简体   繁体   English

AES - Java中的简单加密,使用openssl解密

[英]AES - simple encrypt in Java, decrypt with openssl

I am trying to do a simple AES encryption in Java, using Java Cryto, that can then be decrypted in ObjectiveC, using OpenSSL. 我正在尝试使用Java Cryto在Java中进行简单的AES加密,然后可以使用OpenSSL在ObjectiveC中对其进行解密。

as I am not doing the ObjectiveC side, I want to make sure it works, using the openSSL command line, but I always get "bad magic number" 因为我没有做ObjectiveC方面,我想确保它的工作原理,使用openSSL命令行,但我总是得到“糟糕的魔法数字”

Here is my Java code 这是我的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();

}

Now that runs as expected, file "test-encrypted.txt" is indeed encrypted, and "test-decrypted.txt" == "test.txt" 现在按预期运行,文件“test-encrypted.txt”确实是加密的,“test-decrypted.txt”==“test.txt”

I then tried to run a decryption on the command line using OpenSSL 然后我尝试使用OpenSSL在命令行上运行解密

openssl enc -d -aes-128-ecb -in test-encrypted.txt -k aes.key

however, this always give me 然而,这总是给我

bad magic number

From what I can see, the using algorithm "AES" in Java uses "ECB" mode by default, so the above should work. 从我所看到的,Java中的使用算法“AES”默认使用“ECB”模式,所以上面应该有效。 What am I doing wrong. 我究竟做错了什么。

The problem is indeed due to the key that is computed from the password by OpenSSL. 问题确实是由于OpenSSL从密码计算出的密钥。

Most likely the reason is that OpenSSL has its own algorithm to derive a key, EVP_BytesToKey , from the password, and that is not the same as Java's. 最有可能的原因是OpenSSL有自己的算法来从密码中导出密钥EVP_BytesToKey ,这与Java的不同。

The only solution I found was to use a Java reimplementation of that algorithm: 我找到的唯一解决方案是使用该算法的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 is part of Apache Commons library. ArrayUtils是Apache Commons库的一部分。

Full usage: 完整用法:

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);

Credit to this answer for showing me the way. 相信这个答案给我指路。

The problem is with the key. 问题在于钥匙。 The -k argument expects a passphrase, not a file. -k参数需要密码,而不是文件。 In turn, when a passphrase is used by the openssl encryption routine, a magic and salt is put in front of the encrypted result. 反过来,当openssl加密例程使用密码短语时,会在加密结果前放置魔法和盐。 That's the magic that cannot be found. 这是无法找到的魔力。

To use the openssl command line, print out the key in hex and use the -K option instead of the lowercase -k option. 要使用openssl命令行,请以十六进制打印密钥,并使用-K选项而不是小写-k选项。

You could also use: 你也可以使用:

`cat aes.key`

as argument after -K , given that aes.key contains the key in hexadecimals. 作为-K之后的参数,假设aes.key包含十六进制的键。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM