这是相当标准的知识,由于加密导出控制,Oracle JRE 附带了启用的“有限”加密强度,如JCA 文档中所列。对于 AES,默认的最大值恰好是128 bit密钥长度。要启用192 bit或256 bit加密,必须将JCE Unlimited Strength Jurisdiction Policy 文件安装到 JRE 中。
我最近偶然遇到了一个情况,这让我相信这种执法存在问题。我不确定我会称它为错误,但它肯定没有很好的记录(或者至少我找不到任何记录它的东西)。
密钥长度检查在内部完成cipher.init(),我相信它用于Cipher.getMaxAllowedKeyLength("AES")确定最大密钥大小是128还是Integer.MAX_VALUE。
使用普通的密钥加密,这个检查很好。在默认的 JRE 安装中,下面的代码按预期执行(我使用 Groovy 进行测试,但我也在纯 Java 中尝试过):
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
}
这会生成输出:
[main] INFO *.crypto.OpenSSLPBEEncryptorTest - Key: 6d71f677ecb99cf623246fb48a1d8130
[main] INFO *.crypto.OpenSSLPBEEncryptorTest - IV : 912ed675905eb4cb0f9f5714c9c9ec39
而这个测试:
@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"
}
生成此输出:
[main] INFO *.crypto.OpenSSLPBEEncryptorTest - Key: 6d71f677ecb99cf623246fb48a1d81306d71f677ecb99cf623246fb48a1d8130
[main] INFO *.crypto.OpenSSLPBEEncryptorTest - IV : 912ed675905eb4cb0f9f5714c9c9ec39
并通过抛出异常成功通过。
使用基于密码的加密时会出现问题。
因为从密码(和盐,如果提供)派生的密钥发生在密钥长度检查期间cipher.init()但之后,所以长度检查实际上适用byte[]于SecretKey.getEncoded(). 这意味着如果使用 <= 16 个字符 ( 16 bytes / 128 bits) 的密码,即使指定的密码使用256 bit密钥,检查也会通过。256 bits即使管辖政策禁止这样做,派生的密钥也将是。相反,如果使用大于 16 个字符的密码,即使使用128 bit密码,长度检查也会失败并InvalidKeyException抛出 an。下面的代码演示了这一点:
@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"
}
在具有“有限”强度加密的系统上,这两个测试都“通过”,256 bit如果密码足够短,加密仍然可用。相反,此测试表明,即使使用128 bit加密,长密码也会导致异常:
@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"
}
我认为这可能是误报,所以我使用 OpenSSL 来加密文件,128并256 bit使用“长”和“短”密码进行加密,并尝试使用 Java 对其进行解密。结果来自具有“有限”强度加密的系统:
$ 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
我有几个问题:
- 其他人可以重现这种行为吗?
- 这是预期的行为还是错误?
- 如果有意,它是否在某处充分记录?
更新在没有安装无限强度管辖策略的机器上进一步研究后,我确定了以下 PBE 算法的这些最大密码长度:
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