杰瑞科技汇

Java与OpenSSL的AES加密如何互通?

Of course. This is a very common and important task, as it involves interoperability between two major platforms: the OpenSSL command-line tool and Java applications.

Java与OpenSSL的AES加密如何互通?-图1
(图片来源网络,侵删)

The key to success is ensuring that both sides use the exact same parameters for encryption and decryption. The most common source of errors is mismatched settings, especially the IV (Initialization Vector) and the mode of operation.

Let's break it down step-by-step.

The Critical Parameters for Interoperability

For openssl aes-256-cbc to work with Java's AES/CBC/PKCS5Padding, these parameters must be identical on both sides:

  1. Key (-k or -K): The secret key. For AES-256, this must be 32 bytes long. It's often provided as a password, which both sides will hash to derive the key.
  2. IV (-iv): The Initialization Vector. This is 16 bytes long and must be the same for encryption and decryption. OpenSSL will generate a random one by default, and you must capture and use it in Java.
  3. Cipher Mode: aes-256-cbc in OpenSSL directly maps to AES/CBC/PKCS5Padding in Java.
  4. Password/PBKDF: The way the password is turned into a key must be the same. By default, OpenSSL uses PBKDF2 with a salt. This is the most complex part to replicate in Java.
  5. Salt: The salt used for key derivation. If you don't provide one to OpenSSL, it generates a random one, which you must capture and use in Java.
  6. Encoding: The output format. The most straightforward format for this is Raw Binary, not Base64 or Hex.

Step 1: Encrypting with OpenSSL

The goal here is to generate three things:

Java与OpenSSL的AES加密如何互通?-图2
(图片来源网络,侵删)
  1. The encrypted file.
  2. The Salt.
  3. The IV.

OpenSSL does not output the salt and IV by default in a simple way. The best approach is to capture the entire command's output (which includes the salt and IV prepended to the ciphertext) and then parse it.

Here is a robust script to do this.

#!/bin/bash
# --- Configuration ---
PASSWORD="my-secret-password"
INPUT_FILE="plaintext.txt"
ENCRYPTED_FILE="openssl_encrypted.bin"
SALT_FILE="salt.bin"
IV_FILE="iv.bin"
# --- 1. Generate a random salt (16 bytes) ---
openssl rand -hex 16 > $SALT_FILE
SALT=$(cat $SALT_FILE)
# --- 2. Encrypt the file using the salt and capturing the output ---
# The output format is: Salt<salt> + <iv> + <ciphertext>
openssl enc -aes-256-cbc -in $INPUT_FILE -out $ENCRYPTED_FILE -k $PASSWORD -S $SALT -md sha256
# --- 3. Extract the IV from the encrypted file ---
# The IV is located at offset 8 (for "Salt__" = 6 bytes) + 16 (for the salt) = 24 bytes
dd if=$ENCRYPTED_FILE bs=1 skip=24 count=16 of=$IV_FILE 2>/dev/null
echo "Encryption complete."
echo "Generated files:"
echo "  - Encrypted Data: $ENCRYPTED_FILE"
echo "  - Salt:          $SALT_FILE (Content: $SALT)"
echo "  - IV:            $IV_FILE"

After running this script, you will have:

  • openssl_encrypted.bin: The full output from OpenSSL (Salt + IV + Ciphertext).
  • salt.bin: The 16-byte salt.
  • iv.bin: The 16-byte IV.

Step 2: Decrypting in Java

Now, let's write the Java code to decrypt the file. This code must replicate OpenSSL's key derivation using the salt, and then use the extracted IV to decrypt the ciphertext.

Java与OpenSSL的AES加密如何互通?-图3
(图片来源网络,侵删)

The most important part is using SecretKeyFactory with the PBKDF2WithHmacSHA256 algorithm, which is OpenSSL's default.

OpenSSLDecryptor.java

import javax.crypto.Cipher;
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.FileInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Arrays;
public class OpenSSLDecryptor {
    public static void main(String[] args) throws Exception {
        // --- Configuration (MUST MATCH OPENSSL COMMAND) ---
        String password = "my-secret-password";
        String encryptedFilePath = "openssl_encrypted.bin";
        String saltFilePath = "salt.bin";
        String ivFilePath = "iv.bin";
        String outputFilePath = "decrypted_from_java.txt";
        // --- 1. Read the Salt, IV, and Ciphertext ---
        // Read Salt (16 bytes)
        byte[] salt = readAllBytesOrExit(saltFilePath);
        if (salt.length != 16) {
            System.err.println("Error: Salt must be 16 bytes long.");
            return;
        }
        // Read IV (16 bytes)
        byte[] iv = readAllBytesOrExit(ivFilePath);
        if (iv.length != 16) {
            System.err.println("Error: IV must be 16 bytes long.");
            return;
        }
        // Read the full encrypted file (Salt + IV + Ciphertext)
        byte[] fullEncryptedData = readAllBytesOrExit(encryptedFilePath);
        // The actual ciphertext starts after the salt and iv
        // The file format is: Salt__<salt><iv><ciphertext>
        // "Salt__" is 8 bytes, salt is 16, iv is 16.
        int offset = 8 + 16 + 16;
        if (fullEncryptedData.length < offset) {
            System.err.println("Error: Encrypted file is too small.");
            return;
        }
        byte[] ciphertext = Arrays.copyOfRange(fullEncryptedData, offset, fullEncryptedData.length);
        // --- 2. Derive the Key using PBKDF2 (MUST MATCH OPENSSL'S DEFAULT) ---
        // OpenSSL uses PBKDF2 with HMAC-SHA256 by default.
        // The iteration count is important. A common default is 10000.
        int iterations = 10000;
        char[] passwordChars = password.toCharArray();
        PBEKeySpec pbeKeySpec = new PBEKeySpec(passwordChars, salt, iterations, 256); // 256 bits for AES-256
        SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        SecretKey tmpKey = secretKeyFactory.generateSecret(pbeKeySpec);
        // Convert the derived key to an AES key
        SecretKey secretKey = new SecretKeySpec(tmpKey.getEncoded(), "AES");
        // --- 3. Decrypt the Data ---
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
        cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
        byte[] decryptedBytes = cipher.doFinal(ciphertext);
        // --- 4. Output the result ---
        // In a real app, you'd write this to a file. For this example, we'll print it.
        System.out.println("Decryption successful!");
        System.out.println("Decrypted content:");
        System.out.println(new String(decryptedBytes, StandardCharsets.UTF_8));
    }
    private static byte[] readAllBytesOrExit(String path) throws Exception {
        try (InputStream is = new FileInputStream(path)) {
            return is.readAllBytes();
        } catch (Exception e) {
            System.err.println("Could not read file: " + path);
            e.printStackTrace();
            System.exit(1);
        }
        return null; // Should not be reached
    }
}

How to Run It

  1. Create a plaintext file: echo "This is a secret message from OpenSSL." > plaintext.txt

  2. Run the OpenSSL encryption script: ./encrypt_script.sh

  3. Compile and run the Java code:

    # Make sure you have the Java Development Kit (JDK) installed
    javac OpenSSLDecryptor.java
    java OpenSSLDecryptor

Expected Output:

Decryption successful!
Decrypted content:
This is a secret message from OpenSSL.

Summary and Troubleshooting

  • Mismatched Password/Salt/IV: This is the #1 cause of failure. Double-check that the password, salt, and IV used in Java are identical to the ones generated and used by OpenSSL.
  • Incorrect Key Derivation: Using the wrong SecretKeyFactory algorithm (e.g., PBKDF2WithHmacSHA1) or iteration count will result in a different key and a decryption failure. PBKDF2WithHmacSHA256 with 10000 iterations is the most common default.
  • Incorrect Mode/Padding: Ensure Java uses AES/CBC/PKCS5Padding. This is the direct equivalent of openssl aes-256-cbc.
  • File Format: The default OpenSSL output format (Salt__...) is crucial. The Java code must be aware of this structure to correctly strip the salt and IV from the beginning of the ciphertext. If you use a different OpenSSL format (like -base64), you must adjust the Java code accordingly. The raw binary format used here is the simplest for programmatic parsing.
分享:
扫描分享到社交APP
上一篇
下一篇