Programming Praxis


Home | Pages | Archives


Crypt(1), Again

November 1, 2019 9:00 AM

In the previous exercise I tried to write a replacement for the old Unix crypt(1) program, but never did figure out how to enter a password in Chez Scheme. So today I have the program in C.

To answer some questions that came up in the previous exercise: Yes, I know about ccrypt. Yes, I know that I should not rely on any cryptographic code I write. Yes, the original Unix crypt wasn’t very secure, though at least I can claim that my crypt is better now than the Unix crypt was at the time.

You can see my crypt program on the next page.

Posted by programmingpraxis

Categories: Exercises

Tags:

5 Responses to “Crypt(1), Again”

  1. #! /usr/bin/env python3
    
    # Crypt - based on Programming Praxis
    # https://programmingpraxis.com/2019/10/29/crypt1/
    
    """
    Encrypt and decrypt using RC4drop512 algorithm
    
    Input: stdin
    Output: stdout
    
    **** THIS IS NOT CRYPTOGRAPHICALLY SECURE ****
    This script is NOT intended to protect sensitive data,
    and should NOT be relied upon for that purpose.
    Use professional crypto instead.
    """
    
    # TODO: add command line options to read/output in raw hex or base64
    
    import sys
    
    from getpass import getpass
    
    def key_scheduler(key):
        klen = len(key)
        key_array = bytearray(range(256))
        j = 0
        for i in range(256):
            j = (j + key_array[i] + key[i % klen]) % 256
            key_array[i], key_array[j] = key_array[j], key_array[i]
        return key_array
    
    def key_stream_generator(key_array, drop_bytes=512):
        i = j = 0
        for drop in range(drop_bytes):
            i = (i + 1) % 256
            j = (j + key_array[i]) % 256
            key_array[i], key_array[j] = key_array[j], key_array[i]
        while True:
            i = (i + 1) % 256
            j = (j + key_array[i]) % 256
            key_array[i], key_array[j] = key_array[j], key_array[i]
            yield key_array[(key_array[i] + key_array[j]) % 256]
    
    def process(plain_stream, key_stream_generator):
        cypher_stream = bytearray()
        for c in plain_stream:
            cypher_stream.append(c ^ next(key_stream_generator))
        return bytes(cypher_stream)
    
    if __name__ == '__main__':
        key = getpass('Enter key: ')
        key_chk = getpass('Repeat key: ')
        if key != key_chk: # Check keys match
            sys.stderr.write(f'ERROR: {sys.argv[0]}: Keys do not match')
            sys.exit(1)
        if not key: # Check key is not null (getpass returns empty string, not None)
            sys.stderr.write(f'ERROR: {sys.argv[0]}: Null key')
            sys.exit(1)
        key = bytes(key, encoding='utf-8')
        key_array = key_scheduler(key)
        key_stream_gen = key_stream_generator(key_array)
        in_stream = sys.stdin.buffer.read() # use .buffer to access as bytes
        output = process(in_stream, key_stream_gen)
        sys.stdout.buffer.write(output) # use .buffer to write as bytes
    

    By Alex B on November 1, 2019 at 11:06 AM

  2. I’m sure no-one was suggesting you personally were unaware of these things, I certainly wasn’t.

    It occurs to me that both your solution and mine are deeply flawed – the keystream depends only on key so, as is well known, if two messages are encrypted with the same key, xoring them together gives the xor of the two plaintexts, with the two identical keystreams cancelling each other out, and a skilled cryptographer will be able to split out the two messages and reconstruct the keystream (this is how the brilliant John Tiltman made the first inroad into the Lorenz cipher at Bletchley Park).

    Usual solution is to use a salt or nonce value to perturb the encryption, here’s Speck again, with a randomly generated salt, written as the first 8 bytes of the ciphertext. Unfortunately, this means we have to distinguish between encryption and decryption modes:

    int main(int argc, char *argv[]) {
      bool decrypt = argc > 1 && strcmp(argv[1],"-d") == 0;
      uint64_t pt[2]{0}, ct[2], K[2], data[2];
      auto N = sizeof(pt);
      setpass(K,N);
      if (decrypt) {
        read(0,&pt[1],sizeof(pt[1]));
      } else {
        int fd = open("/dev/urandom",O_RDONLY);
        read(fd,&pt[1],sizeof(pt[1]));
        close(fd);
        write(1,&pt[1],sizeof(pt[1]));
      }
      while(true) {
        auto nread = read(0,data,N);
        if (nread <= 0) break;
        Speck128(pt,ct,K);
        data[0] ^= ct[0]; data[1] ^= ct[1];
        write(1,data,nread);
        ++pt[0];
      }
    }
    

    By matthew on November 1, 2019 at 8:14 PM

  3. There’s a serious flaw in that solution as well – the read at line 15 isn’t guaranteed to read N bytes, even if we aren’t at EOF (eg. if reading from the terminal). Better to use fread (and fwrite), which may also be more efficient since the internal buffering mean fewer syscalls are made. open and read are the right thing for /dev/urandom, which we are told doesn’t block, but in fact on Linux we can use “getrandom(2)” to make life even easier:

    int main(int argc, char *argv[]) {
      uint64_t pt[2]{0}, ct[2], K[2]{0}, data[2];
      auto N = sizeof(pt);
      setpass(K,N);
      bool decrypt = argc > 1 && strcmp(argv[1],"-d") == 0;
      if (decrypt) {
        fread(&pt[1],1,sizeof(pt[1]),stdin);
      } else {
        getrandom(&pt[1],sizeof(pt[1]),0);
        fwrite(&pt[1],1,sizeof(pt[1]),stdout);
      }
      while(true) {
        auto nread = fread(data,1,N,stdin);
        if (nread <= 0) break;
        Speck128(pt,ct,K);
        data[0] ^= ct[0]; data[1] ^= ct[1];
        fwrite(data,1,nread,stdout);
        ++pt[0];
      }
    }
    

    By matthew on November 2, 2019 at 10:52 AM

  4. Updated my Python solution to eliminate the issue raised by matthew.

    #! /usr/bin/env python3
    
    # Crypt - based on Programming Praxis
    # https://programmingpraxis.com/2019/10/29/crypt1/
    
    """
    Encrypt and decrypt using RC4drop512 algorithm
    
    Input: stdin
    Output: stdout
    
    **** THIS IS NOT CRYPTOGRAPHICALLY SECURE ****
    This script is NOT intended to protect sensitive data,
    and should NOT be relied upon for that purpose.
    Use professional crypto instead.
    """
    
    # TODO: add command line options to read/output in raw hex or base64
    
    import argparse
    import os
    import sys
    
    from getpass import getpass
    
    def parse_args():
        parser = argparse.ArgumentParser(
            description='Encrypt and decrypt using RC4drop512 algorithm',
            prefix_chars=r'/-@')
        parser.add_argument('-d', '--decrypt', dest='decrypt', action='store_true',
                            help='decrypt mode (default is encrypt)')
        return(parser.parse_args())
    
    def make_nonce(length=8):
        return os.urandom(length)
    
    def key_scheduler(key, nonce=None):
        if nonce is not None:
            key = key + nonce
        klen = len(key)
        key_array = bytearray(range(256))
        j = 0
        for i in range(256):
            j = (j + key_array[i] + key[i % klen]) % 256
            key_array[i], key_array[j] = key_array[j], key_array[i]
        return key_array
    
    def key_stream_generator(key_array, drop_bytes=512):
        i = j = 0
        for drop in range(drop_bytes):
            i = (i + 1) % 256
            j = (j + key_array[i]) % 256
            key_array[i], key_array[j] = key_array[j], key_array[i]
        while True:
            i = (i + 1) % 256
            j = (j + key_array[i]) % 256
            key_array[i], key_array[j] = key_array[j], key_array[i]
            yield key_array[(key_array[i] + key_array[j]) % 256]
    
    def encrypt(plain_stream, key, nonce=None):
        if nonce is None:
            nonce = make_nonce()
        key_array = key_scheduler(key, nonce)
        key_stream_gen = key_stream_generator(key_array)
        return nonce + process(plain_stream, key_stream_gen)
    
    def decrypt(cypher_stream, key):
        nonce, cypher_stream = cypher_stream[:8], cypher_stream[8:]
        key_array = key_scheduler(key, nonce)
        key_stream_gen = key_stream_generator(key_array)
        return process(cypher_stream, key_stream_gen)    
    
    def process(input_stream, key_stream_generator):
        output_stream = bytearray()
        for c in input_stream:
            output_stream.append(c ^ next(key_stream_generator))
        return bytes(output_stream)
    
    if __name__ == '__main__':
        args = parse_args()
        key = getpass('Enter key: ')
        key_chk = getpass('Repeat key: ')
        if key != key_chk: # Check keys match
            sys.stderr.write(f'ERROR: {sys.argv[0]}: Keys do not match')
            sys.exit(1)
        if not key: # Check key is not null (getpass returns empty string, not None)
            sys.stderr.write(f'ERROR: {sys.argv[0]}: Null key')
            sys.exit(1)
        key = bytes(key, encoding='utf-8')
        in_stream = sys.stdin.buffer.read() # use .buffer to access as bytes
        if args.decrypt:
            output = decrypt(in_stream, key)
        else:
            output = encrypt(in_stream, key)
        sys.stdout.buffer.write(output) # use .buffer to write as bytes
    

    By Alex B on November 8, 2019 at 9:52 AM

  5. Here’s a solution in C. The code depends on wraparound for some of the modular arithmetic.

    #include <assert.h>
    #include <pwd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    
    typedef unsigned char byte;
    
    static byte S[256];
    
    static void swap(int i, int j) {
      byte tmp = S[i];
      S[i] = S[j];
      S[j] = tmp;
    }
    
    static byte next() {
      static byte i = 0;
      static byte j = 0;
      ++i;
      j += S[i];
      swap(i, j);
      return S[(S[i] + S[j]) & 0xFF];
    }
    
    int main(void) {
      // XXX: can't check if input was truncated by getpass.
      char* key = getpass("Key: ");
      int k = strlen(key);
      assert(k >= 1 && k <= 256);
      for (int i = 0; i < 256; ++i) S[i] = i;
      for (int i = 0, j = 0; i < 256; ++i) {
        j = (j + S[i] + key[i % k]) & 0xFF;
        swap(i, j);
      }
      for (int i = 0; i < 512; ++i) next();
      int c;
      while ((c = getchar()) != EOF) printf("%c", c ^ next());
      return EXIT_SUCCESS;
    }
    

    Example usage (the key is “praxis”):

    $ echo 'programming' | ./a.out > encrypted
    Key: 
    
    $ hexdump encrypted 
    0000000 42 1d d7 56 d1 75 33 84 fb fe 3d aa            
    000000c
    
    $ ./a.out < encrypted 
    Key: 
    programming
    

    By Daniel on February 22, 2020 at 12:26 AM

Leave a Reply



Mobile Site | Full Site


Get a free blog at WordPress.com Theme: WordPress Mobile Edition by Alex King.