Crypt(1)

October 29, 2019

I was surprised to learn recently that modern Linux systems do not provide a crypt command, which was common in older Unix systems. So I decided to write one.

The interface to crypt is simple: it reads from standard input and writes to standard output, requesting a key from the console using a method that doesn’t echo the key to the screen. The program has no command-line arguments.

The cipher is symmetric, so encryption and decryption use the same key; the ciphering algorithm I choose is <a href="“>RC4-drop512, which isn’t exactly state-of-the-art encryption, but is simple and good enough for casual use. The base algorithm is RC4; dropping the first 512 characters of the generated keystream eliminates a potential weakness in the bas RC4 key scheduling algorithm.

Your task is to write a crypt program as described above. When you are finished, you are welcome to read a suggested solution, or to post your own solution or discuss the exercise in the comments below.

Advertisement

Pages: 1 2

6 Responses to “Crypt(1)”

  1. John Cowan said

    Turning off echoing is inherently system-dependent and involves calling C functions.

    If your C library has getpass(), that does the whole job; glibc provides it, but it is not any kind of standard.

    Otherwise, on Posix you can call tcgetattr(), turn off the echo flag, and call tcsetaddr() before reading the password, and the same process to turn on the echo flag. Source code for getpass() at https://opensource.apple.com/source/Libc/Libc-167/gen.subproj/getpass.c.auto.html shows how.

    The approach in Windows is basically the same using GetConsoleMode() and SetConsoleMode().

    If you have a typical Posix environment but don’t have access to the tc* functions, then system(“stty -echo”) and system(“stty echo”) will do the trick.

  2. fd said

    My apologies if I’m preaching to the choir, and I know this isn’t the point of the post, but ccrypt(1) claims to be a better crypt:

    DESCRIPTION

       ccrypt is a utility for encrypting and decrypting files and streams. It was designed to replace the standard unix crypt util‐
       ity, which is notorious for using a very weak encryption algorithm.  ccrypt is based on the Rijndael block cipher, a  version
       of  which was also chosen by the U.S. government as the Advanced Encryption Standard (AES, see http://www.nist.gov/aes). This
       cipher is believed to provide very strong cryptographic security.
    
  3. matthew said

    As fd says, there are better alternatives to crypt (which, if https://en.wikipedia.org/wiki/Crypt_(Unix) is anything to go by, was never intended for serious use) – as well as ccrypt, you could use “openssl enc” and have access to a wider range of ciphers (or libressl equivalent, or there’s probably something in GPG).

    If you want to roll your own, something more contemporary than RC4 would be better. Here we use Speck, designed by those nice people at the NSA for use in limited resource devices, which is about as simple as modern ciphers come. It’s a block cipher so we use it in counter mode to get a stream cipher. Code somewhat modernized from 128 bit example in Speck paper, the termios code is fairly standard (cf. the link John posted above), proper error checking left as an exercise.

    Statutory warning: https://classicprogrammerpaintings.com/post/148027314949/we-rolled-our-own-crypto-pieter-bruegel-the

    #include <stdint.h>
    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <termios.h>
    #include <x86intrin.h>
    
    static inline void R(uint64_t &x, uint64_t &y, uint64_t k) {
      x = _lrotr(x,8); x += y; x ^= k;
      y = _lrotl(y,3); y ^= x;
    }
    
    void Speck128(const uint64_t pt[2], uint64_t ct[2], const uint64_t K[2]) {
      auto B = K[1], A = K[0];
      ct[0] = pt[0]; ct[1] = pt[1];
      for (auto i = 0; i < 32; i++) {
        R(ct[1], ct[0], A);
        R(B, A, i);
      }
    }
    
    void setpass(void *pass, int N) {
      auto prompt = "Password: ";
      auto fd = open("/dev/tty",O_RDWR);
      write(fd,prompt,strlen(prompt));
      termios term;
      tcgetattr(fd,&term);
      const auto term0 = term;
      term.c_lflag &= ~ECHO;
      tcsetattr(fd,TCSAFLUSH,&term);
      char c, data[N]{0};
      for (int i = 0; ; i++) {
        if (read(fd,&c,1) < 1 || c == '\n') break;
        data[i%N] ^= c; // extra chars wraparound
      }
      write(fd,"\n",1);
      tcsetattr(fd,TCSAFLUSH,&term0);
      memcpy(pass,data,N);
    }
    
    int main() {
      uint64_t pt[2]{0}, ct[2], K[2], data[2];
      auto N = sizeof(pt);
      setpass(K,N);
      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] || ++pt[1];
      }
    }
    
    $ g++ -O3 -Wall crypt.cpp -Wno-unused-result -o crypt
    $ echo Hello World | ./crypt > ct.data
    Password: 
    $ ./crypt < ct.data
    Password: 
    Hello World
    
  4. chaw said

    I found the most interesting part of this exercise to be the reading
    of the password from the terminal with no echo, so here is a solution
    to just that part, in Kawa Scheme, with java.io.Console.readPassword
    doing all the low-level work. It’s essentially the Java equivalent of
    the C getpass solution suggested by John Cowan.

    (import (scheme base)
            (scheme write)
            (class java.lang System)
            (class java.io Console))
    
    (define (read-line-from-console/no-echo . args)
      (let ((con ::Console
                 (System:console)))
        (and (not (eq? #!null con))
             (let ((carr :: char[]
                         (apply con:readPassword args)))
               (and (not (eq? #!null carr))
                    (String carr))))))
    
    (write (read-line-from-console/no-echo))
    (newline)
    (write (read-line-from-console/no-echo "Enter password: "))
    (newline)
    

  5. […] the previous exercise I tried to write a replacement for the old Unix crypt(1) program, but never did figure out how to […]

  6. Daniel said

    Here’s a solution in Python 3.8.

    from getpass import getpass
    import sys
    
    key = getpass('Key: ').encode()
    k = len(key)
    assert 1 <= k <= 256
    
    S = list(range(256))
    j = 0
    for i in range(256):
        j = (j + S[i] + key[i % k]) % 256
        S[i], S[j] = S[j], S[i]
    
    def keystream():
        i = j = 0
        while True:
            i = (i + 1) % 256
            j = (j + S[i]) % 256
            S[i], S[j] = S[j], S[i]
            K = S[(S[i] + S[j]) % 256]
            yield K
    
    ks = keystream()
    for _ in range(512):
        next(ks)
    while bytes_ := sys.stdin.buffer.read(1):
        byte = bytes_[0]
        K = next(ks)
        sys.stdout.buffer.write(bytes([K ^ byte]))
    

    Example usage (the key is “praxis”):

    $ echo 'programming' | python3.8 praxis.py > encrypted
    Key: 
    
    $ hexdump encrypted 
    0000000 42 1d d7 56 d1 75 33 84 fb fe 3d aa            
    000000c
    
    $ cat encrypted | python3.8 praxis.py 
    Key: 
    programming
    

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: