Java AES 128加密与openssl不同

4
我们遇到了一个奇怪的情况,即使它们在配置上看起来完全相同,但我们在Java中使用的加密方法和openssl产生了不同的输出。
使用相同的密钥和IV,文本“ The quick BROWN fox jumps over the lazy dog!”加密为base64'd字符串... 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

And this is our 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 output

Encrypted hex dump = 03c70c448ac35670588f6f9710a68c381435b2e7e3a6db007f9f2c951dfbdc94c45724b3e7224b1b319deeab00933b90

Encrypted base64 = A8cMRIrDVnBYj2+XEKaMOBQ1sufjptsAf58slR373JTEVySz5yJLGzGd7qsAkzuQ

我们是否遗漏了一些显而易见的东西?或者存在一些隐藏的复杂性?
3个回答

3

我相信它们的区别在于填充(padding),而不是实际加密数据。

你尝试过解密这些字符串吗?

我认为它们会显示相同的内容。

为什么填充不同呢?因为它们可能是以不同的方式实现的,或者因为一个是提供了一个文件,而另一个是提供了一个字符串,在最后,当你读取它们时,它们并不是相同的东西(例如,其中一个具有EoF标记)。

顺便说一下:由于它是CBC,即Cipher Block Chaining,整个最后一个块都受到这种填充差异的影响。


1

这确实是一个提供字符串或文件的问题。如果您在Java代码末尾添加"\n",则结果与openSSL中相同。


0

这些差异发生的原因有几个:

  1. 如果您向OpenSSL和Java提供密码而不是密钥,则从密码派生密钥的方式不同,除非您在Java中重新实现OpenSSL的算法
  2. 关于密钥派生,OpenSSL默认使用的消息摘要取决于OpenSSL的版本。因此,不同版本可能会导致不同的密钥,并且这些密钥与Java计算出的密钥不同。
  3. 最后,如果您确定在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);
    

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接