RC5 in 6 lines of perl5


#!/bin/perl -s-- RC5 in perl5! usage: rc5 -k=<key> -r=<rounds> [-d(ecrypt)]
sub M{($m=pop)+($m<0||-($m>~0))*2**32}sub L{($x=pop)<<($n=31&pop)|2**$n-1&$x>>
32-$n}@L=unpack"V*",pack"H*x3",$k;@S=($P=$T=0xb7e15163,map{$T=M$T+0x9e3779b9}0
..2*$r);sub Y{$_[$_%@_]=L pop,M$_[$_%@_]+M$A+$B}for(0..3*(@S>@L?@S:@L)-1){$A=Y@
S,3;$B=Y@L,M$A+$B}$_='$A=M$A+$S[0];$B=M$B+$S[1]';while(read STDIN,$k,8){($A,$B)
=unpack V2,$k."\0"x3;$d||eval;for(1..@S-2){$d?$B=$A^L 32-($A&31),M$B-$S[@S-$_]
:($A=M$S[$_+1]+L$B,$A^$B);$A^=$B^=$A^=$B}$d&&(y/+/-/,eval);print pack V2,$A,$B}

Many of the perl hacks helping to shorten that from it's previous 8 line incarnation were donated by John Allen <allen@gateway.grumman.com>.

RC5 Block cipher

RC5 is a symmetric block cipher which encrypts in 8 byte blocks. (Well actually there are three implementation options: 64 bit, 32 bit, 16 bit, or 8 bit and the block sizes for these implementations are 16 byte, 8 byte, 4 byte, and 2 byte respectively, I have implemented the 32 bit version. This is denoted RC5-32. The different versions are intended for efficiency on architectures with that number of bits to a word: RC5-32 is intended to work efficiently on a 32 bit architecture, RC5-64 on a 64 bit, etc) In the paper Rivest recommends 12 rounds for the RC5-32.

RC5 is the subject of research at the moment, it is not a tried and trusted cryptosystem. RSA are requesting papers discussing how RC5 stands up to attacks. RSA has a paper on RC5 by Ron Rivest. If you are in the US, you can request a copy of RSAs reference implementation by sending email to <rc5-administrator@rsa.com>. They request you to confirm that you are a US citizen (the marvels of ITAR). See ITAR disclaimer for their suggested wording. I live in the UK, if you come by a copy I wouldn't say no :-) (My keys are on the keyservers, or here :-). I can live without a copy tho' because the paper is available, and Stephen Kapp <skapp@uk.co.compulink.cix> already wrote an implementation of RC5 from the paper for possible inclusion RSAEURO, his plugable replacement for RSADSIs RSAREF library as used by PGP. Also somewhat amusing is that I get the impression that RSA would be happy to surface mail a paper including the source code, just won't export it in electronic form.

RC5 has a variable sized key, and has a variable number of rounds. The number of rounds, and the key size are denoted like this:

RC5-wordsize/rounds/keysize

The wordsize is written in bits, and the keysize in bytes. The perl implementation above has a word size of 32, the key size and rounds are selectable on the command line, with this syntax:

% rc5 -k=1234abcdef1234abcdef -r=12 < plaintext > ciphertext
The key is given in hex, the above corresponds to RC5-32/12/10 because the key is 20 hex nibbles, or 10 bytes long, and because the number of rounds selected (with -r) are 12.

Using the perl implementation of RC5

You must give a key in hex on the command line, and select the number of rounds, as described above. The program encrypts standard input to standard output. In this example we encrypt the message "test message" with the 32 bit hex key "12abcdef" (such a small key size is insecure, you would choose much larger keys, 128 bits (16 bytes) or more for reasonable security). The -e flag is optional, encryption is the default function. To decrypt the -d flag must be given. The -k=12abcdef selects the key in hex to be 12abcdef. The -r=12 selects 12 rounds, the number of rounds must always be specified. The message is encrypted from stdin to stdout. In the example below stdout is redirected to the file "test.rc5":
% echo test message | rc5 -e -k=12abcdef -r=12 > test.rc5
To decrypt the message you just reverse the process giving the same key and using the encrypted file:
% rc5 -d -k=12abcdef -r=12 < test.rc5
You will notice (or more properly you might notice - unix shells seem to silently eat ascii(0)s) that the program pads the decrypted message to a multiple of 8 bytes with ASCII(0)s on decrypt. This is because it can not tell how big the original file was when it is decrypting. If this bothers you and you care, just store the file size either at the begining of the plaintext message so that the size gets encrypted, or store at the begining of the encrypted stream. Then do the appropriate thing on decrypt.

Conformance Testing

From the appendix of Rivest's paper on RC5 here are a set of test keys:

RC5-32/12/16 examples

  1. key = 00000000000000000000000000000000
    plaintext 00000000 00000000 ---> ciphertext EEDBA521 6D8F4B15

  2. key = 915F4619BE41B2516355A50110A9CE91
    plaintext EEDBA521 6D8F4B15 ---> ciphertext AC13C0F7 52892B5B

  3. key = 783348E75AEB0F2FD7B169BB8DC16787
    plaintext AC13C0F7 52892B5B ---> ciphertext B7B3422F 92FC6903

  4. key = DC49DB1375A5584F6485B413B5F12BAF
    plaintext B7B3422F 92FC6903 ---> ciphertext B278C165 CC97D184

  5. key = 5269F149D41BA0152497574D7F153125
    plaintext B278C165 CC97D184 ---> ciphertext 15E444EB 249831DA
The paper specifies that the input and output to the program is expected to be in little-endian byte order (ie PCs), most UNIX machines are big-endian, so if you are comparing the hex output to the test keys, it might look a little confusing.

Here is a transcript of execution on a UNIX machine:

  1. % perl -e "print pack(L2,0)" > PLAIN1
    % perl -0777e 'for(unpack("V*",<>)){printf("%lx ",$_);}' < PLAIN1
    00000000 00000000
    % rc5 -k=00000000000000000000000000000000 -r=12 < PLAIN1 > PLAIN2
    % perl -0777e 'for(unpack("V*",<>)){printf("%lx ",$_);}' < PLAIN2
    eedba521 6d8f4b15
    
  2. % rc5 -k=915F4619BE41B2516355A50110A9CE91 -r=12 < PLAIN2 > PLAIN3
    % perl -0777e 'for(unpack("V*",<>)){printf("%lx ",$_);}' < PLAIN3
    ac13c0f7 52892b5b
    
  3. % rc5 -k=783348E75AEB0F2FD7B169BB8DC16787 -r=12 < PLAIN3 > PLAIN4
    % perl -0777e 'for(unpack("V*",<>)){printf("%lx ",$_);}' < PLAIN4
    b7b3422f 92fc6903
    
  4. % rc5 -k=DC49DB1375A5584F6485B413B5F12BAF -r=12 < PLAIN4 > PLAIN5
    % perl -0777e 'for(unpack("V*",<>)){printf("%lx ",$_);}' < PLAIN5
    b278c165 cc97d184
    
  5. % rc5 -k=5269F149D41BA0152497574D7F153125 -r=12 < PLAIN5 > RESULT
    % perl -0777e 'for(unpack("V*",<>)){printf("%lx ",$_);}' < RESULT
    15e444eb 249831da
    
The perl -0777e 'for(unpack("V*",<>)){printf("%lx ",$_);}' treats the data on stdin as little endian hex words. It should be portable to both little endian and big endian machines, as should the rc5 perl script.
Comments, html bugs to me (Adam Back) at <adam@cypherspace.org>