Java相当于一个OpenSSL AES CBCencryption
我不是一个encryptionprofi,特别是由于OpenSSL有很多遗漏的文档,我不知道我该如何解决这个问题。
我有一个外部系统,预计接收encryption消息。 所提供的唯一例子就是这样使用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
文件在一行中包含此string的位置:
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`
我发现这个问题,我已经能够使用下面的代码进行encryption:
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);
我不明白的是,我应该如何生成类似的输出(encryptedData)到OpenSSL的function。 我有salt,iv和cipherText,是OpenSSL输出的这些串联的Base64编码的结果吗? 或者只有其中一个?
在encryption之前我与其他系统共享的唯一的东西就是密码短语。 如果他们不知道迭代的次数和次数,他们怎么能解密结果呢?
有人可以给这些未知参数的答案,也告诉我,如果上面的代码是相当于OpenSSL过程?
以下是解密上述OPENSSLencryption(它需要Java 8)的Java程序:
import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.security.MessageDigest; import java.util.Arrays; import java.util.Base64; import java.util.Base64.Decoder; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; public class TestAesDecrypt { public static void main(final String[] args) throws Exception { final byte[] pass = "testpass".getBytes(StandardCharsets.US_ASCII); final byte[] magic = "Salted__".getBytes(StandardCharsets.US_ASCII); final String inFile = "e:/t/e.txt"; String source = new String(Files.readAllBytes(Paths.get(inFile)), StandardCharsets.US_ASCII); source = source.replaceAll("\\s", ""); final Decoder decoder = Base64.getDecoder(); final byte[] inBytes = decoder.decode(source); final byte[] shouldBeMagic = Arrays.copyOfRange(inBytes, 0, magic.length); if (!Arrays.equals(shouldBeMagic, magic)) { System.out.println("Bad magic number"); return; } final byte[] salt = Arrays.copyOfRange(inBytes, magic.length, magic.length + 8); final byte[] passAndSalt = concat(pass, salt); byte[] hash = new byte[0]; byte[] keyAndIv = new byte[0]; for (int i = 0; i < 3; i++) { final byte[] data = concat(hash, passAndSalt); final MessageDigest md = MessageDigest.getInstance("MD5"); hash = md.digest(data); keyAndIv = concat(keyAndIv, hash); } final byte[] keyValue = Arrays.copyOfRange(keyAndIv, 0, 32); final byte[] iv = Arrays.copyOfRange(keyAndIv, 32, 48); final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); final SecretKeySpec key = new SecretKeySpec(keyValue, "AES"); cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv)); final byte[] clear = cipher.doFinal(inBytes, 16, inBytes.length - 16); final String clearText = new String(clear, StandardCharsets.ISO_8859_1); System.out.println(clearText); } private static byte[] 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; } }
这个问题有一个已经被接受的答案,但是这似乎是一再出现的。 我有2个项目是我们与第三方沟通的,密码是带有预共享密钥的OpenSSL AES。
我已经使用了not-common-ssl库。 然而,它似乎被困在0.3.x版本,近两年没有发布,没有任何邮件列表的stream量或可见的发展,我不得不断定这基本上是死的。
基于一些额外的stackoverflow问题,我确实发现Spring Security和Encryptor4j都似乎提供了一些合理的打包文本编码。 然而,试图让Spring Security的encryption器在解码一个已知的编码文本string时失败了,我猜测OpenSSL使用的IV和密钥生成在所提供的实现中是不支持的。
通过检查上面的代码,以及一个已知的工作C#和PHP实现,我能够拿出一个实用程序类,目前正在通过我的testing互操作性。 一般来说,我非常喜欢使用已知的库,但是如果有一个我一直无法find它。 该类( 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; } }
您可以查看这个讨论,将密钥生成algorithm指定为两个MD5散列的串联。
关于那里提到的盐, openssl的enc man手册说:
当使用salt时,encryption数据的前八个字节是为salt保留的:encryption文件时随机生成,当解密时从encryption文件中读取。