问题
I'm working on project when some arbitrary data are encrypted using Python simple-crypt (source here) and same encrypted data are then used in java application.
I would like to understand conceptual difference between JSSE and Pycrypto.
This is python part doing encryption (source):
counter = Counter.new(HALF_BLOCK, prefix=salt[:HALF_BLOCK//8])
cipher = AES.new(cipher_key, AES.MODE_CTR, counter=counter)
This is my attempt for java re-implementation of same operation:
SecretKeySpec key = new SecretKeySpec(cipher_key, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(salt, 0, HALF_BLOCK / 8);
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC");
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
Problem here is that initialization of java Cipher throw exception:
java.security.InvalidAlgorithmParameterException: IV must be 16 bytes long.
at org.bouncycastle.jce.provider.JCEBlockCipher.engineInit(Unknown Source)
at javax.crypto.Cipher.init(Cipher.java:1394)
at javax.crypto.Cipher.init(Cipher.java:1327)
Value of HALF_BLOCK is 64.
So question is, how does python's AES implementation works with HALF_BLOCK/8 key size and java's not?
Thanks!
回答1:
The nonce (the "left side" of the IV; the "right" being the sequential counter) should be transported alongside the cipher text as the IV. There is no need to keep the nonce secret. It just must not be re-used for another message encrypted by the same key.
It appears the Python code is generating a new Counter
which is 64
bits long and sets the prefix
(I'm assuming the nonce
value) to be the first 8
bytes of the salt
variable. It likely (huge assumption here because I don't have access to the Python code) starts the actual counter value at 0x00
* 8, so your initial IV would be:
salt = '#Eg����' # => UTF-8 encoding of 0x0123456789ABCDEF (not familiar enough with Python for the actual expression)
# Really may be misunderstanding, but as the AES IV must be 16 bytes, I imagine the terminology here is prefix = 8 bytes, sequence = 8 bytes
counter = Counter.new(HALF_BLOCK, prefix=salt[:HALF_BLOCK]) # => '0x01234567 89ABCDEF 00000000 00000000'
# Perform one encryption
counter # => '0x01234567 89ABCDEF 00000000 00000001'
# etc.
To perform the same operation in Java, it should be as simple as initializing your IvParameterSpec
to the same as above (i.e. right-pad the first 8 bytes of the salt with 0
to 16 bytes).
// Intentionally verbose for demonstration; this can obviously be compacted
byte[] salt = org.bouncycastle.util.encoders.Hex.decode("0123456789ABCDEF");
byte[] nonceAndCounter = new byte[16];
System.arraycopy(salt, 0, nonceAndCounter, 0, ((int) (HALF_BLOCK / 8)));
IvParameterSpec iv = new IvParameterSpec(nonceAndCounter);
Here's a full test case which asserts that the encrypt and decrypt are internally compatible; you could also run this with data from the Python side to verify.
@Test
public void testPythonCompatibility() {
// Arrange
byte[] cipher_key = org.bouncycastle.util.encoders.Hex.decode("0123456789ABCDEFFEDCBA9876543210");
final int HALF_BLOCK = 64;
byte[] salt = org.bouncycastle.util.encoders.Hex.decode("0123456789ABCDEF");
byte[] nonceAndCounter = new byte[16];
System.arraycopy(salt, 0, nonceAndCounter, 0, ((int) (HALF_BLOCK / 8)));
IvParameterSpec iv = new IvParameterSpec(nonceAndCounter);
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC");
SecretKeySpec key = new SecretKeySpec(cipher_key, "AES");
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
final String plaintext = "This is a plaintext message.";
// Act
byte[] cipherBytes = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
// Assert
cipher.init(Cipher.DECRYPT_MODE, key, iv);
byte[] recoveredBytes = cipher.doFinal(cipherBytes);
String recovered = new String(recoveredBytes, StandardCharsets.UTF_8);
assert recovered.equals(plaintext);
}
来源:https://stackoverflow.com/questions/40615753/need-advice-about-aes-ctr-cipher-python-vs-java