Java AES 128 encrypting differently to openssl

2020-01-27 06:55发布

We've encountered a weird situation where the encryption method we're using in Java produces different output to openssl, despite them appearing identical in configuration.

Using the same key and IV, the text "The quick BROWN fox jumps over the lazy dog!" encrypts to base64'd strings...

openssl: A8cMRIrDVnBYj2+XEKaMOBQ1sufjptsAf58slR373JTeHGPWyRqJK+UQxvJ1B/1L

Java: A8cMRIrDVnBYj2+XEKaMOBQ1sufjptsAf58slR373JTEVySz5yJLGzGd7qsAkzuQ

This is our openssl call...

#!/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);
}

oopenssl output

Encrypted hex dump = 03c70c448ac35670588f6f9710a68c381435b2e7e3a6db007f9f2c951dfbdc94de1c63d6c91a892be510c6f27507fd4b

Encrypted base64 = A8cMRIrDVnBYj2+XEKaMOBQ1sufjptsAf58slR373JTeHGPWyRqJK+UQxvJ1B/1L

Java output

Encrypted hex dump = 03c70c448ac35670588f6f9710a68c381435b2e7e3a6db007f9f2c951dfbdc94c45724b3e7224b1b319deeab00933b90

Encrypted base64 = A8cMRIrDVnBYj2+XEKaMOBQ1sufjptsAf58slR373JTEVySz5yJLGzGd7qsAkzuQ

Are we missing something obvious? Or is there some hidden complexity?

3条回答
仙女界的扛把子
2楼-- · 2020-01-27 07:19

There are several reasons why these divergences can occur:

  1. 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.
  2. Still related to key derivation, the message digest used by OpenSSL by default depends on OpenSSL's version. Different versions can thus lead to different keys, and keys that differ from that computed by Java.
  3. Finally, if you are sure to be using the same key through OpenSSL and Java, one reason why it can differ is because OpenSSL prepends Salted__<yoursalt> to the encrypted string.

    Thus, in order to have the same output from Java as from OpenSSL, you need to prepend this to your result, like so:

    byte[] rawEncryptedInput = cipher.doFinal(input.getBytes());
    byte[] encryptedInputWithPrependedSalt = ArrayUtils.addAll(ArrayUtils.addAll(
                "Salted__".getBytes(), SALT), rawEncryptedInput);
    return Base64.getEncoder()
                .encodeToString(encryptedInputWithPrependedSalt);
    
查看更多
干净又极端
3楼-- · 2020-01-27 07:24

I believe the difference is the padding, not the actual encrypted data.

Have you tried to decrypt the strings?

I believe they will show up as the same.

Why is the padding different? because they are either implementing it differently, or because one is provided a file, while the other a string, which in the end, when you read them, they are not the same thing (one has an EoF marker, for example).

BTW: Since it is CBC, Cipher Block Chaining, the whole last block is affected by this padding difference

查看更多
成全新的幸福
4楼-- · 2020-01-27 07:29

It is indeed a problem of providing a string or a file. If you put a "\n" at the end of your Java code the result will be the same as in openSSL.

查看更多
登录 后发表回答