/* ciphersaber.c - an implementation of the CipherSaber algorithm
 * Written 2000-04-28 by Decklin Foster. Public domain.
 *
 * Usage: ciphersaber -e 'your key' <plaintext >encrypted
 *        ciphersaber -d 'your key' <encrypted >plaintext
 *
 * Notes: requires a Linux-style /dev/random device. Remember to
 * redirect encrypted output to somwhere so it doesn't hork your
 * terminal. */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define LEN 256
#define RANDBYTES 10
#define BUFSIZE 8192
#define streq(a, b) (strcmp(a, b) == 0)

/* Two boring utility functions. */

void swap(unsigned char *a, int i, int j)
{
    unsigned char temp = a[i];
    a[i] = a[j];
    a[j] = temp;
}

void die_usage()
{
    fprintf(stderr, "usage: ciphersaber -[de] key\n");
    exit(EXIT_FAILURE);
}

/* Another utility function. If you need to build this program on a
 * system lacking /dev/random, this can be trivially be rewritten to
 * call rand(), but be warned that most versions of rand() are not all
 * that cryptographically secure. */

void fill_rand(unsigned char *dest, int len)
{
    FILE *dev_random = fopen("/dev/random", "rb");
    fread(dest, 1, len, dev_random);
    fclose(dev_random);
}

/* Grab the first part of the key from argv, then get the 10 random
 * unsigned chars and tack them on. If the first part is very long, only copy
 * so many unsigned chars that we have enough room left for the random ones.
 * Where the random unsigned chars come from depends on whether we are
 * encrypting or decrypting. Return the key length for use later. */

int setup_key(unsigned char *key, int argc, const char **argv)
{
    int keylen;

    if (argc != 3) die_usage();

    strncpy(key, argv[2], LEN-RANDBYTES);
    keylen = strlen(key);

    if (streq(argv[1], "-e")) {
        fill_rand(key+keylen, RANDBYTES);
        fwrite(key+keylen, 1, RANDBYTES, stdout);
    } else if (streq(argv[1], "-d")) {
        fread(key+keylen, 1, RANDBYTES, stdin);
    } else {
        die_usage();
    }

    return keylen + RANDBYTES;
}

/* Part 1 of the RC-4-ish algorithm. This code follows directly from
 * the textual description at the CipherSaber Page. */

void setup_state(unsigned char *state, unsigned char *key, int keylen)
{
    int i, j = 0;

    for (i=0; i<LEN; i++) state[i] = i;

    for (i=0; i<LEN; i++) {
        j = (j + state[i] + key[i % keylen]) % LEN;
        swap(state, i, j);
    }
}

/* Part 2 of the RC-4-ish algorithm. Again, a simple translation from
 * the English version at http://ciphersaber.gurus.com/. */

void do_encryption(unsigned char *state, FILE *input, FILE *output)
{
    unsigned char buf[BUFSIZE];
    int i = 0, j = 0, k, count;

    while ((count = fread(buf, 1, sizeof buf, input))) {
        for (k=0; k<count; k++) {
            i = (i + 1) % LEN;
            j = (j + state[i]) % LEN;
            swap(state, i, j);
            buf[k] ^= state[(state[i] + state[j]) % LEN];
        }
        fwrite(buf, 1, count, output);
    }
}

/* This should be simple enough to follow. */

int main(int argc, const char **argv)
{
    unsigned char state[LEN], key[LEN] = {0};
    int keylen;

    keylen = setup_key(key, argc, argv);
    setup_state(state, key, keylen);
    do_encryption(state, stdin, stdout);

    return EXIT_SUCCESS;
}
