Skip to content
Advertisement

Invalid AES key length: 20 bytes (Java 11)

I am trying to generate a key using Java. To be honest I am not that experienced with keys, password, ciphers and encryption.

And from whatever I have searched from this site, I see it as a very common problem. I did some reading and came up with this code that I wrote:

    Security.setProperty("crypto.policy", "unlimited");

    String valueToEncode = "some_random_text";

    SecureRandom secureRandom = new SecureRandom();
    byte[] salt = new byte[256];

    secureRandom.nextBytes(salt);

    KeySpec keySpec = new PBEKeySpec("some_random_password".toCharArray(), salt, 65536, 256); // AES-256
    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBEWITHHMACSHA512ANDAES_256");
    byte[] key = secretKeyFactory.generateSecret(keySpec).getEncoded();
    SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");

    byte[] ivBytes = new byte[16];

    secureRandom.nextBytes(ivBytes);

    IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes);
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");

    cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);

    byte[] encValue = cipher.doFinal(valueToEncode.getBytes(StandardCharsets.UTF_8));
    byte[] finalCiphertext = new byte[encValue.length + 2 * 16];

    System.arraycopy(ivBytes, 0, finalCiphertext, 0, 16);
    System.arraycopy(salt, 0, finalCiphertext, 16, 16);
    System.arraycopy(encValue, 0, finalCiphertext, 32, encValue.length);

    System.out.println(finalCiphertext.toString());

This is modified from an answer that I saw on another post. But I still get the “invalid length” error.

The error that I get is:

Exception in thread "main" java.security.InvalidKeyException: Invalid AES key length: 20 bytes
    at com.sun.crypto.provider.AESCrypt.init(AESCrypt.java:87)
    at com.sun.crypto.provider.CipherBlockChaining.init(CipherBlockChaining.java:93)
    at com.sun.crypto.provider.CipherCore.init(CipherCore.java:591)
    at com.sun.crypto.provider.AESCipher.engineInit(AESCipher.java:346)
    at javax.crypto.Cipher.implInit(Cipher.java:805)
    at javax.crypto.Cipher.chooseProvider(Cipher.java:863)
    at javax.crypto.Cipher.init(Cipher.java:1395)
    at javax.crypto.Cipher.init(Cipher.java:1326)
    at com.att.logicalprovisioning.simulators.Trial.main(Trial.java:47)

Trial.java:47 being the line: cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);

Is there a one-size fits all solution to this? Or is it just my lack of understanding?

Any help would be appreciated.

Advertisement

Answer

Your key is 20 bytes long because secretKeyFactory.generateSecret(keySpec).getEncoded() returns the password some_random_password.

An easy way to fix the code is to use the key derivation PBKDF2WithHmacSHA512 instead of PBEWITHHMACSHA512ANDAES_256. This generates a key of the specified length based on the password and salt:

...
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
...

However, PBEWITHHMACSHA512ANDAES_256 can also be applied. This algorithm specifies a key derivation with PBKDF2WithHmacSHA512 and subsequent AES encryption. The implementation is functionally identical to yours, but requires a few more changes to the code:

...
IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes);
PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, 65536, ivParameterSpec);

PBEKeySpec keySpec = new PBEKeySpec("some_random_password".toCharArray());
SecretKeyFactory kf = SecretKeyFactory.getInstance("PBEWITHHMACSHA512ANDAES_256");
SecretKey secretKey = kf.generateSecret(keySpec);       

Cipher cipher = Cipher.getInstance("PBEWITHHMACSHA512ANDAES_256");
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
byte[] encValue = cipher.doFinal(valueToEncode.getBytes(StandardCharsets.UTF_8));
...

Two other issues are:

  • You are using a 256 bytes salt, but only storing 16 bytes when concatenating. To be consistent with the concatenation, apply a 16 bytes salt: byte[] salt = new byte[16].
  • The output finalCiphertext.toString() returns only class and hex hashcode of the object, s. here. For a meaningful output use a Base64 or hex encoding of the byte[] instead, e.g. Base64.getEncoder().encodeToString(finalCiphertext).
User contributions licensed under: CC BY-SA
1 People found this is helpful
Advertisement