After encrypting a buffer of arbitrary length ruby-aes adds a character to the output representing the number of padding
bytes which were added to the input in order to make its length a multiple of 16:
- input "a" becomes "a\xf\xf\xf\xf\xf\xf\xf\xf\xf\xf\xf\xf\xf\xf\xf", "\xf" is appended
to the output
- input "ab" becomes "ab\xe\xe\xe\xe\xe\xe\xe\xe\xe\xe\xe\xe\xe\xe", "\xe" is appended
to the output
- input "abcdef" becomes "abcdef\xa\xa\xa\xa\xa\xa\xa\xa\xa\xa", "\xa" is appended to
the output
- input "abcdefghijklmnop" is kept (length is multiple of 16), "\x0" is appended to the output
The padding scheme looks like the one described by RFC 1423, except the later pads the input with 16 * "\x10"
when the input length is already a multiple of 16. This way, when decrypting the resulting buffer, it's possible to
discover unambiguously the number of padding bytes, dispensing with the extra character ruby-aes adds to the output.
Additionally, by implementing RFC 1423, ruby-aes would comply with other AES implementations and ease the interchange
of encrypted data. For example, the following Java program:
import java.security.Security;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class AesDemo
{
private static final byte[] initVector =
new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7 };
private static final byte[] key =
new byte[] { 8, 9, 10, 11, 12, 13, 14, 15, 8, 9, 10, 11, 12, 13, 14, 15 };
public static void main(String[] args) throws Exception
{
System.out.print("iv:\t" + hexEncode(initVector) + "\n");
System.out.print("key:\t" + hexEncode(key) + "\n");
System.out.print("\n");
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(initVector);
String inputStr = "abcdefghijklmnopqrstuvwxyz012345";
StringBuffer inputBuf = new StringBuffer();
for (int i = 0; i < inputStr.length(); i++)
{
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
byte[] output = cipher.doFinal(inputBuf.toString().getBytes("UTF-8"));
System.out.print(String.valueOf(i) + ":\t" + inputBuf.toString() + "\n\t=> ");
System.out.print(hexEncode(output));
System.out.print("\n");
inputBuf.append(inputStr.charAt(i));
}
}
private static String hexEncode(byte[] bytes)
{
StringBuffer strBuf = new StringBuffer();
for (int i = 0; i < bytes.length; i++)
{
strBuf.append(Character.forDigit((bytes[i] >> 4) & 0x0F, 16));
strBuf.append(Character.forDigit((bytes[i]) & 0x0F, 16));
}
return strBuf.toString();
}
}
produces this output:
iv: 00010203040506070001020304050607
key: 08090a0b0c0d0e0f08090a0b0c0d0e0f
0:
=> ec8e5fd8181ff9c3dad3e22ca848ff65
1: a
=> 4be77f92b8b3390835c40558548d7ac2
2: ab
=> 0764eab7ee267f61dcff2ed22e2465f6
3: abc
=> 069f6ba202d6f0d262942a9b7d5f716b
4: abcd
=> 7cc107cbd72c2168f8e2636066044b47
5: abcde
=> f7217548c5bff82b727095937f49349d
6: abcdef
=> 018762c5c8f1f7c011f770b546ba2726
7: abcdefg
=> 1eee05e699e6fc5d964c213c64ffa4ad
8: abcdefgh
=> 4e741595c018d9b8936b2ce226e65744
9: abcdefghi
=> 860db26ea500fc7a62facb98d9d60a07
10: abcdefghij
=> 468bbd00eab491bfffbd62273a0d973b
11: abcdefghijk
=> 1ad26692c857a1da116f8e253c66f38d
12: abcdefghijkl
=> 1354e4841c3332c56418a9a3a7722be6
13: abcdefghijklm
=> a3bfa681abadc995df562a42a29a3791
14: abcdefghijklmn
=> a8cc5e4c35e015ac9833410a3cce09a9
15: abcdefghijklmno
=> abbb72f16b55188bcab47fc217e16193
16: abcdefghijklmnop
=> 0f67968f40cb0bacd2d0fb50b77486d8d8535ee9c5f1043d7c37f39a8de5c043
17: abcdefghijklmnopq
=> 0f67968f40cb0bacd2d0fb50b77486d88095973e500f546135c3fad74ccef6ad
18: abcdefghijklmnopqr
=> 0f67968f40cb0bacd2d0fb50b77486d8db860c4719747e06e74b1739e335f5e5
19: abcdefghijklmnopqrs
=> 0f67968f40cb0bacd2d0fb50b77486d8086637abfdc5e9bc339c7045695f2629
20: abcdefghijklmnopqrst
=> 0f67968f40cb0bacd2d0fb50b77486d80b05ccf4ee64f9b6a2336ad021b17e87
21: abcdefghijklmnopqrstu
=> 0f67968f40cb0bacd2d0fb50b77486d8980076187180ffaee19da736419110e2
22: abcdefghijklmnopqrstuv
=> 0f67968f40cb0bacd2d0fb50b77486d87dca3c3d5f7613d8cd9d8ac91b212cc1
23: abcdefghijklmnopqrstuvw
=> 0f67968f40cb0bacd2d0fb50b77486d8796b2ca8a13ebf63e8ebaeb5e05c13e7
24: abcdefghijklmnopqrstuvwx
=> 0f67968f40cb0bacd2d0fb50b77486d8e90a280f9f67112eaf1a67f6de1f3380
25: abcdefghijklmnopqrstuvwxy
=> 0f67968f40cb0bacd2d0fb50b77486d8c47bb49ddc1d750d2541f8f638632f8a
26: abcdefghijklmnopqrstuvwxyz
=> 0f67968f40cb0bacd2d0fb50b77486d8efdd7cb20ddd97814e76291863bcd055
27: abcdefghijklmnopqrstuvwxyz0
=> 0f67968f40cb0bacd2d0fb50b77486d86ce6cad7a120316acc668cfca1571373
28: abcdefghijklmnopqrstuvwxyz01
=> 0f67968f40cb0bacd2d0fb50b77486d831d0b1ad00e213071677451163c83163
29: abcdefghijklmnopqrstuvwxyz012
=> 0f67968f40cb0bacd2d0fb50b77486d8f4035fcf46516641ffccd6091fba3418
30: abcdefghijklmnopqrstuvwxyz0123
=> 0f67968f40cb0bacd2d0fb50b77486d8429c441546c35dc1ef8ea90b485a4327
31: abcdefghijklmnopqrstuvwxyz01234
=> 0f67968f40cb0bacd2d0fb50b77486d8c22ab8a079b2a6be540b0cdc90db635a
while the Ruby equivalent using the current ruby-aes implementation:
require 'ruby-aes'
IV = '00010203040506070001020304050607'
KEY = '08090a0b0c0d0e0f08090a0b0c0d0e0f'
puts "iv:\t%s" % IV
puts "key:\t%s" % KEY
puts ""
input_str = 'abcdefghijklmnopqrstuvwxyz012345'
(0..31).each do |i|
s = input_str[0, i]
puts "%d:\t%s" % [i, s]
output_str = Aes.encrypt_buffer(128, 'CBC', KEY, IV, s)
puts "\t=> %s" % output_str.unpack('H*').first
if s != Aes.decrypt_buffer(128, 'CBC', KEY, IV, output_str)
puts "Decryption of output does not match input string"
end
end
produces this:
iv: 00010203040506070001020304050607
key: 08090a0b0c0d0e0f08090a0b0c0d0e0f
0:
=> 00
1: a
=> 4be77f92b8b3390835c40558548d7ac20f
2: ab
=> 0764eab7ee267f61dcff2ed22e2465f60e
3: abc
=> 069f6ba202d6f0d262942a9b7d5f716b0d
4: abcd
=> 7cc107cbd72c2168f8e2636066044b470c
5: abcde
=> f7217548c5bff82b727095937f49349d0b
6: abcdef
=> 018762c5c8f1f7c011f770b546ba27260a
7: abcdefg
=> 1eee05e699e6fc5d964c213c64ffa4ad09
8: abcdefgh
=> 4e741595c018d9b8936b2ce226e6574408
9: abcdefghi
=> 860db26ea500fc7a62facb98d9d60a0707
10: abcdefghij
=> 468bbd00eab491bfffbd62273a0d973b06
11: abcdefghijk
=> 1ad26692c857a1da116f8e253c66f38d05
12: abcdefghijkl
=> 1354e4841c3332c56418a9a3a7722be604
13: abcdefghijklm
=> a3bfa681abadc995df562a42a29a379103
14: abcdefghijklmn
=> a8cc5e4c35e015ac9833410a3cce09a902
15: abcdefghijklmno
=> abbb72f16b55188bcab47fc217e1619301
16: abcdefghijklmnop
=> 0f67968f40cb0bacd2d0fb50b77486d800
17: abcdefghijklmnopq
=> 0f67968f40cb0bacd2d0fb50b77486d88095973e500f546135c3fad74ccef6ad0f
18: abcdefghijklmnopqr
=> 0f67968f40cb0bacd2d0fb50b77486d8db860c4719747e06e74b1739e335f5e50e
19: abcdefghijklmnopqrs
=> 0f67968f40cb0bacd2d0fb50b77486d8086637abfdc5e9bc339c7045695f26290d
20: abcdefghijklmnopqrst
=> 0f67968f40cb0bacd2d0fb50b77486d80b05ccf4ee64f9b6a2336ad021b17e870c
21: abcdefghijklmnopqrstu
=> 0f67968f40cb0bacd2d0fb50b77486d8980076187180ffaee19da736419110e20b
22: abcdefghijklmnopqrstuv
=> 0f67968f40cb0bacd2d0fb50b77486d87dca3c3d5f7613d8cd9d8ac91b212cc10a
23: abcdefghijklmnopqrstuvw
=> 0f67968f40cb0bacd2d0fb50b77486d8796b2ca8a13ebf63e8ebaeb5e05c13e709
24: abcdefghijklmnopqrstuvwx
=> 0f67968f40cb0bacd2d0fb50b77486d8e90a280f9f67112eaf1a67f6de1f338008
25: abcdefghijklmnopqrstuvwxy
=> 0f67968f40cb0bacd2d0fb50b77486d8c47bb49ddc1d750d2541f8f638632f8a07
26: abcdefghijklmnopqrstuvwxyz
=> 0f67968f40cb0bacd2d0fb50b77486d8efdd7cb20ddd97814e76291863bcd05506
27: abcdefghijklmnopqrstuvwxyz0
=> 0f67968f40cb0bacd2d0fb50b77486d86ce6cad7a120316acc668cfca157137305
28: abcdefghijklmnopqrstuvwxyz01
=> 0f67968f40cb0bacd2d0fb50b77486d831d0b1ad00e213071677451163c8316304
29: abcdefghijklmnopqrstuvwxyz012
=> 0f67968f40cb0bacd2d0fb50b77486d8f4035fcf46516641ffccd6091fba341803
30: abcdefghijklmnopqrstuvwxyz0123
=> 0f67968f40cb0bacd2d0fb50b77486d8429c441546c35dc1ef8ea90b485a432702
31: abcdefghijklmnopqrstuvwxyz01234
=> 0f67968f40cb0bacd2d0fb50b77486d8c22ab8a079b2a6be540b0cdc90db635a01
Note how the output produced by the Ruby version is similar to the Java one, except for the lines 0 and 16.
The Java PKCS5Padding scheme is based on RFC 1423 (see
<http://java.sun.com/j2se/1.4.2/docs/guide/security/jce/JCERefGuide.html#AppA>,
<http://www.rsa.com/rsalabs/node.asp?id=2127> and <ftp://ftp.rsasecurity.com/pub/pkcs/pkcs_docs_pdf.zip>
(PKCS/PKCS-5.pdf)).
Since I don't know of any other AES implementation which shares ruby-aes' behavior I think tweaking it to mimic Java's
scheme would be a welcome change by the reasons explained before.
Attached is a patch which makes ruby-aes follow my proposal.
|