tags:

views:

629

answers:

2

Hi all,

I'm trying to AES encode data in java, send it over the network and decode it in ruby.

Works fine with basic strings, but once the string's length is 16 bytes or more, I have garbage at the encode of the decoded ruby string. I guess it has to do with padding (not sure though since it affects even strings with the exact size of 16)

I tried using PKCS or just adding white space at the end of my string to match the exact length with no luck

Also can anyone explain why I have to do a "aes-256-cbc" in ruby knowing that my java code uses aes 128? trying aes-128-cbc in ruby does not work for any string

Any help is greatly appreciated

Here's my basic code

Java

byte[] raw = key.getBytes();
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(str.getBytes());
return new BASE64Encoder().encode(encrypted);

Ruby

def aes_decrypt(key, encrypted)
decipher =OpenSSL::Cipher::Cipher.new("aes-256-cbc")
decipher.decrypt
decipher.padding = 0
decipher.key = key
d = decipher.update(encrypted)
d << decipher.final
logger.debug "email  #{d.to_s}"
return d.to_s
end
+1  A: 

I agree with your suspicion about padding. PKCS#5 padding will "round up" your plaintext to the next whole block boundary; if you give it a whole number of blocks, it will add one whole block of padding.

If I were you I'd be looking to see exactly what you're getting when you ask JCE for AES - I suspect you're getting AES/CBC/PKCS5Padding, but it might make more sense to explicitly request what you want. Similarly, you need to look up exactly what you're getting at the Ruby end - might the "garbage" at the end of the decrypt be the padding bytes? Go read the PKCS#5 spec and compare it with what you're getting.

I'm slightly worried by you saying you're using AES-128 on the java side but (apparently) AES-256 on the Ruby/OpenSSL side. That sort of mismatch is pretty much guaranteed to not work - yet you say that the crypto is working, at least for short messages. It's possible that you're actually getting AES-256 from java (again, if you ask it for "AES", what do you actually get?) or there's something odd going on on the ruby side which is detecting that you're using a 128 bit key and doing what it considers to be the Right Thing.

Aside: Have you considered whether your ciphertexts need integrity protection? Confidentiality is all very well, but a successful decryption is no guarantee that the ciphertext hasn't been tampered with in transit.

crazyscot
+2  A: 

There is padding and there is chaining. You should first get the chaining right.

AES encrypts blocks of 16 bytes, no more and no less. When you want to encrypt a message which is potentially longer than 16 bytes, then you have to decide how data is split into blocks and reassembled afterwards. The basic splitting algorithm is called ECB: this is "just a split" into blocks encrypted individually. ECB is known to be weak with real-world data because it leaks information about which plaintext blocks are equal to each other (they will be encrypted identically) and such redundancy often happens in "normal" data.

Thus it is needed to "randomize" the data blocks in some way, so that data redundancy is hidden. The CBC mode performs that through "chaining": the processing of a block depends on the result of the encryption of the previous block. Namely, the to-encrypt plaintext block is combined (with bitwise XOR) with the output of the previous block encryption.

The important point here is that the first block to encrypt (the first 16 bytes of data) has no "previous block" so there is nothing to XOR with. This is solved by choosing a "random IV", i.e. a sequence of 16 random bytes, which will be used as "block -1" for the XORing step. The IV must be known to the decrypting party (otherwise it will not know what to XOR the decrypted block with, and the first 16 bytes of data will not be intelligible). The bright side is that the IV needs not be secret; it MUST be selected uniformly (it cannot be a counter incremented for each message), but it can be transmitted "in the clear", usually along the encrypted message itself.

So you have to worry a bit about the IV, and I see nothing about it in either the Java or Ruby code. Usually, Java defaults to ECB (hence no IV at all). If Ruby defaults to an "all-zero IV" in CBC (which is, conceptually, insecure), then it is normal that you can decrypt the first block (write it down, it "just works"), but it is equally normal that it does not work for subsequent blocks.

So I suggest that you explicitly use CBC (in Java, use "AES/CBC/PKCS5Padding" as algorithm name, not "AES") and manage the IV (which must then be transmitted; you could take the convention to concatenate the IV right before the encrypted message).

A few other notes:

  • Padding is about adding some data to the plaintext so that you have an appropriate input length. CBC requires that the input length has a length multiple of the block size (16 bytes). The PKCS#5 padding is a popular method, by which you add at least 1 byte, at most 16 bytes, such that all of them have value n where n is the number of added bytes. The receiver can then know (unambiguously) how many bytes were added, and remove them. Java can add the padding for you, and I suppose Ruby can also process the padding automatically if nicely asked.

  • In the Java code, you use key.getBytes(). I suppose that key is a String. Know that getBytes() encodes the string according to the platform default character set, which is not always the same worldwide. You will save some worry by specifying an explicit charset. Also, since you want to use a 128-bit key but you get something on the Ruby side only with "AES-256", then I assume that you are actually using a 256-bit key on the Java side. My guess is that your key string is the hexadecimal representation of your key, as 32 characters. key.getBytes() does not interpret hexadecimal digits; it encodes the characters themselves, yielding a 32-byte array -- and 32 bytes, that's 256 bits, not 128.

Thomas Pornin