Java中简单AES加密,OpenSSL解密

5

我正在尝试使用Java Cryto进行简单的AES加密,然后在ObjectiveC中使用OpenSSL进行解密。

由于我不做ObjectiveC那一侧,我想确保它可以工作,因此我使用openSSL命令行进行测试,但始终收到“bad magic number”错误提示。

以下是我的Java代码:

public class EncryptionUtils {

private static final String AES_CIPHER_METHOD = "AES";
private static final int AES_KEY_SIZE = 128;

public static byte[] generateAesKey() throws NoSuchAlgorithmException {
    KeyGenerator keyGenerator = KeyGenerator.getInstance(AES_CIPHER_METHOD);
    keyGenerator.init(AES_KEY_SIZE);
    SecretKey key = keyGenerator.generateKey();
    return key.getEncoded();
}

public static SecretKeySpec createAesKeySpec(byte[] aesKey) {
    return new SecretKeySpec(aesKey, AES_CIPHER_METHOD);
}

public static void aesEncryptFile(File in, File out, SecretKeySpec aesKeySpec) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IOException {
    Cipher aesCipher = Cipher.getInstance(AES_CIPHER_METHOD);
    aesCipher.init(Cipher.ENCRYPT_MODE, aesKeySpec);
    InputStream inputStream = new FileInputStream(in);
    try {
        OutputStream outputStream = new CipherOutputStream(new FileOutputStream(out), aesCipher);
        try {
            IOUtils.copy(inputStream , outputStream);
        } finally {
            outputStream.close();
        }
    } finally {
        inputStream.close();
    }
}
}


//testcode
@Test
public void testAesEncryptFile() throws IOException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException {
    byte[] aesKey = EncryptionUtils.generateAesKey();
    SecretKeySpec aesKeySpec = EncryptionUtils.createAesKeySpec(aesKey);
    EncryptionUtils.aesEncryptFile(new File("C:\\test\\test.txt"), new File("C:\\test\\test-encrypted.txt"), aesKeySpec);

    FileOutputStream outputStream = new FileOutputStream("C:\\test\\aes.key");
    outputStream.write(aesKey);
    outputStream.close();
}

@Test
public void testAesDecryptFile() throws IOException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException {
    FileInputStream keyFis = new FileInputStream("C:\\test\\aes.key");
    ByteArrayOutputStream keyBaos = new ByteArrayOutputStream();
    IOUtils.copy(keyFis, keyBaos);

    SecretKeySpec keySpec = new SecretKeySpec(keyBaos.toByteArray(), "AES");
    Cipher cipher = Cipher.getInstance("AES");
    cipher.init(Cipher.DECRYPT_MODE, keySpec);

    FileInputStream fileInputStream = new FileInputStream("C:\\test\\test-encrypted.txt");
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    IOUtils.copy(fileInputStream, baos);

    byte[] decrypted = cipher.doFinal(baos.toByteArray());
    FileOutputStream outputStream = new FileOutputStream("C:\\test\\test-decrypted.txt");
    outputStream.write(decrypted);
    outputStream.close();

}

现在程序已如预期运行,文件“test-encrypted.txt”已被加密,而“test-decrypted.txt”与“test.txt”相等。
然后我尝试在命令行上使用OpenSSL进行解密。
openssl enc -d -aes-128-ecb -in test-encrypted.txt -k aes.key

然而,这总是让我感到困惑。
bad magic number

据我所见,在Java中使用算法"AES"默认使用"ECB"模式,因此上述代码应该可以正常工作。我做错了什么?

你的文件里可能含有一些无法打印的 ASCII 字符?尝试使用 -base64 选项。 - Hunter McMillen
似乎是 https://dev59.com/Y2Ik5IYBdhLWcg3wke3M 的重复问题。 - Oleg Estekhin
旁注:如果您想要使用 ECB 模式,请明确指定。将“"AES"”更改为“"AES/ECB/<padding>"”,以避免歧义。 (其中<padding>是您首选的填充方案)。依赖默认值是不明智的。 - Duncan Jones
1
可能是重复的问题:使用OpenSSL加密,Java解密AES - jww
3个回答

4

问题确实是由OpenSSL从密码计算出的密钥引起的。

最可能的原因是OpenSSL有自己的算法来从密码中派生一个键,EVP_BytesToKey,这与Java不同。

我发现的唯一解决方案是使用Java重新实现该算法:

private static final int KEY_LENGTH = 32;    

private byte[] deriveKey(String encryptionPassword, byte[] salt) throws NoSuchAlgorithmException {
    final byte[] passAndSalt = ArrayUtils.addAll(encryptionPassword.getBytes(), salt);
    byte[] hash = new byte[0];
    byte[] keyAndIv = new byte[0];
    for (int i = 0; i < 3 && keyAndIv.length < KEY_LENGTH; i++) {
        final byte[] dataToHash = ArrayUtils.addAll(hash, passAndSalt);
        final MessageDigest md = MessageDigest.getInstance("SHA-256");
        hash = md.digest(dataToHash);
        keyAndIv = ArrayUtils.addAll(keyAndIv, hash);
    }
    return Arrays.copyOfRange(keyAndIv, 0, KEY_LENGTH);
}

ArrayUtils是Apache Commons库的一部分。

完整使用:

IvParameterSpec initializationVectorSpec = new IvParameterSpec(
                Hex.decodeHex(encryptionInitializationVector.toCharArray()));

cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] salt = new SecureRandom().generateSeed(8);
byte[] key = deriveKey(encryptionPassword, salt);
Key keySpec = new SecretKeySpec(key, "AES");
cipher.init(Cipher.ENCRYPT_MODE, keySpec, initializationVectorSpec);

byte[] rawEncryptedInput = cipher.doFinal(input.getBytes());
byte[] encryptedInputWithPrependedSalt = ArrayUtils.addAll(ArrayUtils.addAll(
                "Salted__".getBytes(), salt), rawEncryptedInput);
return Base64.getEncoder()
                .encodeToString(encryptedInputWithPrependedSalt);

感谢这个答案指引我找到正确的方法。

我无法按原样使其工作,并且无法让openssl解密该消息。但是当我切换到“AES / ECB / PKCS5Padding”时,它可以工作。 - Anne van Leyden

0

问题出在密钥上。 -k 参数需要一个密码短语,而不是一个文件。转而使用openssl加密例程时,会在加密结果前面放置一个魔法和盐的标记。这就是找不到该魔法的原因。

要使用openssl命令行,请以十六进制形式打印密钥,并使用大写字母-K选项,而不是小写字母-k选项。

您也可以使用以下方法:

`cat aes.key`

作为参数传递给-K之后,假设aes.key包含十六进制格式的密钥。

0
我曾经为同样的问题苦苦挣扎了几天。我的任务是使用AES/CBC/PKCS5PADDING对文件进行编码,并在Java中使用给定的密码短语,以便可以通过以下命令进行解码:
.\openssl.exe enc -d -aes-256-cbc -in "test_enc.txt" -out "test_dec.txt" -iter 1000000
当我使用简单的代码(例如这个链接:https://www.baeldung.com/java-cipher-input-output-stream)对文件进行编码时,我不断地遇到“bad magic number”或“bad decrypt”错误,即使进行了一些修改。 最后,我根据openssl的代码(链接:https://github.com/openssl/openssl/blob/master/apps/enc.c)成功编写了可以正常工作的代码。以下是结果:
import lombok.SneakyThrows;

import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.OutputStream;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import java.util.Arrays;

import static java.nio.charset.StandardCharsets.US_ASCII;

public class EncryptionUtil {

    private static final int KEY_LENGTH = 32;
    private static final int IV_LENGTH = 16;

    /** OpenSSL's magic initial bytes. */
    private static final String SALTED_STR = "Salted__";
    private static final byte[] SALTED_MAGIC = SALTED_STR.getBytes(US_ASCII);

    @SneakyThrows
    public static OutputStream encodeOutputStream(OutputStream outputStream, String password) {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");

        byte[] salt = new SecureRandom().generateSeed(8);
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 1000000, (KEY_LENGTH + IV_LENGTH)*8);
        byte[] encodedKey = factory.generateSecret(spec).getEncoded();
        SecretKey secretKey = new SecretKeySpec(Arrays.copyOfRange(encodedKey, 0, KEY_LENGTH), "AES");
        IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(encodedKey, KEY_LENGTH, encodedKey.length));

        outputStream.write(SALTED_MAGIC);
        outputStream.write(salt);

        cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);

        return new CipherOutputStream(outputStream, cipher);
    }
}

以上代码我们可以简单地像这样使用:
try (InputStream in = new FileInputStream(inFilePath);
     OutputStream encryptedOutputStream = EncryptionUtil.encodeOutputStream(new FileOutputStream(encFilePath), "password")) {
     IOUtils.copy(in, encryptedOutputStream);
}

除了源代码openssl外,它还受到另一个主题中提供的代码的启发:https://dev59.com/Io7ea4cB1Zd3GeqPIf1i#41495143,但我认为使用了EVP_BytesToKey方法(https://www.openssl.org/docs/man3.1/man3/EVP_BytesToKey.html),就像@Vic的答案中所示,并且我需要PBKDF2(https://www.openssl.org/docs/man3.0/man7/EVP_KDF-PBKDF2.html)。

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