web-dev-qa-db-ja.com

Javaは、PBEを使用する場合、JCE無制限強度ポリシーのないシステムでAES-256ビット暗号化を許可するのはなぜですか?

暗号化エクスポート制御により、Oracle JREは JCA Documentation にリストされているように「制限された」暗号化強度が有効な状態で出荷されることはかなり標準的な知識です。 AESの場合、デフォルトの最大値はたまたま_128 bit_キー長です。 _192 bit_または_256 bit_暗号化を有効にするには、 JCE無制限強度管轄ポリシーファイル を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がスローされます。次のコードはこれを示しています。

_@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
_

少し質問があります:

  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
_
7
Andy

国際法

JCAフレームワークは国際法を明確に施行するように設計されていませんが、法律とソフトウェアの恣意的な違いを修正してコンプライアンスを促進する必要があります。相互認証は、プロバイダーが適切に処理する場合にのみ、強度制限の適切な処理を保護します。

コンプライアンス違反がある場合、オラクルおよびプロバイダーベンダーの免責事項は、これらの製品をシステムに統合する責任を負いません。

国際暗号化規制とグローバル情報経済 に関するこの調査は、CTO、セキュリティスペシャリスト、およびアーキテクトの責任が国際市場における暗号化の制限に関して何であるかを理解するための良いスタートです。

米国法則

米国商務省の産業安全保障局 は、やや複雑な一連の文書で定義されています。コンピュータ暗号化に関連するセクションはカテゴリ5と呼ばれ、文書化されています here

JCAドキュメント

これらはJCAフレームワークに関するOracleのドキュメントですが、フレームワークはプロバイダーからの協力を期待しています。

暗号化プロバイダーのドキュメントとブルートフォーステスト

各プロバイダーには、独自のドキュメントがある場合とない場合があります。私は昨年、BountyCastleのドキュメントを制限やオプションの明確な説明のために読んでいたとき、それほど感心していませんでした。アルゴリズムとポリシーファイルの主要な順列を使用してテストを実行することが、国際法の遵守を安全に検証する唯一の方法であることは驚くに値しません。

質問には、実際のテスト結果について、私がどこでも見たよりも多くの情報があります。 (よくやった。)

シャノンのビットの定義

ほとんどの制限はビット単位で測定されることに注意してください。暗号法は主に確率論的ゲームであり、シャノンのビットの定義は2:1の確率比です。正確であることが望まれる場合、これはパスワードに直接適用されます。暗号プロバイダーが数学を適切に適用するかどうかは疑わしいです。

文字のビット数は、エンコーディングの範囲によって異なります。 16進数はログです2(16)= 4ビット。 ASCII印刷可能な文字には95の可能な値があるため、実際にはログです。2(95)= 6.57ビット(バイトではない)。 UTF-8の印刷可能な文字は8ビットをかなり超えています。

国会議員が数学を理解しているかどうかも疑わしい。

1
Douglas Daseeco