1

I am working on trying to encrypt a text file via RC4 with a cpp file that I wrote with openssl/rc4 headers, and then decrypt via the command line to show that my implementation is correct.

My terminal command for the file is below, and the cpp file is below it, along with the terminal compile command I used for it.

There barely seems to be any information about this anywhere online, outside of some vague youtube videos that explain how the RC4 cypher works(which I already know). I can't find anything in the man pages to explain the details of the openssl implementation.

Any pointers on why my cpp file isn't decrypting to the original content would be much appreciated. I am tearing my hair out over here trying to figure this out. Thanks in advance.

(and yes, I understand there are vulnerabilities that make RC4 less of a good option, but right now, I just want to understand how these work)

command line encrypt:

openssl rc4-40 -k PASSword123 -in /home/chris/Desktop/test.txt -out /home/chris/Desktop/ssloutput.txt -p -nosalt

cpp file compilation:

g++ rc4.cpp -o rc4 -lssl -lcrypto

cpp file:

#include <openssl/rc4.h>
#include <string>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
    int fd = open("/home/chris/Desktop/ssloutput.txt", O_RDWR);
    unsigned char keygen[12] = "PASSword123";
    RC4_KEY key;

    struct stat st;
    fstat(fd, &st);
    int size = st.st_size;

    unsigned char* fileIn;
    fileIn = (unsigned char*) calloc(size, sizeof(char));
    pread(fd, fileIn, size, 0);
    unsigned char *fileOut = (unsigned char*)malloc(size);

    RC4_set_key(&key, 16, keygen);
    RC4(&key, size, fileIn, fileOut);
    close(fd);

    int fd2 = open("/home/chris/Desktop/rc4output.txt", O_RDWR | O_CREAT);
    pwrite(fd2, fileOut, size, 0);
    close(fd2);

    free(fileIn);
    free(fileOut);

    return 0;
}
Chris
  • 95
  • 9
  • Are the sizes of the encrypted and plain files identical? – Shawn Apr 11 '19 at 01:13
  • Yes, they are both 116 bytes. Shouldn't matter even if they weren't though(Although they would technically always be with RC4). The algorithm is supposed to be symmetrical. – Chris Apr 11 '19 at 01:17
  • If you’re using `rc4-40` on the CLI, surely you should be passing `5` to `RC4_set_key()` as the key size? Otherwise if you want to use 16 byte keys, use `rc4` mode on the CLI. – Alastair McCormack Apr 11 '19 at 01:18
  • This is true, I should have caught that. But regardless, when I run just rc4 in the CLI, instead of rc4-40, it should match up with the key size = 16, but the files still don't match up. – Chris Apr 11 '19 at 01:35
  • On a related note, if `keygen` is 12 bytes (11 really since you shouldn't be counting the trailing 0), why are you telling `RC4_set_key()` that it's 16? That probably causes all sorts of lovely undefined behavior from out of bounds array access – Shawn Apr 11 '19 at 01:42
  • Because on one of the very few sources of information I could find on this, the allowed sizes in the CLI command are supposedly 128, 64, or 40 bits. And the RC4 function is supposed to pad the key to the proper length. – Chris Apr 11 '19 at 01:45
  • You're lying to it about the size of the key. That's just asking for trouble and things not working. – Shawn Apr 11 '19 at 01:48
  • Well from everything I can find on it shows that it pads the input. Even the RC4 keystream generator, on a high-level, pads the key to the proper length with a modulus function. And most implementations I've seen on it don't use, 40, 64, or 128 bit keys, despite those being the only available options in the CLI. Just to be safe though, I changed my key to PASSword1234567 to make a size 16 array, and that still didn't decrypt it properly. Maybe its the input type for the key? From what I understand, the CLI makes a hex key from the input, but idk about the C/C++ functions. – Chris Apr 11 '19 at 01:59
  • 1
    That's a 15 byte key... If you're still telling the function to use 16, yeah, still going to be a problem. – Shawn Apr 11 '19 at 02:17
  • I've also tried unsigned char keygen[17] = "PASSword12345678"; Still didn't work. I tried that key passing the full size of the array (17) into the rc4 function call, and I also tried it with passing the size (16) in to ignore the trailing null character, but neither worked. – Chris Apr 11 '19 at 02:20
  • Oooh, this SO question might be really helpful: https://stackoverflow.com/questions/9329631/rc4-doesnt-work-correctly-with-openssl-command – Shawn Apr 11 '19 at 02:34
  • Yeah, that's actually the exact post I was looking at, but without being able to compare to his C implementation, I'm not sure where/if I'm going wrong with the function calls... – Chris Apr 11 '19 at 02:39

1 Answers1

1

So, here's a version of your code with a lot of error checking added, bugs fixed, odd stuff (Using O_RDWR with open() when you're only reading or writing? pread()? pwrite()?) cleaned up, and using EVP_BytesToKey() like the -k option to openssl rc4 uses (That was the key (heh) factor):

#include <fcntl.h>
#include <openssl/evp.h>
#include <openssl/rc4.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
  int fd = open("ssloutput.txt", O_RDONLY);
  if (fd < 0) {
    perror("open ssloutput.txt");
    return 1;
  }

  struct stat st;
  if (fstat(fd, &st) < 0) {
    perror("fstat");
    return 1;
  }
  size_t size = st.st_size;

  unsigned char *fileIn = calloc(size, 1);
  if (!fileIn) {
    perror("calloc");
    return 1;
  }
  if (read(fd, fileIn, size) != (ssize_t)size) {
    perror("read");
    return 1;
  }
  close(fd);

  unsigned char *fileOut = malloc(size);
  if (!fileOut) {
    perror("malloc");
    return 1;
  }

  // Prepare the key according to the same rules as openssl rc4 -k foo
  char keygen[] = "PASSword123";
  RC4_KEY key;
  unsigned char computed_key[16];
  if (EVP_BytesToKey(EVP_rc4(), EVP_sha256(), NULL,
                     (const unsigned char *)keygen, strlen(keygen), 1,
                     computed_key, NULL) != 16) {

    fputs("Error calculating rc4 key!\n", stderr);
    return 1;
  }
  // Should match the one printed out by openssl rc4 -p
  fputs("key=", stdout);
  for (size_t n = 0; n < sizeof computed_key; n += 1) {
    printf("%02hhx", computed_key[n]);
  }
  putchar('\n');

  RC4_set_key(&key, sizeof computed_key, computed_key);
  RC4(&key, size, fileIn, fileOut);

  int fd2 = open("rc4output.txt", O_WRONLY | O_TRUNC | O_CREAT, 0644);
  if (fd2 < 0) {
    perror("open rc4output.txt");
    return 1;
  }
  if (write(fd2, fileOut, size) != (ssize_t)size) {
    perror("write");
    return 1;
  }
  close(fd2);

  free(fileIn);
  free(fileOut);

  return 0;
}

Demonstration:

$ cat input.txt
the quick brown dog jumped over the lazy red fox.
$ gcc -o myrc4 -O -Wall -Wextra myrc4.c -lcrypto
$ openssl rc4 -k PASSword123 -md sha256 -p -nosalt -in input.txt -out ssloutput.txt
key=B554C1D224D8EF1738ED4EE238317463
$ ./myrc4
key=B554C1D224D8EF1738ED4EE238317463
$ cat rc4output.txt
the quick brown dog jumped over the lazy red fox.
Shawn
  • 47,241
  • 3
  • 26
  • 60
  • Thank you so much, that worked perfectly (This is a C++ file, though, so I had to cast the malloc and calloc calls to (Unisgned char*). So the issue, you're saying was not including the EVP_Bytes to key? Where did you find the information that it was needed? I'm happy for the code fix, but I'd like to understand how you got there. I would've thought that functionality should have been built in to the lssl and libcrypto libraries? – Chris Apr 11 '19 at 03:17
  • If that was supposed to be C++, why was it using `malloc()`, C casts, C headers, etc at all? Looks like C, I assume C. Didn't notice the tag. Anyways, I got the need for `EVP_BytesToKey()` from that SO question I linked to in a comment. Then it was just a matter of reading that function's documentation. That question demonstrated how to use `-K` to give openssl the exact bytes to use as a key, so I took the other route, demonstrating how to work with `-k`.. – Shawn Apr 11 '19 at 03:23
  • Ah wow, I can't believe I glossed over that sentence. He specifically mentioned the function lol. Anyways, thanks for the help. Much appreciated. – Chris Apr 11 '19 at 03:26
  • The OpenSSL documentation definitely leaves things to be desired, though. – Shawn Apr 11 '19 at 03:29