如果使用 PBE,为什么 Java 允许在没有 JCE 无限强度策略的系统上进行 AES-256 位加密?

信息安全 加密 openssl 爪哇 AES 出口
2021-08-30 13:24:19

这是相当标准的知识,由于加密导出控制,Oracle JRE 附带了启用的“有限”加密强度,如JCA 文档中所列。对于 AES,默认的最大值恰好是128 bit密钥长度。要启用192 bit256 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 来加密文件,128256 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

我有几个问题:

  1. 其他人可以重现这种行为吗?
  2. 这是预期的行为还是错误?
  3. 如果有意,它是否在某处充分记录?

更新在没有安装无限强度管辖策略的机器上进一步研究后,我确定了以下 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
1个回答

国际法

JCA 框架并非旨在明确执行国际法,但应纠正法律和软件之间的任意差异以促进合规性。仅当提供者正确处理它时,相互身份验证才能确保正确处理强度限制。

如果存在违规行为,甲骨文和供应商供应商的免责声明将归咎于那些将这些产品集成到系统中的人。

这份对国际密码学法规和全球信息经济的调查是了解 CTO、安全专家和架构师在国际市场上的密码学限制方面的职责的良好开端。

美国法律

美国商务部的工业和安全政策局是在一组有些复杂的文件中定义的。计算机密码学的相关部分称为第 5 类,并在此处记录。

JCA 文档

这些是有关 JCA 框架的 Oracle 文档,但是该框架需要提供者的合作。

密码学提供者文档和蛮力测试

每个提供商可能有也可能没有自己的文档。去年,当我阅读 BountyCastle 的文档以清楚地描述限制和选项时,我对它的印象并不深刻。使用算法和策略文件的关键排列运行测试是安全验证是否符合国际法的唯一方法,这不足为奇。

这个问题实际上比我在任何地方看到的更多关于实际测试结果的信息。(做得好。)

香农对比特的定义

请注意,大多数限制都是以比特为单位的,它们应该是。密码学很大程度上是一种概率游戏,香农对比特的定义是 2:1 的概率比。如果希望准确的话,这直接适用于密码。密码学提供者是否正确应用数学是值得怀疑的。

字符中的位数取决于编码的范围。十六进制数字是 log 2 (16) = 4 位。一个 ASCII 可打印字符有 95 个可能的值,因此实际上是 log 2 (95) = 6.57 位(不是一个字节)。UTF-8 可打印字符大大超过 8 位。

立法者是否理解数学也是值得怀疑的。