From 73ddd17f7a1932cfcd01f871447f1d5b19bf1f14 Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 26 Apr 2023 20:31:37 +0800 Subject: [PATCH] add openssl --- .../crypto/openssl/OpenSSLPBECommon.java | 43 +++++++ .../crypto/openssl/OpenSSLPBEInputStream.java | 108 ++++++++++++++++++ .../hutool/crypto/symmetric/SaltUtilTest.java | 28 +++++ 3 files changed, 179 insertions(+) create mode 100755 hutool-crypto/src/main/java/org/dromara/hutool/crypto/openssl/OpenSSLPBECommon.java create mode 100755 hutool-crypto/src/main/java/org/dromara/hutool/crypto/openssl/OpenSSLPBEInputStream.java diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/openssl/OpenSSLPBECommon.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/openssl/OpenSSLPBECommon.java new file mode 100755 index 000000000..204968f48 --- /dev/null +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/openssl/OpenSSLPBECommon.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023 looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.crypto.openssl; + +import org.dromara.hutool.crypto.KeyUtil; +import org.dromara.hutool.crypto.SecureUtil; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.PBEParameterSpec; +import java.nio.charset.StandardCharsets; + +public class OpenSSLPBECommon { + protected static final int SALT_SIZE_BYTES = 8; + /** + * OpenSSL's magic initial bytes. + */ + protected static final byte[] SALTED_MAGIC = "Salted__".getBytes(StandardCharsets.US_ASCII); + protected static final String OPENSSL_HEADER_ENCODE = "ASCII"; + + protected static Cipher initializeCipher(char[] password, byte[] salt, int cipherMode, + final String algorithm, int iterationCount) + throws Exception { + + final SecretKey key = KeyUtil.generateKey(algorithm, new PBEKeySpec(password)); + + final Cipher cipher = SecureUtil.createCipher(algorithm); + cipher.init(cipherMode, key, new PBEParameterSpec(salt, iterationCount)); + + return cipher; + } +} diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/openssl/OpenSSLPBEInputStream.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/openssl/OpenSSLPBEInputStream.java new file mode 100755 index 000000000..ba9c8eb18 --- /dev/null +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/openssl/OpenSSLPBEInputStream.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2023 looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.crypto.openssl; + +import org.dromara.hutool.core.array.ArrayUtil; +import org.dromara.hutool.core.text.StrUtil; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; + +/* +source: http://stackoverflow.com/questions/11783062/how-to-decrypt-an-encrypted-file-in-java-with-openssl-with-aes +*/ +public class OpenSSLPBEInputStream extends InputStream { + + protected final static int READ_BLOCK_SIZE = 64 * 1024; + + private final Cipher cipher; + private final InputStream inStream; + + private final byte[] bufferCipher = new byte[READ_BLOCK_SIZE]; + private byte[] bufferClear = null; + + private int index = Integer.MAX_VALUE; + private int maxIndex = 0; + + public OpenSSLPBEInputStream(final InputStream streamIn, + final String algorithm, + final int iterationCount, + final char[] password) throws IOException { + + this.inStream = streamIn; + + readHeader(); + final byte[] salt = readSalt(); + + try { + this.cipher = OpenSSLPBECommon.initializeCipher(password, salt, Cipher.DECRYPT_MODE, algorithm, iterationCount); + } catch (final Exception e) { + throw new IOException(e); + } + } + + @Override + public int available() throws IOException { + return inStream.available(); + } + + @Override + public int read() throws IOException { + if (maxIndex < 0) { + return -1; + } + if (index > maxIndex) { + index = 0; + final int read = inStream.read(bufferCipher); + + if (read != -1) { + bufferClear = cipher.update(bufferCipher, 0, read); + } + + if (read == -1 || bufferClear == null || bufferClear.length == 0) { + try { + bufferClear = cipher.doFinal(); + } catch (IllegalBlockSizeException | BadPaddingException e) { + bufferClear = null; + } + } + + maxIndex = bufferClear == null ? -1 : bufferClear.length - 1; + + if (maxIndex < 0) { + return -1; + } + } + + return bufferClear[index++] & 0xff; + } + + private void readHeader() throws IOException { + final byte[] headerBytes = new byte[OpenSSLPBECommon.SALTED_MAGIC.length]; + inStream.read(headerBytes); + + if (!Arrays.equals(OpenSSLPBECommon.SALTED_MAGIC, headerBytes)) { + throw new IOException("unexpected file header " + StrUtil.utf8Str(headerBytes)); + } + } + + private byte[] readSalt() throws IOException { + final byte[] salt = new byte[OpenSSLPBECommon.SALT_SIZE_BYTES]; + inStream.read(salt); + return salt; + } +} diff --git a/hutool-crypto/src/test/java/org/dromara/hutool/crypto/symmetric/SaltUtilTest.java b/hutool-crypto/src/test/java/org/dromara/hutool/crypto/symmetric/SaltUtilTest.java index bd1dc0ff7..40d51e08b 100755 --- a/hutool-crypto/src/test/java/org/dromara/hutool/crypto/symmetric/SaltUtilTest.java +++ b/hutool-crypto/src/test/java/org/dromara/hutool/crypto/symmetric/SaltUtilTest.java @@ -12,12 +12,18 @@ package org.dromara.hutool.crypto.symmetric; +import org.dromara.hutool.core.codec.binary.Base64; +import org.dromara.hutool.core.io.IoUtil; +import org.dromara.hutool.core.lang.Console; import org.dromara.hutool.crypto.KeyUtil; import org.dromara.hutool.crypto.SecureUtil; +import org.dromara.hutool.crypto.openssl.OpenSSLPBEInputStream; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import javax.crypto.SecretKey; +import java.io.ByteArrayInputStream; +import java.io.IOException; public class SaltUtilTest { @@ -70,4 +76,26 @@ public class SaltUtilTest { final String decrypt = des.decryptStr(encrypted); Assertions.assertEquals("hutool", decrypt); } + + /** + * 测试: + * https://www.bejson.com/enc/aesdes/
+ * https://stackoverflow.com/questions/11783062/how-to-decrypt-file-in-java-encrypted-with-openssl-command-using-aes + */ + @Test + void aesTest2() throws IOException { + final String encrypted = "U2FsdGVkX1+lqsuKAR+OdOeNduvx5wgXf6yEUdDIh3g="; + final String pass = "1234567890123456"; + final String algorithm = "PBEWITHMD5AND256BITAES-CBC-OPENSSL"; + final String result = "hutool"; + + final OpenSSLPBEInputStream stream = new OpenSSLPBEInputStream( + new ByteArrayInputStream(Base64.decode(encrypted)), + algorithm, + 1, + pass.toCharArray()); + + final String s = IoUtil.readUtf8(stream); + Assertions.assertEquals(result, s); + } }