当使用密码时,OpenSSL 以特定格式存储密文,即 ASCII 编码Salted__
,后面是 8 个字节的 salt,然后是实际的密文。
在解密过程中,salt 一定不能是随机生成的(就像在发布的代码中所做的那样),否则将得出错误的密钥和 IV。相反,盐必须根据密文的元数据来确定。还必须修复流类的使用:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.charset.StandardCharsets;
import org.bouncycastle.crypto.digests.MD5Digest;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.generators.OpenSSLPBEParametersGenerator;
import org.bouncycastle.crypto.io.CipherInputStream;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.ParametersWithIV;
...
String inputPath = "..."; // path to enc file
String outputPath = "..."; // path to dec file
String passwordStr = "...";
// Decrypt with AES-256, CBC using streams
try (FileInputStream fis = new FileInputStream(inputPath)){
// Determine salt from OpenSSL format
fis.readNBytes(8); // Skip prefix Salted__
byte[] salt = fis.readNBytes(8); // Read salt
// Derive 32 bytes key (AES_256) and 16 bytes IV via EVP_BytesToKey()
byte[] password = passwordStr.getBytes(StandardCharsets.UTF_8);
OpenSSLPBEParametersGenerator pbeGenerator = new OpenSSLPBEParametersGenerator(new MD5Digest()); // SHA256 as of v1.1.0 (if in OpenSSL the default digest is applied)
pbeGenerator.init(password, salt);
ParametersWithIV parameters = (ParametersWithIV) pbeGenerator.generateDerivedParameters(256, 128); // keySize, ivSize in bits
// Decrypt chunkwise (for large data)
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()));
cipher.init(false, parameters);
try (CipherInputStream cis = new CipherInputStream(fis, cipher);
FileOutputStream fos = new FileOutputStream(outputPath)) {
int bytesRead = -1;
byte[] buffer = new byte[64 * 1024 * 1024]; // chunksize, e.g. 64 MiB
while ((bytesRead = cis.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}
}
}
这在功能上等同于 OpenSSL 语句:
openssl enc -d -aes256 -k <passpharse> -in <enc file> -out <dec file>
请注意,OpenSSL 在早期版本中默认应用 MD5 作为摘要,自 v.1.1.0 起使用 SHA256。代码和 OpenSSL 语句必须使用相同的摘要以实现兼容性。
在代码中,摘要是显式指定的,在 OpenSSL 语句中,可以通过以下方式显式设置:-md选项,以便双方都可以匹配。
请记住EVP_BytesToKey()
OpenSSL 默认使用它来派生密钥,如今被认为是不安全的。
关于 Java 8 的补充:对于 Java 8,例如盐的测定可采用下列方法:
int i = 0;
byte[] firstBlock = new byte[16];
while (i < firstBlock.length) {
i += fis.read(firstBlock, i, firstBlock.length - i);
}
byte[] salt = Arrays.copyOfRange(firstBlock, 8, 16);
循环是必要的,因为read(byte[],int,int),不像readNBytes(int)
, does 不保证缓冲区是完全充满(这里考虑非 EOF 和非错误情况)。
如果省略循环(这意味着使用等效的read(byte[])),对于那些也完全填满缓冲区的 JVM,代码仍将运行。由于这适用于最常见的小缓冲区大小的 JVM,因此代码大部分都可以工作,请参阅 dave_thompson_085 的评论。但是,这不能保证anyJVM,因此不太健壮(尽管可能不是很多)。