相当于 OpenSSL AES CBC 加密的 Java

标签 java encryption openssl cryptography

我不是密码学专业人士,特别是由于 OpenSSL 缺少很多文档,我不确定如何解决这个问题。

我有一个外部系统需要接收加密消息。提供的唯一示例以这种方式使用 OpenSSL:

$ openssl enc -aes-256-cbc -a -in t.txt -k testpass
U2FsdGVkX1/RUdaSJKRXhHv3zUyTsQwu5/ar2ECKDlrNyH5GL4xRR4fgxkiWqkS1
cQstcoSIgWfRPSOFj/5OtdNLeNXiVR6MxSKJ+NvS9LyUD8+Rg6XIcYUvxR4gHi3w
DWT44LAMCpRAh1Q0t4Z2g7rwb0D05T6ygLaWvB5zD/xGZD3brTqSlWmiJb9Imgda
M6soZO7BhbYdqWqEUl5r6+EbkD21f6L3NX3hJFo+BJ+VFctiAlBO8NwT5l4ogo/s
GErm8gqRr57XoX/kvKAimg==

t.txt 文件在一行中包含此字符串:

AMOUNT=10&TID=#19:23&CURRENCY=EUR&LANGUAGE=DE&SUCCESS_URL=http://some.url/sucess&ERROR_URL=http://some.url/error&CONFIRMATION_URL=http://some.url/confirm&NAME=customer full name`

我找到了 this其他问题,我已经能够使用以下代码进行加密:

String password = "passPhrase";
String salt = "15charRandomSalt";
int iterations = 100;

/* Derive the key, given password and salt. */
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt.getBytes(Charset.forName("UTF8")), iterations, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");

/* Encrypt the message. */
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();
byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
byte[] cipherText = cipher.doFinal(toBeEncrypted.getBytes("UTF-8"));
encryptedData = Base64.getEncoder().encodeToString(cipherText);
encryptedData += Base64.getEncoder().encodeToString(iv);

我不明白的是我应该如何生成与 OpenSSL 所做的类似的输出(加密数据)。我有 salt、iv 和 cipherText,OpenSSL 输出的 Base64 编码结果是这些的串联结果吗?还是只有一个?

在加密之前,我唯一与其他系统共享的是密码。如果不知道盐分和迭代次数,他们如何解密结果?

谁能给出那些未知参数的答案,并告诉我上面的代码是否等同于 OpenSSL 过程?

最佳答案

这个问题有一个公认的答案,有点旧,但这似乎是一次又一次出现的问题。我有 2 个项目,我们与第三方通信,密码是带有预共享 key 的 OpenSSL AES。

我已经使用了 not-yet-common-ssl 库。然而,它似乎停留在 0.3.x 版本,并且近 2 年没有发布,没有任何邮件列表流量或可见的开发,我不得不得出结论,这基本上已经死了。

根据一些额外的 stackoverflow 问题,我确实找到了 Spring SecurityEncryptor4j两者似乎都提供了一些合理打包的文本编码。然而,尝试让 Spring Security 的加密器解码已知编码的文本字符串对我来说失败了,我猜测 OpenSSL 使用的 IV 和 key 生成在提供的实现中根本不受支持。

通过检查上面的代码,以及已知的工作 C# 和 PHP 实现,我能够想出一个实用程序类,该类目前正在通过我的互操作性测试。一般来说,我更愿意使用已知的库,但即使有,我也无法找到它。类(https://gist.github.com/rrsIPOV/4d0f6be7c58173c16e9edf9f97c7d7f2)如下:

import groovy.transform.CompileStatic;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.security.SecureRandom;
import static java.nio.charset.StandardCharsets.*

/**
* Mimics the OpenSSL AES Cipher options for encrypting and decrypting messages using a shared key (aka password) with symetric ciphers.
*/
@CompileStatic
class OpenSslAes {

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


static String encryptAndURLEncode(String password, String clearText) {
    String encrypted = encrypt(password, clearText);
    return URLEncoder.encode(encrypted, UTF_8.name() );
}

/**
 *
 * @param password  The password / key to encrypt with.
 * @param data      The data to encrypt
 * @return  A base64 encoded string containing the encrypted data.
 */
static String encrypt(String password, String clearText) {
    final byte[] pass = password.getBytes(US_ASCII);
    final byte[] salt = (new SecureRandom()).generateSeed(8);
    final byte[] inBytes = clearText.getBytes(UTF_8);

    final byte[] passAndSalt = array_concat(pass, salt);
    byte[] hash = new byte[0];
    byte[] keyAndIv = new byte[0];
    for (int i = 0; i < 3 && keyAndIv.length < 48; i++) {
        final byte[] hashData = array_concat(hash, passAndSalt);
        final MessageDigest md = MessageDigest.getInstance("MD5");
        hash = md.digest(hashData);
        keyAndIv = array_concat(keyAndIv, hash);
    }

    final byte[] keyValue = Arrays.copyOfRange(keyAndIv, 0, 32);
    final byte[] iv = Arrays.copyOfRange(keyAndIv, 32, 48);
    final SecretKeySpec key = new SecretKeySpec(keyValue, "AES");

    final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
    byte[] data = cipher.doFinal(inBytes);
    data =  array_concat(array_concat(SALTED_MAGIC, salt), data);
    return Base64.getEncoder().encodeToString( data );
}

/**
 * @see http://stackoverflow.com/questions/32508961/java-equivalent-of-an-openssl-aes-cbc-encryption  for what looks like a useful answer.  The not-yet-commons-ssl also has an implementation
 * @param password
 * @param source The encrypted data
 * @return
 */
static String decrypt(String password, String source) {
    final byte[] pass = password.getBytes(US_ASCII);

    final byte[] inBytes = Base64.getDecoder().decode(source);

    final byte[] shouldBeMagic = Arrays.copyOfRange(inBytes, 0, SALTED_MAGIC.length);
    if (!Arrays.equals(shouldBeMagic, SALTED_MAGIC)) {
        throw new IllegalArgumentException("Initial bytes from input do not match OpenSSL SALTED_MAGIC salt value.");
    }

    final byte[] salt = Arrays.copyOfRange(inBytes, SALTED_MAGIC.length, SALTED_MAGIC.length + 8);

    final byte[] passAndSalt = array_concat(pass, salt);

    byte[] hash = new byte[0];
    byte[] keyAndIv = new byte[0];
    for (int i = 0; i < 3 && keyAndIv.length < 48; i++) {
        final byte[] hashData = array_concat(hash, passAndSalt);
        final MessageDigest md = MessageDigest.getInstance("MD5");
        hash = md.digest(hashData);
        keyAndIv = array_concat(keyAndIv, hash);
    }

    final byte[] keyValue = Arrays.copyOfRange(keyAndIv, 0, 32);
    final SecretKeySpec key = new SecretKeySpec(keyValue, "AES");

    final byte[] iv = Arrays.copyOfRange(keyAndIv, 32, 48);

    final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
    final byte[] clear = cipher.doFinal(inBytes, 16, inBytes.length - 16);
    return new String(clear, UTF_8);
}


private static byte[] array_concat(final byte[] a, final byte[] b) {
    final byte[] c = new byte[a.length + b.length];
    System.arraycopy(a, 0, c, 0, a.length);
    System.arraycopy(b, 0, c, a.length, b.length);
    return c;
}
}

关于相当于 OpenSSL AES CBC 加密的 Java,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32508961/

相关文章:

java - html文件的lucene索引

Java GPA 计算器循环

git - Git-encrypt 中的错误加密实践?

openssl sha256 差异

Android Spring 和 SSL 的 java.lang.StackOverflowError

Java YouTube正则表达式获取视频ID和时间戳

c# - DataProtectionProvider构造函数保护说明

Java如何检查值是否已经AES加密;

c++ - EVP_PKEY_encrypt() 输出大小为 0

c++ - 使用 openssl libcrypto 使用 RSA 私钥解密数据时 RSA_NO_PADDING 的用法