7

It's fairly standard knowledge that due to the cryptography export controls, Oracle JRE ships with "limited" cryptographic strength enabled as listed in the JCA Documentation. For AES, the default max happens to be 128 bit key length. To enable 192 bit or 256 bit encryption, the JCE Unlimited Strength Jurisdiction Policy files must be installed into the JRE.

I came across a situation by accident recently that lead me to believe there is an issue with this enforcement. I'm not sure I would call it a bug, but it's definitely not well documented (or at least I can't find anything documenting it).

The key length check is done inside cipher.init(), and I believe it uses Cipher.getMaxAllowedKeyLength("AES") to determine if the max key size is 128 or Integer.MAX_VALUE.

Using normal keyed encryption, this check is fine. On a default JRE installation, the code below executes as expected (I'm using Groovy for the test but I've tried this in pure Java as well):

static boolean isUnlimitedStrengthCrypto() {
    Cipher.getMaxAllowedKeyLength("AES") > 128
}

@Test
public void testShouldEncryptAndDecryptWith128BitKey() throws Exception {
    // Arrange
    MessageDigest sha1 = MessageDigest.getInstance("SHA1")
    String key = Hex.encodeHexString(sha1.digest("thisIsABadPassword".getBytes()))[0..<32]
    String iv = Hex.encodeHexString(sha1.digest("thisIsABadIv".getBytes()))[0..<32]

    logger.info("Key: ${key}")
    logger.info("IV : ${iv}")

    SecretKey secretKey = new SecretKeySpec(Hex.decodeHex(key.toCharArray()), "AES")
    IvParameterSpec ivParameterSpec = new IvParameterSpec(Hex.decodeHex(iv.toCharArray()))

    // Act    
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
    cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec)

    String message = "This is a plaintext message."

    byte[] cipherBytes = cipher.doFinal(message.getBytes())

    cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec)

    byte[] recoveredBytes = cipher.doFinal(cipherBytes)

    String recovered = new String(recoveredBytes)
    System.out.println("Recovered message: " + recovered)

    // Assert
    assert recovered == message
}

This generates the output:

[main] INFO  *.crypto.OpenSSLPBEEncryptorTest  - Key: 6d71f677ecb99cf623246fb48a1d8130
[main] INFO  *.crypto.OpenSSLPBEEncryptorTest  - IV : 912ed675905eb4cb0f9f5714c9c9ec39

And this test:

@Test
public void testShouldNotEncryptAndDecryptWith256BitKey() throws Exception {
    // Arrange
    Assume.assumeTrue("This test should only run when unlimited (256 bit) encryption is not available", !isUnlimitedStrengthCrypto())

    MessageDigest sha1 = MessageDigest.getInstance("SHA1")
    String key = Hex.encodeHexString(sha1.digest("thisIsABadPassword".getBytes()))[0..<32] * 2
    String iv = Hex.encodeHexString(sha1.digest("thisIsABadIv".getBytes()))[0..<32]

    logger.info("Key: ${key}")
    logger.info("IV : ${iv}")

    SecretKey secretKey = new SecretKeySpec(Hex.decodeHex(key.toCharArray()), "AES")
    IvParameterSpec ivParameterSpec = new IvParameterSpec(Hex.decodeHex(iv.toCharArray()))

    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")

    // Act
    def msg = shouldFail(InvalidKeyException) {
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec)
    }

    // Assert
    assert msg =~ "Illegal key size"
}

Generates this output:

[main] INFO  *.crypto.OpenSSLPBEEncryptorTest  - Key: 6d71f677ecb99cf623246fb48a1d81306d71f677ecb99cf623246fb48a1d8130
[main] INFO  *.crypto.OpenSSLPBEEncryptorTest  - IV : 912ed675905eb4cb0f9f5714c9c9ec39

And successfully passes by throwing the exception.

The problem arises when password-based encryption is used.

Because the key derivation from the password (and salt, if provided), occurs during cipher.init() but after the key length check, the length check actually applies to the byte[] representation of SecretKey.getEncoded(). This means that if a password <= 16 characters (16 bytes / 128 bits) is used, the check will pass even if the cipher specified uses a 256 bit key. The derived key will be 256 bits even though the jurisdiction policy prohibits this. Conversely, if a password > 16 characters is used, even with a 128 bit cipher, the length check will fail and an InvalidKeyException will be thrown. The following code demonstrates this:

@Test
public void testShouldEncryptAndDecryptWithPBEShortPassword() throws Exception {
    // Arrange
    final String PASSWORD = "password"
    String salt = "saltsalt"

    logger.info("Password: ${PASSWORD}")
    logger.info("Salt    : ${salt}")

    String algorithm;
    algorithm = "PBEWITHMD5AND256BITAES-CBC-OPENSSL"
    PBEKeySpec pbeSpec = new PBEKeySpec(PASSWORD.toCharArray());
    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm, "BC");
    SecretKey secretKey = secretKeyFactory.generateSecret(pbeSpec);
    PBEParameterSpec saltParams = new PBEParameterSpec(salt.getBytes("US-ASCII"), 0);

    // Act
    Cipher cipher = Cipher.getInstance(algorithm, "BC");
    cipher.init(Cipher.ENCRYPT_MODE, secretKey, saltParams);

    String message = "This is a plaintext message."

    byte[] cipherBytes = cipher.doFinal(message.getBytes())

    cipher.init(Cipher.DECRYPT_MODE, secretKey, saltParams)

    byte[] recoveredBytes = cipher.doFinal(cipherBytes)

    String recovered = new String(recoveredBytes)
    System.out.println("Recovered message: " + recovered)

    // Assert
    assert recovered == message
}

@Test
public void testShouldNotEncryptAndDecryptWithPBELongPassword() throws Exception {
    // Arrange
    Assume.assumeTrue("This test should only run when unlimited (256 bit) encryption is not available", !isUnlimitedStrengthCrypto())

    final String PASSWORD = "thisIsABadPassword"
    String salt = "saltsalt"

    logger.info("Password: ${PASSWORD}")
    logger.info("Salt    : ${salt}")

    String algorithm;
    algorithm = "PBEWITHMD5AND256BITAES-CBC-OPENSSL"
    PBEKeySpec pbeSpec = new PBEKeySpec(PASSWORD.toCharArray());
    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm, "BC");
    SecretKey secretKey = secretKeyFactory.generateSecret(pbeSpec);
    PBEParameterSpec saltParams = new PBEParameterSpec(salt.getBytes("US-ASCII"), 0);

    Cipher cipher = Cipher.getInstance(algorithm, "BC");

    // Act
    def msg = shouldFail(InvalidKeyException) {
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, saltParams);
    }

    // Assert
    assert msg =~ "Illegal key size"
}

Both tests "pass" in that on a system with "limited" strength cryptography, 256 bit encryption is still available if the password is short enough. Conversely, this test demonstrates that a long password causes an exception even when using 128 bit encryption:

@Test
public void testShouldNotEncryptAndDecryptWithPBELongPasswordEvenWith128BitKey() throws Exception {
    // Arrange
    Assume.assumeTrue("This test should only run when unlimited (256 bit) encryption is not available", !isUnlimitedStrengthCrypto())

    final String PASSWORD = "thisIsABadPassword"
    String salt = "saltsalt"

    logger.info("Password: ${PASSWORD}")
    logger.info("Salt    : ${salt}")

    String algorithm;
    algorithm = "PBEWITHMD5AND128BITAES-CBC-OPENSSL"
    PBEKeySpec pbeSpec = new PBEKeySpec(PASSWORD.toCharArray());
    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm, "BC");
    SecretKey secretKey = secretKeyFactory.generateSecret(pbeSpec);
    PBEParameterSpec saltParams = new PBEParameterSpec(salt.getBytes("US-ASCII"), 0);

    Cipher cipher = Cipher.getInstance(algorithm, "BC");

    // Act
    def msg = shouldFail(InvalidKeyException) {
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, saltParams);
    }

    // Assert
    assert msg =~ "Illegal key size"
}

I thought it might be a false positive, so I used OpenSSL to encrypt files using 128 and 256 bit encryption with "long" and "short" passwords and tried to decrypt them with Java. The results are from a system with "limited" strength crypto:

$ openssl enc -aes-128-cbc -e -in plain.txt -out salted_raw_128_long.enc -k thisIsABadPassword -p
$ openssl enc -aes-128-cbc -e -in plain.txt -out salted_raw_128_short.enc -k password -p
$ openssl enc -aes-256-cbc -e -in plain.txt -out salted_raw_256_long.enc -k thisIsABadPassword -p
$ openssl enc -aes-256-cbc -e -in plain.txt -out salted_raw_256_short.enc -k password -p


Cipher  | Password length | Should Work | Does Work
--------|-----------------|-------------|-----------
AES-128 |   <= 16 chars   |     YES     |    YES
AES-128 |    > 16 chars   |     YES     |     NO
AES-256 |   <= 16 chars   |      NO     |    YES
AES-256 |    > 16 chars   |      NO     |     NO

I have a few questions:

  1. Can anyone else reproduce this behavior?
  2. Is this intended behavior or a bug?
  3. If intended, is it sufficiently documented somewhere?

Update After further research on a machine without the unlimited strength jurisdiction policies installed, I have determined these maximum password lengths for the following PBE algorithms:

Algorithm        |        Max Password Length
---------------------------------------------
PBEWITHMD5AND128BITAES-CBC-OPENSSL |    16
PBEWITHMD5AND192BITAES-CBC-OPENSSL |    16
PBEWITHMD5AND256BITAES-CBC-OPENSSL |    16
PBEWITHMD5ANDDES                   |    16
PBEWITHMD5ANDRC2                   |    16
PBEWITHSHA1ANDRC2                  |    16
PBEWITHSHA1ANDDES                  |    16
PBEWITHSHAAND128BITAES-CBC-BC      |     7
PBEWITHSHAAND192BITAES-CBC-BC      |     7
PBEWITHSHAAND256BITAES-CBC-BC      |     7
PBEWITHSHAAND40BITRC2-CBC          |     7
PBEWITHSHAAND128BITRC2-CBC         |     7
PBEWITHSHAAND40BITRC4              |     7
PBEWITHSHAAND128BITRC4             |     7
PBEWITHSHA256AND128BITAES-CBC-BC   |     7
PBEWITHSHA256AND192BITAES-CBC-BC   |     7
PBEWITHSHA256AND256BITAES-CBC-BC   |     7
PBEWITHSHAAND2-KEYTRIPLEDES-CBC    |     7
PBEWITHSHAAND3-KEYTRIPLEDES-CBC    |     7
PBEWITHSHAANDTWOFISH-CBC           |     7
  • was OpenSSL installed when you ran the code? – Daniel Ruf Dec 5 '15 at 9:10
  • Yes, both OpenSSL 0.9.8zg and 1.0.2d are installed. – Andy Dec 5 '15 at 20:41
  • I am not sure if Java uses the ciphersuite of OpenSSL if it is installed and in the path (as fallback). – Daniel Ruf Dec 5 '15 at 21:13
  • 1
    @AlainO'Dea I am not using any esoteric libraries -- I'll try a version where the JCE provider for the cipher is the default instead of BouncyCastle, but my understanding is that BC can do its own thing using the custom classes, but its implementation of the JCA is still subject to the export controls. Thanks. – Andy Dec 7 '15 at 13:30
  • 1
    @AlainO'Dea, I am unable to replicate the issue using any other JCE provider because BouncyCastle is the only provider I have found which supports OpenSSL-compatible PBE through the KDF. Here is the source code (using test testShouldNotEncryptAndDecryptWithPBELongPasswordWith128BitKeyAndDefaultJCEProvider ) and the output on my machine. – Andy Dec 13 '15 at 0:41
1

International Law

The JCA framework is not designed to unequivocally enforce international law, however arbitrary differences between law and software should be corrected to facilitate compliance. Mutual authentication secures proper handling of the strength restrictions only if the provider handles it side properly.

If there is a breach of compliance, the disclaimers from Oracle and provider vendors will drop the blame on those integrating these products into systems.

This survey of International Cryptography Regulation and the Global Information Economy is a good start to understand what the CTO, security specialist, and architect's responsibilities are regarding cryptographic limitations in an international market.

U.S. Law

The U.S. Department of Commerce's Bureau of Industry and Security Policy is defined in a set of somewhat complicated documents. The pertinent section for computer cryptography is called Category 5 and is documented here.

JCA Documentation

These are the Oracle documents regarding the JCA framework, however the framework expects cooperation from the providers.

Cryptography Provider Documentation and Brute Force Testing

Each provider may or may not have its own documentation. I was not highly impressed with BountyCastle's documentation last year when I was reading it for clear descriptions of limits and options. It would not be surprising that running tests with key permutations of algorithm and policy files is the only way to safely verify compliance with international law.

The question actually has more information about actual test results than I've seen anywhere. (Good job.)

Shannon's Definition of a Bit

Note that most restrictions are measured in bits, which they should be. Cryptography is largely a probabilistic game, and Shannon's definition of a bit is a 2:1 probability ratio. This directly applies to passwords, if one wishes to be accurate. Whether cryptography providers properly apply the mathematics is dubious.

The number of bits in a character is dependent upon the encoding's range. A hexidecimal digit is log2(16) = 4 bits. An ASCII printable character has 95 possible values, thus is actually log2(95) = 6.57 bits (not a byte). UTF-8 printable characters considerably exceed 8 bits.

Whether the lawmakers understand the math is also questionable.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.