zentara has asked for the wisdom of the Perl Monks concerning the following question:

Hi, This is a Friday "waste your time" type of question. :-)

I got interested in trying this, after reading Perl module for language portable encryption. Here are 2 versions of RC4, one in c, and one a perl 3-liner (expanded for clarity). Each works fine by themselves, but one won't decrypt the other. I am almost sure the "internal keys" are being changed from what is entered on the command line, to introduce the incompatibilty. Does anyone know enough to change the perl code to be compatible with the c code?

#!/usr/bin/perl -0777 -- # $0 key infile > outfile # symmetric works both ways @k = unpack( 'C*', pack( 'H*', shift ) ); for ( @t = @s = 0 .. 255 ) { $y = ( $k[ $_ % @k ] + $s[ $x = $_ ] + $y ) % 256; &S; } $x = $y = 0; for ( unpack( 'C*', <> ) ) { $x++; $y = ( $s[ $x %= 256 ] + $y ) % 256; &S; print pack( C, $_ ^= $s[ ( $s[$x] + $s[$y] ) % 256 ] ); } sub S { @s[ $x, $y ] = @s[ $y, $x ] } __END__ <code> <p> <code> /* gcc -o rc4 rc4.c */ #include <stdio.h> #define buf_size 1024 typedef struct rc4_key { unsigned char state[256]; unsigned char x; unsigned char y; } rc4_key; #define swap_byte(x,y) t = *(x); *(x) = *(y); *(y) = t void prepare_key(unsigned char *key_data_ptr, int key_data_len, rc4_ke +y *key) { int i; unsigned char t; unsigned char swapByte; unsigned char index1; unsigned char index2; unsigned char* state; short counter; state = &key->state[0]; for(counter = 0; counter < 256; counter++) state[counter] = counter; key->x = 0; key->y = 0; index1 = 0; index2 = 0; for(counter = 0; counter < 256; counter++) { index2 = (key_data_ptr[index1] + state[counter] + index2) % 256; swap_byte(&state[counter], &state[index2]); index1 = (index1 + 1) % key_data_len; } } void rc4(unsigned char *buffer_ptr, int buffer_len, rc4_key *key) { unsigned char t; unsigned char x; unsigned char y; unsigned char* state; unsigned char xorIndex; short counter; x = key->x; y = key->y; state = &key->state[0]; for(counter = 0; counter < buffer_len; counter++) { x = (x + 1) % 256; y = (state[x] + y) % 256; swap_byte(&state[x], &state[y]); xorIndex = (state[x] + state[y]) % 256; buffer_ptr[counter] ^= state[xorIndex]; } key->x = x; key->y = y; } int main(int argc, char* argv[]) { char seed[256]; char data[512]; char buf[buf_size]; char digit[5]; int hex, rd,i; int n; rc4_key key; if (argc < 2) { fprintf(stderr,"%s key <in >out\n",argv[0]); exit(1); } strcpy(data,argv[1]); n = strlen(data); if (n&1) { strcat(data,"0"); n++; } n/=2; strcpy(digit,"AA"); for (i=0;i<n;i++) { digit[2] = data[i*2]; digit[3] = data[i*2+1]; sscanf(digit,"%x",&hex); seed[i] = hex; } prepare_key(seed,n,&key); rd = fread(buf,1,buf_size,stdin); while (rd>0) { rc4(buf,rd,&key); fwrite(buf,1,rd,stdout); rd = fread(buf,1,buf_size,stdin); } }

I'm not really a human, but I play one on earth. flash japh

Replies are listed 'Best First'.
Re: perl encryptions keys vs. c
by polettix (Vicar) on Apr 22, 2005 at 15:33 UTC
    There's something wrong with:
    @k = unpack( 'C*', pack( 'H*', shift ) );
    I tried with key "ciao" and @k ends up with only two elements instead of 4 as in the C version, which operates on all 4 characters. I really don't know anything about pack/unpack, so I cannot solve the problem without studying it, and it's friday afternoon :)

    Update: sorry, fried in friday, I completely missed the pre-calculations over the input key, ikegami answer forced me to take a deeper look. I swear I'll re-read it, in the meantime "--" me!

    Flavio (perl -e 'print(scalar(reverse("\nti.xittelop\@oivalf")))')

    Don't fool yourself.
      The Perl version is expecting the key to be represented as hex digits. Try '6369616F' instead of 'ciao' for the Perl version (making @k = ('c', 'i', 'a', 'o')). I'm too lazy to figure out whatI don't know what the C version expects.
Re: perl encryptions keys vs. c
by polettix (Vicar) on Apr 23, 2005 at 22:02 UTC
    zentara, as promised I returned on the analysis of the codes to get a solution for this interworking. I don't know if multiple replies to a post is frowned, I make a new reply just to allow people "--" me on the other sloppy response :)

    The initial key's state shuffling is different in the two programs, mainly due to the unpack/pack trick (at least in this I did not make a mistake). Quoting from perldoc -f pack:

    The "h" and "H" fields pack a string that many nybbles (4-bit groups, representable as hexadecimal digits, 0-9a-f) long.

    Each byte of the input field of pack() generates 4 bits of the result. For non- alphabetical bytes the result is based on the 4 least-significant bits of the input byte, i.e., on "ord($byte)%16". In par- ticular, bytes "0" and "1" generate nyb- bles 0 and 1, as do bytes "\0" and "\1". For bytes "a".."f" and "A".."F" the result is compatible with the usual hexadecimal digits, so that "a" and "A" both generate the nybble "0xa==10". The result for bytes "g".."z" and "G".."Z" is not well-defined.

    Which I read as: "never, never, never count on it for evaluating portable shuffling keys in ARCFOUR implementations!".

    The initial shuffling in the C program is even more akward, if possible: trying to interpret characters out of [0-9a-fA-F] as hexadecimal can lead to a situation in which the input key is virtually ignored (for example, key "zentara!" leads to "AAAAAAAA" in hex, much like "pppppppp"...).

    To make both programs use the same shuffling data, I've simply used the input key - how good this system is I leave to the experts. This is straightforward in C, and requires a bit of map mangling in Perl (C has to be better for something. :-) Interworking has been tested on a very little test file, I leave further analysis to the interested ones.

    The Perl modified code is the following. I've added strict and warnings just to be sure that there wasn't anything spoiling the output under the hood. The shuffling array @k is evaluated from the input key applying ord() to each input char. Yes, I could use unpack() instead of split().

    #!/usr/bin/perl -0777 -- # $0 key infile > outfile # symmetric works both ways use strict; use warnings; my @k = map { ord($_) } split //, shift; my (@s, $x, $y); $y = 0; for ( my @t = @s = 0 .. 255 ) { $y = ( $k[ $_ % @k ] + $s[ $x = $_ ] + $y ) % 256; &S; } $x = $y = 0; for ( unpack( 'C*', <> ) ) { $x++; $y = ( $s[ $x %= 256 ] + $y ) % 256; &S; print pack( 'C', $_ ^= $s[ ( $s[$x] + $s[$y] ) % 256 ] ); } sub S { @s[ $x, $y ] = @s[ $y, $x ] }
    The C code is virtually unmodified (for being a C code, of course). I added a couple of includes and removed some unneeded variables to shut the warnings off (-Wall was added for the same reasons I used strict and warnings). Of course, I eliminated the initial key mangling to make the program use it directly for state shuffling inside key.
    /* gcc -o rc4 rc4.c -Wall */ #include <stdio.h> #include <string.h> #include <stdlib.h> #define buf_size 1024 typedef struct rc4_key { unsigned char state[256]; unsigned char x; unsigned char y; } rc4_key; #define swap_byte(x,y) t = *(x); *(x) = *(y); *(y) = t void dumphex(unsigned char* data, unsigned long size) { unsigned long i; printf("%lu elementi\n\t", size); for (i = 0; i < size; ) { printf("%02X ", data[i]); if (! (++i % 16)) { printf("\n\t"); } } printf("\n"); } void prepare_key(unsigned char *key_data_ptr, int key_data_len, rc4_ke +y *key) { unsigned char t; unsigned char index1; unsigned char index2; unsigned char* state; short counter; state = &key->state[0]; for(counter = 0; counter < 256; counter++) state[counter] = counter; key->x = 0; key->y = 0; index1 = 0; index2 = 0; for(counter = 0; counter < 256; counter++) { index2 = (key_data_ptr[index1] + state[counter] + index2) % 256; swap_byte(&state[counter], &state[index2]); index1 = (index1 + 1) % key_data_len; } } void rc4(unsigned char *buffer_ptr, int buffer_len, rc4_key *key) { unsigned char t; unsigned char x; unsigned char y; unsigned char* state; unsigned char xorIndex; short counter; x = key->x; y = key->y; state = &key->state[0]; for(counter = 0; counter < buffer_len; counter++) { x = (x + 1) % 256; y = (state[x] + y) % 256; swap_byte(&state[x], &state[y]); xorIndex = (state[x] + state[y]) % 256; buffer_ptr[counter] ^= state[xorIndex]; } key->x = x; key->y = y; } int main(int argc, char* argv[]) { char seed[256]; char buf[buf_size]; int rd; rc4_key key; if (argc < 2) { fprintf(stderr,"%s key <in >out\n",argv[0]); exit(1); } strcpy(seed,argv[1]); prepare_key(seed, strlen(seed), &key); rd = fread(buf,1,buf_size,stdin); while (rd>0) { rc4(buf,rd,&key); fwrite(buf,1,rd,stdout); rd = fread(buf,1,buf_size,stdin); } return 0; }
    As a final note, it has to be observed that the C approach seems more robust for large input files: read a chunk of data, encrypt and write. In the Perl version, slurp mode in input (according to "-0777") makes the process read the entire file in memory before analysing it. Transforming the encription cycle in the Perl source is left as an exercise for the memory impaired Monk.

    Flavio (perl -e 'print(scalar(reverse("\nti.xittelop\@oivalf")))')

    Don't fool yourself.
      Wow, thanks frodo72, I wish there was a way to give you 100 points for figuring that out.

      I'm not really a human, but I play one on earth. flash japh