10th April 2008

Java Cryptography Compatibility

posted in Java, Security |

One relatively standard way to identify someone is to generate a small token which you can give to them, and which they can later give back to you. This is a classic “user cookie” scenario. The ID could be any data which is unique, but it should also be hard fake - you shouldn’t be able to guess one from scratch, nor to change an existing ID slightly to get someone else’s ID. One standard way to do this is to encrypt a sequential user ID. While almost all languages offer some form of encryption, it can be tricky to get encryption working between platforms. To find ou

A few years ago, the National Institute of Standards and Technology introduced a new encryption standard, called AES (Advanced Encryption Standard). Designed to be faster in software then the previous national standard for encryption (Triple-DES), AES is a very good way pass encrypted data between platforms - it’s reasonably fast, and you can get libraries that perform AES encryption for ust about every language or operating system. But there are some details which can make it harder than it first seems.

The project I was working on included a tool which would encrypt a bunch of IDs in bulk. The encrypted IDs would be put into a link in an email, so we could tell which email generated the visit. The IDs and other data were pulled from MS Access database, put into a CSV file, and then sent to an email service. The application which had to decrypt the encrypted IDs was a Java application.

Java (since 1.4) provides an implementation of AES (in the Java Cryptography Extension). OpenSSL provides libcrypto, which can be compiled for many platforms and provides an AES implementation. Therefor it should be simple to exchange encrypted data.

Not quite. The default Java implementation of AES operates in “electronic codebook” mode, or ECB. This is a relatively weak way to use AES, because it applies the encryption to each 16 byte block of data independently. This can reveal significant pattens in the encrypted data, and precludes the use of salt. Most of the sample code you’re likely to find for OpenSSL use more secure modes, like “cipher-block chaining” (CBC) mode, in which the results of encrypting of one block of data are fed into process for the next. This removes many of the patterns which ECB mode leaves in the encrypted data. CBC mode is more secure than ECB mode, so the first step in compatibility is making sure

But it’s still not quite enough. Because AES operates on 16 byte blocks of data, any message not a multiple o 16 bytes long needs to be padded. There are several padding techniques, but in this case Java and SSL agree on the same default padding technique: PKCS #5 padding (same as PKCS #7 padding).

Finally, there is the question of the salt, or initialization vector. Why salt is needed and how to use it are a subject for another post. However, using the same salt for every message still allows some patterns to appear in the encrypted data, so at a minimum one should use random salt, and send the salt (unecnrypted) with the message. That may sound strange, but it actually does make a difference. There are more complex ways to use salt for more security, but that’s for another post.

What you end up with is Java code that looks something like this:

---

SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
IvParameterSpec salt = key.getIv(saltBytes);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, salt);
cipherText = cipher.doFinal(plainText);

---

and C code (using the OpenSLL library) that is something like this:

---

// Basic data structures
EVP_CIPHER_CTX ctx;
EVP_CIPHER_CTX_init(&ctx);
EVP_EncryptInit(&ctx, EVP_aes_128_cbc(), key, salt);

// Non-padded portion
int ciphertext_size = 0;
int err = EVP_EncryptUpdate(&ctx, ciphertext, &output_size, plaintext, plaintext_length);
if (err != 1) {
return -1;
}

// Including terminal padding
int final_size = 0;
err = EVP_EncryptFinal(&ctx, ciphertext + output_size, &final_size);
if (err != 1) {
return -1;
}
EVP_CIPHER_CTX_cleanup (&ctx);

---

(You may be able to skip the EncryptUpdate and just use EncryptFinal - I haven’t tried it).

Leave a Reply