2

I'm getting crazy to encrypt/decrypt between c and Java but so far the encrypted string in Java and the one in c don't look the same. I've investigated base64 encoding/decoding but after getting crazy to find a library for java and c the respective base64 results looked different! I think it's a problem of converting between Java UTF16 string, saving Byte in java or perhaps AES options (128/256 key, PK5 padding or who knows what), or maybe the UTF8 conversion of the terminal or an absurd combination of the above. So far I get:

  user1@comp1:~/Desktop$ gcc AES.c /usr/lib/libmcrypt.a -lssl -lcrypto -lpthread
  user1@comp1:~/Desktop$ /usr/java/jdk1.6.0_25/bin/javac AES.java
  user1@comp1:~/Desktop$ ./a.out 
      Before encryption: test text 123
      After encryption: 49 -60 66 43 -8 66 -106 0 -14 -44 3 47 65 127 -110 117 
      After decryption: test text 123
  user1@comp1:~/Desktop$ java AES 
     Before encryption: test text 123
     After encryption: -110 21 23 59 47 120 70 -93 -54 -93 -12 -70 -91 83 -113 85 
     After decryption: test text 123

I think I really need help here from someone into low level coding, below is the code for Java and c respectively:

import java.security.MessageDigest;
import java.util.Arrays;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class AES {
  public static void main(String [] args) {
      try {
  String text = "test text 123";
  /*fixed here now it is 128 bits = 16 Bytes*/
  String encryptionKey = "E072EDF9534053A0";

  System.out.println("Before encryption: " + text);

  byte[] cipher = encrypt(text, encryptionKey);

  System.out.print("After encryption: ");
  for (int i=0; i<cipher.length; i++)
        System.out.print(new Integer(cipher[i])+" ");
  System.out.println("");

  String decrypted = decrypt(cipher, encryptionKey);

  System.out.println("After decryption: " + decrypted);

      } catch (Exception e) {
  e.printStackTrace();
      } 
  }

  public static byte[] encrypt(String plainText, String encryptionKey) throws Exception {
      Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding", "SunJCE");
      SecretKeySpec key = new SecretKeySpec(encryptionKey.getBytes("UTF-8"), "AES");
      cipher.init(Cipher.ENCRYPT_MODE, key,new IvParameterSpec(new byte[cipher.getBlockSize()]));
      return cipher.doFinal(plainText.getBytes("UTF-8"));
  }

  public static String decrypt(byte[] cipherText, String encryptionKey) throws Exception{
      Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding", "SunJCE");
      SecretKeySpec key = new SecretKeySpec(encryptionKey.getBytes("UTF-8"), "AES");
      cipher.init(Cipher.DECRYPT_MODE, key,new IvParameterSpec(new byte[cipher.getBlockSize()]));
      return new String(cipher.doFinal(cipherText),"UTF-8");
  }
  }

and

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mcrypt.h>

#include <math.h>
#include <stdint.h>
#include <stdlib.h>

int main()
{
MCRYPT td, td2;
const char * plaintext = "test text 123";
int i;
char *key; /* created using mcrypt_gen_key */
char *IV;
char * block_buffer;
int blocksize;
int keysize = 16; /* 128 bits == 16 bytes */
size_t* sizet;

key = calloc(1, keysize);

/*below dirty trick to be sure the entire key has been padded with \0's */
strcpy(key, "E072EDF9534053A0");
memset(key, '\0', sizeof(key));
strcpy(key, "E072EDF9534053A0");

/*  MCRYPT mcrypt_module_open( char *algorithm, char* algorithm_directory, char* mode, char* mode_directory);
 * This function normally returns an encryption descriptor, or MCRYPT_FAILED on error. 
 */
td = mcrypt_module_open("rijndael-128", NULL, "cbc", NULL);
/*we need two encryption descriptors td and td2 for decryption*/
td2 = mcrypt_module_open("rijndael-128", NULL, "cbc", NULL);

blocksize = mcrypt_enc_get_block_size(td);
block_buffer = calloc(1, blocksize);
/*below to be sure the entire block_buffer has been padded with \0's */
memset(block_buffer, '\0', blocksize);

IV = malloc(mcrypt_enc_get_iv_size(td));
if ((block_buffer == NULL) || (IV == NULL)) {
fprintf(stderr, "Failed to allocate memory\n");
exit(EXIT_FAILURE);
}
for (i = 0; i < mcrypt_enc_get_iv_size(td); i++) {
IV[i] = 0;
}
/*as we can see both td and td2 get same key and IV*/
mcrypt_generic_init(td, key, keysize, IV);
mcrypt_generic_init(td2, key, keysize, IV);

memset(block_buffer, '\0', sizeof(plaintext));
strcpy(block_buffer, plaintext);

printf("Before encryption: %s\n", block_buffer);
mcrypt_generic (td, block_buffer, blocksize);

printf("After encryption: ");
for (i=0; i < blocksize; i++)
    printf("%d ", block_buffer[i]);
printf("\n");

mdecrypt_generic (td2, block_buffer, blocksize);
printf("After decryption: %s\n", block_buffer);

/* deinitialize the encryption thread */
mcrypt_generic_deinit (td);
mcrypt_generic_deinit(td2);
/* Unload the loaded module */
mcrypt_module_close(td);
mcrypt_module_close(td2);
return 0;
}
brice
  • 24,329
  • 7
  • 79
  • 95
dendini
  • 3,842
  • 9
  • 37
  • 74
  • If I wasn't clear enough, I want the encrypted string printed to terminal to look the same in Java and c output, this way I will manage later to send that between Java client and c server and the other way round. – dendini Apr 20 '12 at 14:10
  • you could also just print out the encrypted data as hex (much easier) – ratchet freak Apr 20 '12 at 14:14
  • I tried that but printing to hex in c is not that simple (I saw a discussion here on stackoverflow with 10 different solutions to do that and not even one referring to a standard c function!). Apart from that hex is not very efficient that's why I first tried base64 encode, if you can make the above two work with hex however I'd be thankful – dendini Apr 20 '12 at 14:18
  • @dendini if you use unix(you should!) you can use the command hexdump as a filter. – byrondrossos Apr 20 '12 at 14:21
  • AES is RIJNDAEL-128, not 256? – brice Apr 20 '12 at 14:22
  • I work in Linux, hexdump however works for files, that means I should save encoded strings to file adding another possibility for strings to get altered! I'll try that but I suspect it will only make things more complicated... Yes you're right, AES is rijndael 128, I'll fix that anyway I had already tried swapping rindjael 128 and 256 and both ways the result was different. – dendini Apr 20 '12 at 14:27
  • I notice you did a `strcpy()` to copy the plaintext into `block_buffer`. http://php.net/manual/en/function.mcrypt-generic.php (not C++, but may apply) seems to indicate that the entire block needs to be right-padded with NULLs. Perhaps a `memset(block_buffer, '\0', blocksize)`? – Dan Nissenbaum Apr 20 '12 at 14:28
  • Hey good point Dan, I tried memsetting to '\0' all arrays however the output of the encryption doesn't change so that wasn't an issue with the above code – dendini Apr 20 '12 at 14:43
  • There's a vaguely small chance that it's just a false alarm due to some oddity regarding different ways that the C program and the Java program are displaying the encrypted string in the console window? Perhaps you could check the byte count in both applications, or even (similar to what others have suggested) put a `for` loop that loops through each byte, converts the byte to an `int`, and prints that `int` to the screen for comparison? – Dan Nissenbaum Apr 20 '12 at 14:52
  • Thank you Dan, I tried that, both print 16 numbers however they are all pretty different! now however who knows from byte to int what strange conversions happen in c and in Java!? – dendini Apr 20 '12 at 15:05
  • byte to int should be identical for both, with the possible exception of `-128` - `127` vs. `0` - `255` issues (unsigned vs. signed). That's the purpose of the `byte` data type, as opposed to `char` or `wchar`. – Dan Nissenbaum Apr 20 '12 at 15:47
  • You might be interested in [the Java AES article](http://java.sun.com/developer/technicalArticles/Security/AES/AES_v1.html)... – brice Apr 23 '12 at 11:17

2 Answers2

13

Summary

After resolving all the issues, I get:

$ ./a.out
==C==
plain:   test text 123
cipher:  16 -124 41 -83 -16 -123 61 -64 -15 -74 87 28 63 30 64 78 
decrypt: test text 123

$java AES
==JAVA==
plain:   test text 123
cipher:  16 -124 41 -83 -16 -123 61 -64 -15 -74 87 28 63 30 64 78 
decrypt: test text 123

See below for the code.

Issues

  1. Wrong Cipher: AES is Rijndael-128, which is what the Java encryption uses. However, your C code specifies Rijndael-256, which is not AES. From the Mcrypt docs:

    Rijndael [...] AES if used in 128-bit mode

    Remember that when referring to a cipher as CIPHER-XXX, XXX refers to the block size, not the key length. Indeed, Rijndael-128 will accept keys of 128, 192 and 256 bits. AES, however, refers strictly to 128 bit Rijndael. You will find more information at the usual place.

  2. Incorrect memory initialisation: You do not initialise your memory properly in C, which leaves the bytes between the end of your message and the block limit undefined.

  3. Incorrect Padding: You specify PKCS5Padding in your java code, but you do not pad your plaintext appropriately in C. PKCS5 is actually trivially simple, and is described quite well on the wiki. To pad with PKCS#5, simply ensure that every padding byte is equal to the total length padded:

    ... | DD DD DD DD DD DD DD DD | DD DD DD DD 04 04 04 04 |
    ... | DD DD DD DD DD DD DD DD | DD DD DD DD DD 03 03 03 |
    ... | DD DD DD DD DD DD DD DD | DD DD DD DD DD DD 02 02 |
    etc...
    

    Where DD are data bytes. There are other padding methods, that are explained elsewhere.

  4. Incorrect key handling: You extract the key for your Java program from an hexadecimal encoded string, whereas in your C program, you take the hexadecimal string directly as your key. You must handle your keys consistently for both programs to give the same output.

  5. Different Initialization Vectors on either side: You will need the same Initialization Vector on both sides. Randomly generating your IV is correct. The IV should then be passed along with the cipher-text for decryption on the other side. IVs should not be reused.

Example programs

The following are available on github

import java.security.MessageDigest;
import java.util.Arrays;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class AES {
  /*
   * Please realise that the following IV is terrible.
   * (As easy to crack as ROT13...)
   * Real situations should use a randomly generated IV.
   */
  static String IV = "AAAAAAAAAAAAAAAA";
  /* 
   * Note null padding on the end of the plaintext.
   */
  static String plaintext = "test text 123\0\0\0"; 
  static String encryptionKey = "0123456789abcdef";
  public static void main(String [] args) {
    try {

      System.out.println("==JAVA==");
      System.out.println("plain:   " + plaintext);

      byte[] cipher = encrypt(plaintext, encryptionKey);

      System.out.print("cipher:  ");
      for (int i=0; i<cipher.length; i++){
        System.out.print(new Integer(cipher[i])+" ");
      }
      System.out.println("");

      String decrypted = decrypt(cipher, encryptionKey);

      System.out.println("decrypt: " + decrypted);

    } catch (Exception e) {
      e.printStackTrace();
    } 
  }

  public static byte[] encrypt(String plainText, String encryptionKey) throws Exception {
    Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding", "SunJCE");
    SecretKeySpec key = new SecretKeySpec(encryptionKey.getBytes("UTF-8"), "AES");
    cipher.init(Cipher.ENCRYPT_MODE, key,new IvParameterSpec(IV.getBytes("UTF-8")));
    return cipher.doFinal(plainText.getBytes("UTF-8"));
  }

  public static String decrypt(byte[] cipherText, String encryptionKey) throws Exception{
    Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding", "SunJCE");
    SecretKeySpec key = new SecretKeySpec(encryptionKey.getBytes("UTF-8"), "AES");
    cipher.init(Cipher.DECRYPT_MODE, key,new IvParameterSpec(IV.getBytes("UTF-8")));
    return new String(cipher.doFinal(cipherText),"UTF-8");
  }
}

And the C file:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/*
 * MCrypt API available online:
 * http://linux.die.net/man/3/mcrypt
 */
#include <mcrypt.h>

#include <math.h>
#include <stdint.h>
#include <stdlib.h>

int encrypt(
    void* buffer,
    int buffer_len, /* Because the plaintext could include null bytes*/
    char* IV, 
    char* key,
    int key_len 
){
  MCRYPT td = mcrypt_module_open("rijndael-128", NULL, "cbc", NULL);
  int blocksize = mcrypt_enc_get_block_size(td);
  if( buffer_len % blocksize != 0 ){return 1;}

  mcrypt_generic_init(td, key, key_len, IV);
  mcrypt_generic(td, buffer, buffer_len);
  mcrypt_generic_deinit (td);
  mcrypt_module_close(td);

  return 0;
}


int decrypt(
    void* buffer,
    int buffer_len,
    char* IV, 
    char* key,
    int key_len 
){
  MCRYPT td = mcrypt_module_open("rijndael-128", NULL, "cbc", NULL);
  int blocksize = mcrypt_enc_get_block_size(td);
  if( buffer_len % blocksize != 0 ){return 1;}

  mcrypt_generic_init(td, key, key_len, IV);
  mdecrypt_generic(td, buffer, buffer_len);
  mcrypt_generic_deinit (td);
  mcrypt_module_close(td);

  return 0;
}

void display(char* ciphertext, int len){
  int v;
  for (v=0; v<len; v++){
    printf("%d ", ciphertext[v]);
  }
  printf("\n");
}

int main()
{
  MCRYPT td, td2;
  char * plaintext = "test text 123";
  char* IV = "AAAAAAAAAAAAAAAA";
  char *key = "0123456789abcdef";
  int keysize = 16; /* 128 bits */
  char* buffer;
  int buffer_len = 16;

  buffer = calloc(1, buffer_len);
  /* 
   * Note that calloc() will null-initialise the memory. (Null padding)
   */

  strncpy(buffer, plaintext, buffer_len);

  printf("==C==\n");
  printf("plain:   %s\n", plaintext);
  encrypt(buffer, buffer_len, IV, key, keysize); 
  printf("cipher:  "); display(buffer , buffer_len);
  decrypt(buffer, buffer_len, IV, key, keysize);
  printf("decrypt: %s\n", buffer);

  return 0;
}

Tested on Linux with the latest Libmcrypt and Java 1.7. Watch out, since I wrote the C in a rush, and it is full of memory leaks and overflow problems. (Exercise left to the reader to clean it up, as they say...)

brice
  • 24,329
  • 7
  • 79
  • 95
  • I tried NoPadding and I got the error javax.crypto.IllegalBlockSizeException: Input length not multiple of 16 bytes, while the c code works fine, so I think mcrypt does some padding instead and assumed it is PKCS5Padding! rijndael 128 256 I already answere before it is wrong in the code but I had already checked that before posting and yet the result remains different.. – dendini Apr 20 '12 at 14:55
  • Updated with another possible reason. Why do you convert the key from hex to bytes in Java but not in C? – brice Apr 20 '12 at 15:05
  • @brice:`AES` has keysize of 256 as well.Why do you say that with 256 it is not `AES`? – Cratylus Apr 20 '12 at 21:02
  • Blocksize is fixed at 128 bits. Not keysize (128, 192, 256). It's on [the wikipedia page](http://en.wikipedia.org/wiki/Advanced_Encryption_Standard). – brice Apr 20 '12 at 23:54
  • @dendini Did you have any luck with the above? – brice Apr 23 '12 at 10:42
  • @brice Thank you loads for the code above, however I get the same results as you with java, while c file returns: plain: test text 123 cipher: 16 -124 41 -83 -16 -123 61 -64 -15 -74 87 28 63 30 64 78 decrypt: test text 123 This is architecture independent as I get the same with i686 and x86_64 kernels! libmcrypt version 2.5.8 – dendini Apr 26 '12 at 19:42
  • Notice I compile with these flags: gcc AES.c /usr/lib/libmcrypt.a -lssl -lcrypto -lpthread – dendini Apr 26 '12 at 19:43
  • If you read the code, you'll see that they're using different keys :-) forgot to edit them. Try with the same key to get the right result. Pro tip: never run code from the internetz without a good read first! (editing to resolve...) – brice Apr 26 '12 at 20:29
2

Further to @brice's flags, I don't see where you initialize the IV in your Java code. You create an `IvParameterSpec', but you pass in an all-zero byte array.

Your C code generates a random IV so it should produce a different ciphertext each time it is run.

Try using a fixed IV for both implementations and see if that gives you consistent results. Of course you'll need to generate a random IV again when you want to do real encryption, but using a fixed IV may help you with debugging.

I'd also make sure that both implementations are using the same padding (by explicitly setting the padding rather than letting mcrypt choose), and instead of writing the raw ciphertext to the console I strongly recommend writing the hex values, or just write each byte as a number - it will be significantly easier to debug when you don't have to worry about unprintable characters. This is only for debugging, so it doesn't matter if it's not efficient or pleasant to look at.

Cameron Skinner
  • 51,692
  • 2
  • 65
  • 86
  • Good catch. If the IVs aren't the same, this will never work. – brice Apr 20 '12 at 15:35
  • Actually the IV just needs to be a random IV, it is not required for it to be equal when decrypting and encrypting, otherwise one should need to know the ciphered text, the secret key and the IV vector to decrypt, that's not obviously the case. I tried putting a different IV in encrypting and decrypting in Java and it has no effect, the ciphered text remains the same.. – dendini Apr 20 '12 at 16:02
  • 1
    @Dendini: That is *exactly* the case. You need to decrypt with the same IV that was used to encrypt. If it didn't do that in your Java implementation then you're doing something wrong. Note that your C implementation attempts to generate a new IV for decryption, but it is not used because you've already initialised `td2` with the encryption IV. – Cameron Skinner Apr 20 '12 at 16:05
  • @dendini think you need another look at [how block ciphers work](http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation). I would highly recommend chapter 9 of [Applied Cryptography](http://www.schneier.com/book-applied.html) – brice Apr 20 '12 at 16:11
  • The IV vector doesn't need to be the same, have you tried the code I put above changing the IV after encryption for decrypting the ciphered text? you would see the ciphered text is decrypted correctly anyway so IV is not the point here.. (I will update the IV code above anyway just to leave any perplexity away) – dendini Apr 20 '12 at 16:22
  • @Dendini: No, the IV vector **must** be the same. Take brice's advice and read up on how IVs work. I have copied your code and tried it with different IVs and confirmed that it does not decrypt correctly. – Cameron Skinner Apr 20 '12 at 16:25
  • That is to say, I copied your Java code. The C code you have for changing the IV is incorrect, because you have already initialized `td2` with the first IV. Changing the array after initialization has no effect because mcrypt obviously takes a copy of the IV during initialization. – Cameron Skinner Apr 20 '12 at 16:27
  • You were right about the IV, now I've fixed the IV initialization (I've set it to 00000 in both c and Java), I've fixed the encryption key length which is now 16 chars = 16Bytes = 128bits in both c and java. I only need to figure out the padding thing, apparently Java doesn't like the length of the key without padding returning a javax.crypto.IllegalBlockSizeException: Input length not multiple of 16 bytes – dendini Apr 20 '12 at 19:55