AES rijndael encrypt between c and java

14,064

Solution 1

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...)

Solution 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.

Share:
14,064
dendini
Author by

dendini

Updated on June 04, 2022

Comments

  • dendini
    dendini almost 2 years

    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;
    }
    
  • dendini
    dendini about 12 years
    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..
  • brice
    brice about 12 years
    Updated with another possible reason. Why do you convert the key from hex to bytes in Java but not in C?
  • brice
    brice about 12 years
    Good catch. If the IVs aren't the same, this will never work.
  • dendini
    dendini about 12 years
    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..
  • Cameron Skinner
    Cameron Skinner about 12 years
    @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.
  • brice
    brice about 12 years
    @dendini think you need another look at how block ciphers work. I would highly recommend chapter 9 of Applied Cryptography
  • dendini
    dendini about 12 years
    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)
  • Cameron Skinner
    Cameron Skinner about 12 years
    @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
    Cameron Skinner about 12 years
    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.
  • dendini
    dendini about 12 years
    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
  • Cratylus
    Cratylus about 12 years
    @brice:AES has keysize of 256 as well.Why do you say that with 256 it is not AES?
  • brice
    brice about 12 years
    Blocksize is fixed at 128 bits. Not keysize (128, 192, 256). It's on the wikipedia page.
  • brice
    brice about 12 years
    @dendini Did you have any luck with the above?
  • dendini
    dendini about 12 years
    @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
    dendini about 12 years
    Notice I compile with these flags: gcc AES.c /usr/lib/libmcrypt.a -lssl -lcrypto -lpthread
  • brice
    brice about 12 years
    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...)