4

Let me explain shortly. I have this encryptor in python:

It uses PyCrypto library.

from Crypto import Random
from Crypto.Cipher import AES
from Crypto.Util import Counter

iv = Random.new().read(8)
encryptor = AES.new(
    CRYPTOGRAPHY_KEY,    // 32 bytes
    AES.MODE_CTR,
    counter=Counter.new(64, prefix=iv),
)

and I want to have a decryptor for it in java.

I wrote this code but it raises java.security.InvalidAlgorithmParameterException: Wrong IV length: must be 16 bytes long.

SecretKeySpec key = new SecretKeySpec(KEY, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);

P.S. I should mention that I'm not an experienced Java developer.


Update. The problem was with initialization vector. Special thanks to @Andy for his time.

Solution:

byte[] nonceAndCounter = new byte[16];
System.arraycopy(iv, 0, nonceAndCounter, 0, 8);
nonceAndCounter[15] = (byte) 1; // PyCrypto's default initial value is 1
IvParameterSpec ivSpec = new IvParameterSpec(nonceAndCounter);
Mehdi Pourfar
  • 488
  • 4
  • 13
  • Can you try changing 64 to 128 also? As I said, IV must be 128 bits. – ram Feb 14 '17 at 20:36
  • I still get this error: `TypeError: CTR counter function returned string not of length 16` – Mehdi Pourfar Feb 14 '17 at 20:41
  • Did you change both 8 to 16 and 64 to 128 together? check the sample [here](https://www.dlitz.net/software/pycrypto/api/current/Crypto.Util.Counter-module.html) in documentation, they also choose 128 rather than 64. – ram Feb 14 '17 at 21:02
  • Yes. I've changed that. In the example 16 is the length of key not iv, and also counter is without iv prefix. – Mehdi Pourfar Feb 14 '17 at 21:14
  • See 128 in the example. It seems to be the length of the counter. – ram Feb 14 '17 at 21:16

2 Answers2

3

If you are trying to use AES-256 (you are using 32 bytes key), then you need to have Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files installed. And this has to be installed wherever this application needs to run. If you plan to distribute this application and thus you don't have control over possible java runtimes, then you have to use other libraries (like Bouncy Castle) through their own API, not through JCE API. (i.e. using Bouncy Castle as a provider like "BC" in JCE would result in the same problem.)

EDIT: First you said you are getting invalid key size exception, now changed the question. For the java.security.InvalidAlgorithmParameterException case, the problem is AES-256 has block size of 16, not 32. 256 represents the key size, not block size. AES (128, 192, 256) always has block size of 128. Thus, the iv must be 16 bytes.

EDIT 2: Not the cause of this exception but another possible problem with your code is, where do you get the iv in Java? Do you generate it random as you do in Python? That would be a big mistake since you have to use the same IV during decryption to make it work. Keep this in mind.

ram
  • 1,099
  • 1
  • 7
  • 22
  • First thanks for your answer. You are saying that I'm using the wrong iv size? – Mehdi Pourfar Feb 14 '17 at 19:36
  • I've a decryptor for it in python and it works perfectly. I just want to have a java version of it. – Mehdi Pourfar Feb 14 '17 at 19:37
  • Yes, it seems so. In Python side, you are starting with 32 bytes. I saw this, but probably Python just takes the first 16 bytes and ignores the rest. See [wiki page of aes](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard), and observe that block size is always 128. Setting the size of iv at java to 16 will solve the problem probably. But if you want to be sure, set the iv size at Python to 16 too, and you will see that it will not complatin and work as expected. – ram Feb 14 '17 at 19:39
  • I'm getting iv from first 8 bytes of my encrypted file. How do you mean you are starting with 32 bytes? 32 bytes is size of my key not iv. I've tried to change my iv size to 16 in python and I've encountered this error while encrypting: `TypeError: CTR counter function returned string not of length 16` – Mehdi Pourfar Feb 14 '17 at 20:10
  • I am not good at python, but I am 100% sure that IV must be of size 16 bytes. You must write 16 bytes to the beginning of the file, read and use it in java appropriately. – ram Feb 14 '17 at 20:19
1

I believe you are encountering an issue where Java expects the IV to be fully-formed (16 bytes) when using AES/CTR/NoPadding while Python accepts an 8 byte prefix and generates its own counter in the other 64 bits (8 bytes).

I have example code for cross-platform compatibility in this answer. The relevant section is:

    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);
Community
  • 1
  • 1
Andy
  • 13,916
  • 1
  • 36
  • 78
  • This solution raises no exception but does not decrypt correctly. – Mehdi Pourfar Feb 16 '17 at 18:14
  • The example code here uses a hard-coded `key` and `IV` value. Are you populating those with your actual `key` and `IV`? – Andy Feb 16 '17 at 18:15
  • Yes, of course. If your I want to trust this solution, the only possible source of error is my key. I've created my key in python like this: `Crypto.Random.new().read(32)` which produce some bytes like this: `b'2Z\x00\x8ew\xcdF\xf5m3#;\x14\xa9\x8c\x04z\xe0\xd8A\x15\xf8\xfd2\x05\xbe\xe3(\xd6\xbe\x90\x9b'`. What is the best way to have it in java? – Mehdi Pourfar Feb 16 '17 at 18:29
  • If you follow the complete code listing in the link, you can see it is internally consistent, and you can use the same key and IV in Python to test cross-language compatibility. What are the results if you run that code? It may just be a situation where the original cipher text isn't valid. – Andy Feb 16 '17 at 18:30
  • It looks like there are some interesting characters in your key. In general, keys are encoded in various languages in either hexadecimal or Base64 encoding, because both of those encoding schemes guarantee that any 8 bit value (0 - 255) can be expressed stably and recovered. A key formatted in Python hex `"\x51\x72\x96\x22\x1b\x5a\x52\x01\x4e\xbc\xdd\x77\x6e\xf0\x38\xb7"` is in Java `Hex.decode("517296221b5a52014ebcdd776ef038b7");` – Andy Feb 16 '17 at 18:42
  • Still with no progress. I've created a simple repository of python code. You can test yourself. Decrypted bytes are not equal to original bytes. https://github.com/mehdipourfar/lightweight_crypto/ – Mehdi Pourfar Feb 16 '17 at 19:53
  • I ran your tool and it decrypted fine. I would recommend you Base64-encode the cipher text before writing it out to a file, but I'm not sure what the issue is. https://gist.github.com/alopresto/cb6e2202ba700557d2d05b0e77bcf901 – Andy Feb 17 '17 at 05:27
  • You definitely don't need the code on lines 36-37 which is padding the input with spaces though. `CTR` mode turns the block cipher into a stream cipher, so there's no padding required, and this non-standard padding will break compatibility with other implementations. – Andy Feb 17 '17 at 05:55
  • Python code works fine. I've said I can't decrypt the files encrypted with this code in java. So you say the only problem is that padding? – Mehdi Pourfar Feb 17 '17 at 11:04
  • I've found the solution. The problem was that default initial value in PyCrypto's Counter was 1. I had to add this line to make my java code works. nonceAndCounter[15] = (byte) 1; – Mehdi Pourfar Feb 19 '17 at 06:13