【问题标题】:BouncyCastle lightweight API equivalence for PBEWITHSHA256AND256BITAES-CBC-BC用于 PBEWITHSHA256AND256BITAES-CBC-BC 的 BouncyCastle 轻量级 API 等效项
【发布时间】:2014-10-06 14:54:16
【问题描述】:

我目前正在向 JCE 注册 BouncyCastleProvider 并在我的 Java 运行时安装 Unlimited Strength Policy。因为这迫使我将运行时与产品一起发布,所以我想切换到 BouncyCastle 的轻量级 API。

不幸的是,轻量级 API 的文档记录很少,因此我正在努力设置“PBEWITHSHA256AND256BITAES-CBC-BC”算法的精确等效性。

在深入研究 BouncyCastle 的源代码和 searching SO 之后,我编写了一个不会返回预期内容的小型测试工具:

public class AESCipherTest {


private static final String data = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, " +
    "sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, " +
    "sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet " +
    "clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem " +
    "ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor " +
    "invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et " +
    "accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata " +
    "sanctus est Lorem ipsum dolor sit amet.";

private static final String password = "MySuperStrongPassword";

private static final byte[] salt = "MakeItSpicey".getBytes();

private static final String jce_algorithm = "PBEWITHSHA256AND256BITAES-CBC-BC";

public static void main(String[] args) throws Exception {

  Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

  File file = new File("D:\\test.dat");
  file.createNewFile();

  FileOutputStream fos = new FileOutputStream(file);
  OutputStream cos = encryptWithJCE(fos, password);
  cos.write(data.getBytes());
  cos.flush();
  cos.close();

  FileInputStream fis = new FileInputStream(file);
  InputStream cis = decryptWithBCLWA(fis, password);
  byte[] readByteData = new byte[data.getBytes().length];
  cis.read(readByteData);
  cis.close();
  String readData = new String(readByteData);
  System.out.println(readData);
}

public static OutputStream encryptWithJCE(OutputStream os, String password) throws Exception {
  javax.crypto.spec.PBEParameterSpec pbeParamSpec = new javax.crypto.spec.PBEParameterSpec(salt, 20);
  javax.crypto.spec.PBEKeySpec pbeKeySpec = new javax.crypto.spec.PBEKeySpec(password.toCharArray());
  javax.crypto.SecretKeyFactory secretKeyFactory = javax.crypto.SecretKeyFactory.getInstance(jce_algorithm);
  javax.crypto.SecretKey secretKey = secretKeyFactory.generateSecret(pbeKeySpec);
  javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(jce_algorithm);
  cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, secretKey, pbeParamSpec);

  OutputStream fOutputStream = new FilterOutputStream(
        new BufferedOutputStream(os)) {
     @Override
     public void close() throws IOException {
        // do nothing
     }
  };
  return new javax.crypto.CipherOutputStream(fOutputStream, cipher);
}

public static InputStream decryptWithBCLWA(InputStream inputStream, String password) throws Exception {
  org.bouncycastle.crypto.generators.PKCS12ParametersGenerator generator = new org.bouncycastle.crypto.generators.PKCS12ParametersGenerator(new org.bouncycastle.crypto.digests.SHA256Digest());
  char[] passwordChars = password.toCharArray();
  byte[] pkcs12PasswordToBytes = org.bouncycastle.crypto.PBEParametersGenerator.PKCS12PasswordToBytes(passwordChars);
  generator.init(pkcs12PasswordToBytes, salt, 20);

  org.bouncycastle.crypto.modes.CBCBlockCipher cbcBlockCipher = new org.bouncycastle.crypto.modes.CBCBlockCipher(new org.bouncycastle.crypto.engines.AESEngine());
  org.bouncycastle.crypto.params.ParametersWithIV cipherParameters = (org.bouncycastle.crypto.params.ParametersWithIV) generator.generateDerivedParameters(256, 128);
  cbcBlockCipher.init(false, cipherParameters);
  org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher aesCipher = new org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher(cbcBlockCipher, new org.bouncycastle.crypto.paddings.PKCS7Padding());

  return new org.bouncycastle.crypto.io.CipherInputStream(inputStream, aesCipher);
}}

我错过了什么?非常感谢任何帮助!


更新

还有一段旧代码需要从 JCE 过渡到 BC 轻量级 API:

byte[] keyBytes = ... // password in bytes
javax.crypto.spec.SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
javax.crypto.Cipher cipher = Cipher.getInstance("PBEWITHSHA256AND128BITAES-CBC-BC");
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
return new javax.crypto.CipherInputStream(in, cipher);

我知道将 AES 密钥与 PBE 混合使用很奇怪,但如前所述,它是用于解密/加密数据的旧代码,现在我必须能够使用 BC 轻量级 API 读取该加密数据。在这里我与 SecretKeySpec 斗争 - BC 轻量级 API 中是否有类似的东西?

【问题讨论】:

  • @ntoskrnl:反射不是一个选项,因为它违反了 Oracle 的 Java 二进制代码许可。
  • 我感觉有人对每一个与密码学相关的问题投了反对票。我不知道这是为什么,但请停止。
  • @owlstead:嗯……刚刚注意到它工作得很好。
  • 请注意data.getBytes() 使用平台默认字符编码。这对 Lorem 来说很好,但我会为现实生活中的协议明确指定字符编码(StandardCharsets.UTF_8 大部分时间)

标签: java cryptography bouncycastle


【解决方案1】:

更新后的问题的答案是,如果您给密码一个预先计算的 AES 密钥,那么完整的 PBKDF2 函数将被完全跳过。基本上,您只是在这里执行 AES 解密。如果您的keyBytes 确实是从密码中检索到的字节,那么您很可能容易受到与密码相关的攻击。

public static void main(String[] args) throws Exception {
    Security.addProvider(new BouncyCastleProvider());

    byte[] keyBytes = new byte[16];
    byte[] iv = new byte[16];
    SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
    javax.crypto.Cipher cipher = Cipher.getInstance("PBEWITHSHA256AND128BITAES-CBC-BC");
    cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
    final byte[] ciphertext = cipher.doFinal("The PBE encryption has gone!".getBytes(UTF_8));

    Cipher dCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    dCipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
    final byte[] plain = dCipher.doFinal(ciphertext);

    System.out.println(new String(plain, UTF_8));
}

输出明文:

The PBE encryption has gone!

[添加了对 alphakermit 问题的直接回答]

因此,要在 Bouncy 中执行解密,您需要执行基本的 CBC 解密,正如 cmets 中的 alphakermit 所指出的:

    KeyParameter keyParameter = new KeyParameter(aesDecrypted);
    CBCBlockCipher cbcBlockCipher = new CBCBlockCipher(new AESEngine());
    cbcBlockCipher.init(false, keyParameter);
    PaddedBufferedBlockCipher aesCipher = new PaddedBufferedBlockCipher(cbcBlockCipher, new PKCS7Padding());
    return new CipherInputStream(in, aesCipher)

当然,使用org.bouncycastle.crypto.io.CipherInputStream 而不是Java 本身提供的那个。

【讨论】:

  • 感谢您指出这一点!这终于让我通过了。我会接受你的回答是正确的。为了完整性(BC轻量级API)起见,也许您可​​以添加以下内容:KeyParameter keyParameter = new KeyParameter(keyBytes); CBCBlockCipher cbcBlockCipher = new CBCBlockCipher(new AESEngine()); cbcBlockCipher.init(false, keyParameter); PaddedBufferedBlockCipher aesCipher = new PaddedBufferedBlockCipher(cbcBlockCipher, new PKCS7Padding()); return CipherInputStream(in, aesCipher)
  • 当我终于搞定了所有加密的东西:有没有办法给你一些“赏金”?
  • 您应该能够有理由地创建赏金:在您的问题上“授予现有答案”。但是我有多余的代表,而你没有那么多,所以当你达到 SO 上的“受信任用户”级别时,也许你可以重新访问 :)
  • 正因为如此,我最初想到的是通过邮政方式获得更多的实物赏金,因为荷兰不是那么远。因此引用。
猜你喜欢
  • 2011-11-20
  • 2016-10-05
  • 1970-01-01
  • 1970-01-01
  • 2015-10-19
  • 2021-01-01
  • 1970-01-01
  • 2013-05-05
  • 1970-01-01
相关资源
最近更新 更多