Java AES 128 加密方式与 openssl 不同
Posted
技术标签:
【中文标题】Java AES 128 加密方式与 openssl 不同【英文标题】:Java AES 128 encrypting differently to openssl 【发布时间】:2014-01-31 21:25:33 【问题描述】:我们遇到了一个奇怪的情况,即我们在 Java 中使用的加密方法对 openssl 产生不同的输出,尽管它们在配置中看起来相同。
使用相同的键和IV,文本“快速的棕色狐狸跳过懒狗!”加密为 base64 的字符串...
openssl: A8cMRIrDVnBYj2+XEKaMOBQ1sufjptsAf58slR373JTeHGPWyRqJK+UQxvJ1B/1L
Java: A8cMRIrDVnBYj2+XEKaMOBQ1sufjptsAf58slR373JTEVySz5yJLGzGd7qsAkzuQ
这是我们的 openssl 调用...
#!/bin/bash
keySpec="D41D8CD98F00B2040000000000000000"
ivSpec="03B13BBE886F00E00000000000000000"
plainText="The quick BROWN fox jumps over the lazy dog!"
echo "$plainText">plainText
openssl aes-128-cbc -nosalt -K $keySpec -iv $ivSpec -e -in plainText -out cipherText
base64 cipherText > cipherText.base64
printf "Encrypted hex dump = "
xxd -p cipherText | tr -d '\n'
printf "\n\n"
printf "Encrypted base64 = "
cat cipherText.base64
这是我们的 Java...
private static void runEncryption() throws Exception
String plainText = "The quick BROWN fox jumps over the lazy dog!";
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec keySpec = new SecretKeySpec(hexToBytes("D41D8CD98F00B2040000000000000000"), 0, 16, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(hexToBytes("03B13BBE886F00E00000000000000000"));
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
byte[] encrypted = cipher.doFinal(plainText.getBytes("UTF-8"));
String encryptedHexDump = bytesToHex(encrypted);
String encryptedBase64 = new String(DatatypeConverter.printBase64Binary(encrypted));
System.out.println("Encrypted hex dump = " + encryptedHexDump);
System.out.println("");
System.out.println("Encrypted base64 = " + encryptedBase64);
private static byte[] hexToBytes(String s)
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2)
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
return data;
final protected 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);
openssl 输出
Encrypted hex dump = 03c70c448ac35670588f6f9710a68c381435b2e7e3a6db007f9f2c951dfbdc94de1c63d6c91a892be510c6f27507fd4b
Encrypted base64 = A8cMRIrDVnBYj2+XEKaMOBQ1sufjptsAf58slR373JTeHGPWyRqJK+UQxvJ1B/1L
Java 输出
Encrypted hex dump = 03c70c448ac35670588f6f9710a68c381435b2e7e3a6db007f9f2c951dfbdc94c45724b3e7224b1b319deeab00933b90
Encrypted base64 = A8cMRIrDVnBYj2+XEKaMOBQ1sufjptsAf58slR373JTEVySz5yJLGzGd7qsAkzuQ
我们是否遗漏了一些明显的东西?还是有一些隐藏的复杂性?
【问题讨论】:
【参考方案1】:我相信区别在于填充,而不是实际的加密数据。
你试过解密字符串吗?
我相信他们会以同样的方式出现。
为什么填充不同?因为他们要么以不同的方式实现它,要么因为一个提供了一个文件,而另一个提供了一个字符串,最后,当你阅读它们时,它们不是同一个东西(例如,一个有一个 EoF 标记)。
BTW:既然是CBC,Cipher Block Chaining,整个最后一个block都会受到这个padding差异的影响
【讨论】:
【参考方案2】:确实是提供字符串或文件的问题。如果您在 Java 代码的末尾加上“\n”,结果将与 openSSL 中的结果相同。
【讨论】:
【参考方案3】:出现这些差异的原因有多种:
-
If you are providing OpenSSL and Java a password instead of a key, the key derivation from the password is different, unless you reimplement OpenSSL's algorithm in Java。
仍与密钥派生相关,the message digest used by OpenSSL by default depends on OpenSSL's version。因此,不同的版本可能会导致不同的密钥,以及与 Java 计算的密钥不同的密钥。
最后,如果您确定通过 OpenSSL 和 Java 使用相同的密钥,它可能不同的一个原因是因为 OpenSSL 在加密字符串前面添加了 Salted__<yoursalt>
。
因此,为了在 Java 中获得与 OpenSSL 相同的输出,您需要将其添加到结果中,如下所示:
byte[] rawEncryptedInput = cipher.doFinal(input.getBytes());
byte[] encryptedInputWithPrependedSalt = ArrayUtils.addAll(ArrayUtils.addAll(
"Salted__".getBytes(), SALT), rawEncryptedInput);
return Base64.getEncoder()
.encodeToString(encryptedInputWithPrependedSalt);
【讨论】:
以上是关于Java AES 128 加密方式与 openssl 不同的主要内容,如果未能解决你的问题,请参考以下文章
javascript 与 PHP 通信加密,使用AES 128 CBC no padding,以及ios,java,c#文章例子