java - OpenSSL 加密密钥和 IV 与 Java 程序生成的不同
问题描述
我正在修改一个用于解密 OpenSSL 加密的密码的 Java 程序。现在我收到以下异常
Exception in thread "main" javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:991)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:847)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446)
at javax.crypto.Cipher.doFinal(Cipher.java:2222)
at test.TestAesDecrypt.main(TestAesDecrypt.java:107)
通过各种链接,了解这是由于生成的密钥不同。OpenSSL 和程序中使用的 key 和 iv 是不同的。这些是由 OpenSSL 和程序在内部生成的。
如果我对 OpenSSL 密钥和 iv 进行硬编码以进行编程,则可以进行解密。
加密的密码是helloworld!
密码是 TIKpasskey001002 。原始程序来自这里。
我该如何解决?
C:\WINDOWS\system32>C:\OpenSSL-Win64\bin\openssl.exe aes-256-cbc -salt -in D:\Misc\tryouts\encryption3\password.txt -out D:\Misc\tryouts\encryption3\enpassfile.encr -pass file:D:\Misc\tryouts\encryption3\passkeyfile.txt -p
salt=E68886B9E9C2ACD8
key=5F9A0BAF409AF8F9B9FECA9008508A91CEEE86B3A334EC6E00D08A7A8B8372C5
iv =FF3EC566671077A5CC7F0695F8CC590B
C:\WINDOWS\system32>C:\OpenSSL-Win64\bin\openssl.exe aes-256-cbc -d -salt -in D:\Misc\tryouts\encryption3\enpassfile.encr -out D:\Misc\tryouts\encryption3\decpassfile.dcr -pass file:D:\Misc\tryouts\encryption3\passkeyfile.txt -p
salt=E68886B9E9C2ACD8
key=5F9A0BAF409AF8F9B9FECA9008508A91CEEE86B3A334EC6E00D08A7A8B8372C5
iv =FF3EC566671077A5CC7F0695F8CC590B
Values from Java Program:
Values from Java Program:
Encrypted cipher: 53616C7465645F5FE68886B9E9C2ACD84CB17D3CA8FEFC54E189766B97E815BD
Encrypted cipher via Apache Commons Hex: 53616c7465645f5fe68886b9e9c2acd84cb17d3ca8fefc54e189766b97e815bd
Pass Key: 54494B706173736B6579303031303032
Pass Key: TIKpasskey001002
keyValue: 6F16A5F5DA36A6A479CEBFAB6A8258D490A0A3994D204AC1AFD338AF7B4D4306
keyValue via Apache Commons Hex: 6f16a5f5da36a6a479cebfab6a8258d490a0a3994d204ac1afd338af7b4d4306
IV: E19C6128D0851D09F7E01F34061C88E3
IV via Apache Commons Hex: e19c6128d0851d09f7e01f34061c88e3
该程序是
import java.io.File;
import java.io.FileInputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.io.IOUtils;
public class TestAesDecrypt {
private final static char[] hexArray = "0123456789ABCDEF".toCharArray();
public static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
public static byte[] getBytes(String inputFile) {
String outputFile = inputFile;
File infile = new File(outputFile);
byte[] readBytes = null;
try {
readBytes = IOUtils.toByteArray(new FileInputStream(infile));
} catch (Exception e) {
e.printStackTrace();
}
return readBytes;
}
public static void main(final String[] args) throws Exception {
final byte[] pass = getBytes("D:\\\\Misc\\\\tryouts\\\\encryption3\\\\passkeyfile.txt");
final byte[] magic = "Salted__".getBytes(StandardCharsets.US_ASCII);
final String inFile = "D:\\Misc\\tryouts\\encryption3\\enpassfile.encr";
final byte[] inBytes = Files.readAllBytes(Paths.get(inFile));// decoder.decode(source);
System.out.println("Encrypted cipher: " + bytesToHex(inBytes));
System.out.println("Encrypted cipher via Apache Commons Hex: " + Hex.encodeHexString(inBytes));
System.out.println("Pass Key: " + bytesToHex(pass));
System.out.println("Pass Key: " + new String(pass));
final byte[] shouldBeMagic = Arrays.copyOfRange(inBytes, 0, magic.length);
if (!Arrays.equals(shouldBeMagic, magic)) {
System.out.println("Bad magic number");
return;
}
final byte[] salt = Arrays.copyOfRange(inBytes, magic.length, magic.length + 8);
final byte[] passAndSalt = concat(pass, salt);
byte[] hash = new byte[0];
byte[] keyAndIv = new byte[0];
for (int i = 0; i < 3; i++) {
final byte[] data = concat(hash, passAndSalt);
final MessageDigest md = MessageDigest.getInstance("MD5");
hash = md.digest(data);
keyAndIv = concat(keyAndIv, hash);
}
final byte[] keyValue = Arrays.copyOfRange(keyAndIv, 0, 32);
final byte[] iv = Arrays.copyOfRange(keyAndIv, 32, 48);
/*
* Key and IV from OpenSSL command -p
* final byte[] keyValue =
* javax.xml.bind.DatatypeConverter.parseHexBinary(
* "5F9A0BAF409AF8F9B9FECA9008508A91CEEE86B3A334EC6E00D08A7A8B8372C5"); final
* byte[] iv = javax.xml.bind.DatatypeConverter.parseHexBinary(
* "FF3EC566671077A5CC7F0695F8CC590B");
*/
final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
final SecretKeySpec key = new SecretKeySpec(keyValue, "AES");
System.out.println("IV: " + bytesToHex(iv));
System.out.println("IV via Apache Commons Hex: " + Hex.encodeHexString(iv));
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
/**** EXCEPTION OCCURS HERE START *****/
final byte[] clear = cipher.doFinal(inBytes, 16, inBytes.length - 16);
/**** EXCEPTION OCCURS HERE STOP *****/
final String clearText = new String(clear, StandardCharsets.ISO_8859_1);
System.out.println(clearText);
}
private static byte[] concat(final byte[] a, final byte[] b) {
final byte[] c = new byte[a.length + b.length];
System.arraycopy(a, 0, c, 0, a.length);
System.arraycopy(b, 0, c, a.length, b.length);
return c;
}
}
解决方案
除了根据reinier的评论从密码值中删除 CRLF 之外:
您显然使用的是OpenSSL 1.1.0或更高版本,它将PBKDF/PBE中默认使用的哈希从 MD5更改为 SHA256。在Unixy系统上(可能需要一个特殊的部分,如),你显然不是,或者在网络上的HISTORY 下,也许密码到与 OpenSSL 命令兼容的关键功能?. 请注意,使用 SHA256,您只需要 2 个块而不是 PBKDF 中的 4 个。enc
man enc
1ssl
仅供参考,您不需要复制数组切片;两者都SecretKeySpec
具有IvParameterSpec
构造函数重载,这些重载使用字节数组的一部分而不是整个数组。并且 AES-256 密钥是 32 字节(至少在 Java 中),这是您编码的,即使您的评论不同。
推荐阅读
- mysql - 如何获取查询在执行 MySQL 期间使用的列/表?
- python - 警告命令 discord.py(日志记录)
- python - 在 Matplotlib 或 Seaborn 中绘制分布在右上角的散点图/线图
- python - 如何正确使用熊猫的切片/替换为另一列的值,然后将一行分解为两列
- google-sheets-api - Google Sheets API spreadsheets.values.get 返回错误的日期值
- node.js - URLSession 正在发出 GET 请求而不是 POST 请求
- regex - 正则表达式提取第三个连字符之前和之后的字符串
- powershell - 如何在 PSCustomObject 的 ScriptMethod 上使用默认值
- javascript - 在本地准确确定或设置新渲染帧的帧速率和回调的解决方案
- r - 如何在 R 中创建包含重复值和唯一值的新列