diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/Cipher.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/Cipher.java index a88795ac8..6709cfb84 100644 --- a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/Cipher.java +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/Cipher.java @@ -12,6 +12,10 @@ package org.dromara.hutool.crypto; +import org.bouncycastle.crypto.BufferedBlockCipher; + +import java.util.Arrays; + /** * 密码接口,提供统一的API,用于兼容和统一JCE和BouncyCastle等库的操作 * @@ -30,7 +34,7 @@ public interface Cipher { /** * 获取块大小,当为Stream方式加密时返回0 * - * @return 块大小 + * @return 块大小,-1表示非块加密 */ int getBlockSize(); @@ -42,16 +46,59 @@ public interface Cipher { */ void init(CipherMode mode, Parameters parameters); + /** + * 根据输入长度,获取输出长度,输出长度与算法相关
+ * 输出长度只针对本次输入关联,即len长度的数据对应输出长度加doFinal的长度 + * + * @param len 输入长度 + * @return 输出长度,-1表示非块加密 + */ + int getOutputSize(int len); + /** * 执行运算,可以是加密运算或解密运算 * - * @param data 被处理的数据 - * @return 运算结果 + * @param in 输入数据 + * @param inOff 输入数据开始位置 + * @param len 被处理数据长度 + * @param out 输出数据 + * @param outOff 输出数据开始位置 + * @return 处理长度 */ - byte[] process(byte[] data); + int process(byte[] in, int inOff, int len, byte[] out, int outOff); + + /** + * 处理最后一块数据
+ * 当{@link #process(byte[], int, int, byte[], int)}处理完数据后非完整块数据,此方法用于处理块中剩余的bytes
+ * 如加密数据要求128bit,即16byes的整数,单数处理数据后为15bytes,此时根据padding方式不同,可填充剩余1byte为指定值(如填充0)
+ * 当对数据进行分段加密时,需要首先多次执行process方法,在最后一块数据处理完后执行此方法。 + * + * @param out 经过process执行过运算的结果数据 + * @param outOff 数据处理开始位置 + * @return 处理长度 + */ + int doFinal(byte[] out, int outOff); + + /** + * 处理数据,并返回最终结果 + * + * @param in 输入数据 + * @return 结果数据 + */ + default byte[] processFinal(final byte[] in) { + final byte[] buf = new byte[getOutputSize(in.length)]; + int len = process(in, 0, in.length, buf, 0); + len += doFinal(buf, len); + + if (len == buf.length) { + return buf; + } + return Arrays.copyOfRange(buf, 0, len); + } /** * Cipher所需参数,包括Key、Random、IV等信息 */ - interface Parameters { } + interface Parameters { + } } diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/CipherMode.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/CipherMode.java index c94eb2336..4e2317261 100644 --- a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/CipherMode.java +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/CipherMode.java @@ -24,19 +24,19 @@ public enum CipherMode { /** * 加密模式 */ - encrypt(Cipher.ENCRYPT_MODE), + ENCRYPT(Cipher.ENCRYPT_MODE), /** * 解密模式 */ - decrypt(Cipher.DECRYPT_MODE), + DECRYPT(Cipher.DECRYPT_MODE), /** * 包装模式 */ - wrap(Cipher.WRAP_MODE), + WRAP(Cipher.WRAP_MODE), /** * 拆包模式 */ - unwrap(Cipher.UNWRAP_MODE); + UNWRAP(Cipher.UNWRAP_MODE); /** diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/JceCipher.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/JceCipher.java index 85d70ac3e..d4dbd5c07 100644 --- a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/JceCipher.java +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/JceCipher.java @@ -15,6 +15,7 @@ package org.dromara.hutool.crypto; import org.dromara.hutool.core.lang.Assert; import org.dromara.hutool.core.lang.wrapper.Wrapper; +import javax.crypto.ShortBufferException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.Key; @@ -63,6 +64,11 @@ public class JceCipher implements Cipher, Wrapper { return this.cipher.getBlockSize(); } + @Override + public int getOutputSize(final int len) { + return this.cipher.getOutputSize(len); + } + @Override public void init(final CipherMode mode, final Parameters parameters) { Assert.isInstanceOf(JceParameters.class, parameters, "Only support JceParameters!"); @@ -100,8 +106,30 @@ public class JceCipher implements Cipher, Wrapper { } @Override - public byte[] process(final byte[] data) { - return new byte[0]; + public int process(final byte[] in, final int inOff, final int len, final byte[] out, final int outOff) { + try { + return this.cipher.update(in, inOff, len, out, outOff); + } catch (final ShortBufferException e) { + throw new CryptoException(e); + } + } + + @Override + public int doFinal(final byte[] out, final int outOff) { + try { + return this.cipher.doFinal(out, outOff); + } catch (final Exception e) { + throw new CryptoException(e); + } + } + + @Override + public byte[] processFinal(final byte[] data) { + try { + return this.cipher.doFinal(data); + } catch (final Exception e) { + throw new CryptoException(e); + } } /** diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/AsymmetricCrypto.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/AsymmetricCrypto.java index 1731a0b62..5542d8395 100644 --- a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/AsymmetricCrypto.java +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/AsymmetricCrypto.java @@ -21,8 +21,6 @@ import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import java.io.IOException; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; import java.security.Key; import java.security.PrivateKey; import java.security.PublicKey; @@ -149,8 +147,8 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto */ public AsymmetricCrypto(final String algorithm, final byte[] privateKey, final byte[] publicKey) { this(algorithm, // - KeyUtil.generatePrivateKey(algorithm, privateKey), // - KeyUtil.generatePublicKey(algorithm, publicKey)// + KeyUtil.generatePrivateKey(algorithm, privateKey), // + KeyUtil.generatePublicKey(algorithm, publicKey)// ); } @@ -255,7 +253,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto final Key key = getKeyByType(keyType); lock.lock(); try { - final Cipher cipher = initMode(Cipher.ENCRYPT_MODE, key); + final JceCipher cipher = initMode(CipherMode.ENCRYPT, key); if (this.encryptBlockSize < 0) { // 在引入BC库情况下,自动获取块大小 @@ -280,7 +278,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto final Key key = getKeyByType(keyType); lock.lock(); try { - final Cipher cipher = initMode(Cipher.DECRYPT_MODE, key); + final JceCipher cipher = initMode(CipherMode.DECRYPT, key); if (this.decryptBlockSize < 0) { // 在引入BC库情况下,自动获取块大小 @@ -352,9 +350,10 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto * @throws BadPaddingException padding错误异常 * @throws IOException IO异常,不会被触发 */ + @SuppressWarnings("resource") private byte[] doFinalWithBlock(final byte[] data, final int maxBlockSize) throws IllegalBlockSizeException, BadPaddingException, IOException { final int dataLength = data.length; - @SuppressWarnings("resource") final FastByteArrayOutputStream out = new FastByteArrayOutputStream(); + final FastByteArrayOutputStream out = new FastByteArrayOutputStream(); int offSet = 0; // 剩余长度 @@ -373,18 +372,15 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto } /** - * 初始化{@link Cipher}的模式,如加密模式或解密模式 + * 初始化{@link JceCipher}的模式,如加密模式或解密模式 * - * @param mode 模式,可选{@link Cipher#ENCRYPT_MODE}或者{@link Cipher#DECRYPT_MODE} + * @param mode 模式,可选{@link CipherMode#ENCRYPT}或者{@link CipherMode#DECRYPT} * @param key 密钥 - * @return {@link Cipher} - * @throws InvalidAlgorithmParameterException 异常算法错误 - * @throws InvalidKeyException 异常KEY错误 + * @return {@link JceCipher} */ - private Cipher initMode(final int mode, final Key key) throws InvalidAlgorithmParameterException, InvalidKeyException { + private JceCipher initMode(final CipherMode mode, final Key key) { final JceCipher cipher = this.cipher; - cipher.init(mode, - new JceCipher.JceParameters(key, this.algorithmParameterSpec, this.random)); - return cipher.getRaw(); + cipher.init(mode, new JceCipher.JceParameters(key, this.algorithmParameterSpec, this.random)); + return cipher; } } diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/bc/BCCipher.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/bc/BCCipher.java index 34a05db0b..44f4eccfd 100644 --- a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/bc/BCCipher.java +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/bc/BCCipher.java @@ -14,6 +14,9 @@ package org.dromara.hutool.crypto.bc; import org.bouncycastle.crypto.BufferedBlockCipher; import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.StreamCipher; +import org.bouncycastle.crypto.modes.AEADBlockCipher; import org.dromara.hutool.core.lang.Assert; import org.dromara.hutool.core.lang.wrapper.Wrapper; import org.dromara.hutool.crypto.Cipher; @@ -21,16 +24,31 @@ import org.dromara.hutool.crypto.CipherMode; import org.dromara.hutool.crypto.CryptoException; /** - * 基于BouncyCastle库的{@link BufferedBlockCipher}封装的加密解密实现 + * 基于BouncyCastle库封装的加密解密实现,包装包括: + *
    + *
  • {@link BufferedBlockCipher}
  • + *
  • {@link StreamCipher}
  • + *
  • {@link AEADBlockCipher}
  • + *
* * @author Looly, changhr2013 */ -public class BCCipher implements Cipher, Wrapper { +public class BCCipher implements Cipher, Wrapper { /** - * {@link BufferedBlockCipher},包含engine、mode、padding + * {@link BufferedBlockCipher},块加密,包含engine、mode、padding */ - private final BufferedBlockCipher blockCipher; + private BufferedBlockCipher blockCipher; + /** + * {@link AEADBlockCipher}, 关联数据的认证加密(Authenticated Encryption with Associated Data) + */ + private AEADBlockCipher aeadBlockCipher; + /** + * {@link StreamCipher} + */ + private StreamCipher streamCipher; + + // region ----- 构造 /** * 构造 @@ -41,42 +59,112 @@ public class BCCipher implements Cipher, Wrapper { this.blockCipher = Assert.notNull(blockCipher); } + /** + * 构造 + * + * @param aeadBlockCipher {@link AEADBlockCipher} + */ + public BCCipher(final AEADBlockCipher aeadBlockCipher) { + this.aeadBlockCipher = Assert.notNull(aeadBlockCipher); + } + + /** + * 构造 + * + * @param streamCipher {@link StreamCipher} + */ + public BCCipher(final StreamCipher streamCipher) { + this.streamCipher = Assert.notNull(streamCipher); + } + // endregion + @Override - public BufferedBlockCipher getRaw() { - return this.blockCipher; + public Object getRaw() { + if (null != this.blockCipher) { + return this.blockCipher; + } + if (null != this.aeadBlockCipher) { + return this.aeadBlockCipher; + } + return this.streamCipher; } @Override public String getAlgorithmName() { - return this.blockCipher.getUnderlyingCipher().getAlgorithmName(); + if (null != this.blockCipher) { + return this.blockCipher.getUnderlyingCipher().getAlgorithmName(); + } + if (null != this.aeadBlockCipher) { + return this.aeadBlockCipher.getUnderlyingCipher().getAlgorithmName(); + } + return this.streamCipher.getAlgorithmName(); } @Override public int getBlockSize() { - return this.blockCipher.getBlockSize(); + if (null != this.blockCipher) { + return this.blockCipher.getBlockSize(); + } + if (null != this.aeadBlockCipher) { + return this.aeadBlockCipher.getUnderlyingCipher().getBlockSize(); + } + return -1; } @Override public void init(final CipherMode mode, final Parameters parameters) { Assert.isInstanceOf(BCParameters.class, parameters, "Only support BCParameters!"); - this.blockCipher.init(mode == CipherMode.encrypt, ((BCParameters) parameters).parameters); + + if (null != this.blockCipher) { + this.blockCipher.init(mode == CipherMode.ENCRYPT, ((BCParameters) parameters).parameters); + return; + } + if (null != this.aeadBlockCipher) { + this.aeadBlockCipher.init(mode == CipherMode.ENCRYPT, ((BCParameters) parameters).parameters); + return; + } + this.streamCipher.init(mode == CipherMode.ENCRYPT, ((BCParameters) parameters).parameters); } @Override - public byte[] process(final byte[] data) { - final byte[] out; - try { - final BufferedBlockCipher cipher = this.blockCipher; - final int updateOutputSize = cipher.getOutputSize(data.length); - final byte[] buf = new byte[updateOutputSize]; - int len = cipher.processBytes(data, 0, data.length, buf, 0); - len += cipher.doFinal(buf, len); - out = new byte[len]; - System.arraycopy(buf, 0, out, 0, len); - } catch (final Exception e) { - throw new CryptoException("encrypt/decrypt process exception.", e); + public int getOutputSize(final int len) { + if (null != this.blockCipher) { + return this.blockCipher.getOutputSize(len); } - return out; + if (null != this.aeadBlockCipher) { + return this.aeadBlockCipher.getOutputSize(len); + } + return -1; + } + + @Override + public int process(final byte[] in, final int inOff, final int len, final byte[] out, final int outOff) { + if (null != this.blockCipher) { + return this.blockCipher.processBytes(in, inOff, len, out, outOff); + } + if (null != this.aeadBlockCipher) { + return this.aeadBlockCipher.processBytes(in, inOff, len, out, outOff); + } + return this.streamCipher.processBytes(in, inOff, len, out, outOff); + } + + @Override + public int doFinal(final byte[] out, final int outOff) { + if (null != this.blockCipher) { + try { + return this.blockCipher.doFinal(out, outOff); + } catch (final InvalidCipherTextException e) { + throw new CryptoException(e); + } + } + if (null != this.aeadBlockCipher) { + try { + return this.aeadBlockCipher.doFinal(out, outOff); + } catch (final InvalidCipherTextException e) { + throw new CryptoException(e); + } + } + return 0; } /** diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/symmetric/SymmetricCrypto.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/symmetric/SymmetricCrypto.java index 3de82962a..a96eb09d6 100644 --- a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/symmetric/SymmetricCrypto.java +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/symmetric/SymmetricCrypto.java @@ -34,8 +34,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Serializable; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; import java.security.SecureRandom; import java.security.spec.AlgorithmParameterSpec; import java.util.concurrent.locks.Lock; @@ -253,9 +251,7 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor, public SymmetricCrypto setMode(final CipherMode mode, final byte[] salt) { lock.lock(); try { - initMode(mode.getValue(), salt); - } catch (final Exception e) { - throw new CryptoException(e); + initMode(mode, salt); } finally { lock.unlock(); } @@ -313,10 +309,8 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor, byte[] result; lock.lock(); try { - final Cipher cipher = initMode(Cipher.ENCRYPT_MODE, salt); - result = cipher.doFinal(paddingDataWithZero(data, cipher.getBlockSize())); - } catch (final Exception e) { - throw new CryptoException(e); + final JceCipher cipher = initMode(CipherMode.ENCRYPT, salt); + result = cipher.processFinal(paddingDataWithZero(data, cipher.getBlockSize())); } finally { lock.unlock(); } @@ -328,8 +322,8 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor, CipherOutputStream cipherOutputStream = null; lock.lock(); try { - final Cipher cipher = initMode(Cipher.ENCRYPT_MODE, null); - cipherOutputStream = new CipherOutputStream(out, cipher); + final JceCipher cipher = initMode(CipherMode.ENCRYPT, null); + cipherOutputStream = new CipherOutputStream(out, cipher.getRaw()); final long length = IoUtil.copy(data, cipherOutputStream); if (this.isZeroPadding) { final int blockSize = cipher.getBlockSize(); @@ -367,9 +361,9 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor, lock.lock(); try { final byte[] salt = SaltMagic.getSalt(bytes); - final Cipher cipher = initMode(Cipher.DECRYPT_MODE, salt); + final JceCipher cipher = initMode(CipherMode.DECRYPT, salt); blockSize = cipher.getBlockSize(); - decryptData = cipher.doFinal(SaltMagic.getData(bytes)); + decryptData = cipher.processFinal(SaltMagic.getData(bytes)); } catch (final Exception e) { throw new CryptoException(e); } finally { @@ -384,8 +378,8 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor, CipherInputStream cipherInputStream = null; lock.lock(); try { - final Cipher cipher = initMode(Cipher.DECRYPT_MODE, null); - cipherInputStream = new CipherInputStream(data, cipher); + final JceCipher cipher = initMode(CipherMode.DECRYPT, null); + cipherInputStream = new CipherInputStream(data, cipher.getRaw()); if (this.isZeroPadding) { final int blockSize = cipher.getBlockSize(); if (blockSize > 0) { @@ -447,14 +441,12 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor, } /** - * 初始化{@link Cipher}为加密或者解密模式 + * 初始化{@link JceCipher}为加密或者解密模式 * - * @param mode 模式,见{@link Cipher#ENCRYPT_MODE} 或 {@link Cipher#DECRYPT_MODE} + * @param mode 模式,见{@link CipherMode#ENCRYPT} 或 {@link CipherMode#DECRYPT} * @return {@link Cipher} - * @throws InvalidKeyException 无效key - * @throws InvalidAlgorithmParameterException 无效算法 */ - private Cipher initMode(final int mode, final byte[] salt) throws InvalidKeyException, InvalidAlgorithmParameterException { + private JceCipher initMode(final CipherMode mode, final byte[] salt) { SecretKey secretKey = this.secretKey; if (null != salt) { // /issues#I6YWWD,提供OpenSSL格式兼容支持 @@ -462,7 +454,7 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor, final byte[][] keyAndIV = OpenSSLSaltParser.ofMd5(32, algorithm) .getKeyAndIV(secretKey.getEncoded(), salt); secretKey = KeyUtil.generateKey(algorithm, keyAndIV[0]); - if(ArrayUtil.isNotEmpty(keyAndIV[1])){ + if (ArrayUtil.isNotEmpty(keyAndIV[1])) { setAlgorithmParameterSpec(new IvParameterSpec(keyAndIV[1])); } } @@ -470,7 +462,7 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor, final JceCipher cipher = this.cipher; cipher.init(mode, new JceCipher.JceParameters(secretKey, this.algorithmParameterSpec, this.random)); - return cipher.getRaw(); + return cipher; } /** diff --git a/hutool-crypto/src/test/java/org/dromara/hutool/crypto/bc/BCCipherTest.java b/hutool-crypto/src/test/java/org/dromara/hutool/crypto/bc/BCCipherTest.java new file mode 100644 index 000000000..b614c1ad5 --- /dev/null +++ b/hutool-crypto/src/test/java/org/dromara/hutool/crypto/bc/BCCipherTest.java @@ -0,0 +1,28 @@ +package org.dromara.hutool.crypto.bc; + +import org.bouncycastle.crypto.BufferedBlockCipher; +import org.bouncycastle.crypto.DefaultBufferedBlockCipher; +import org.bouncycastle.crypto.engines.SM4Engine; +import org.bouncycastle.crypto.modes.CBCBlockCipher; +import org.bouncycastle.crypto.params.KeyParameter; +import org.dromara.hutool.core.util.ByteUtil; +import org.dromara.hutool.crypto.CipherMode; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class BCCipherTest { + @Test + void sm4Test() { + final byte[] data = ByteUtil.toUtf8Bytes("我是测试Hutool的字符串00"); + + final BufferedBlockCipher blockCipher = new DefaultBufferedBlockCipher(CBCBlockCipher.newInstance(new SM4Engine())); + final BCCipher bcCipher = new BCCipher(blockCipher); + bcCipher.init(CipherMode.ENCRYPT, new BCCipher.BCParameters(new KeyParameter(ByteUtil.toUtf8Bytes("1234567890000000")))); + final byte[] encryptData = bcCipher.processFinal(data); + + bcCipher.init(CipherMode.DECRYPT, new BCCipher.BCParameters(new KeyParameter(ByteUtil.toUtf8Bytes("1234567890000000")))); + final byte[] decryptData = bcCipher.processFinal(encryptData); + + Assertions.assertArrayEquals(data, decryptData); + } +} diff --git a/hutool-crypto/src/test/java/org/dromara/hutool/crypto/symmetric/SymmetricTest.java b/hutool-crypto/src/test/java/org/dromara/hutool/crypto/symmetric/SymmetricTest.java index bfb6d7d2f..7a939c9ee 100644 --- a/hutool-crypto/src/test/java/org/dromara/hutool/crypto/symmetric/SymmetricTest.java +++ b/hutool-crypto/src/test/java/org/dromara/hutool/crypto/symmetric/SymmetricTest.java @@ -135,9 +135,9 @@ public class SymmetricTest { final AES aes = new AES(Mode.CBC, Padding.PKCS5Padding, "0123456789ABHAEQ".getBytes(), "DYgjCEIMVrj2W9xN".getBytes()); // 加密为16进制表示 - aes.setMode(CipherMode.encrypt); + aes.setMode(CipherMode.ENCRYPT); final String randomData = aes.updateHex(content.getBytes(StandardCharsets.UTF_8)); - aes.setMode(CipherMode.encrypt); + aes.setMode(CipherMode.ENCRYPT); final String randomData2 = aes.updateHex(content.getBytes(StandardCharsets.UTF_8)); Assertions.assertEquals(randomData2, randomData); Assertions.assertEquals(randomData, "cd0e3a249eaf0ed80c330338508898c4");