Looly 5 years ago
parent
commit
999c1d80d4

+ 49 - 0
hutool-crypto/src/main/java/cn/hutool/crypto/CipherMode.java

@@ -0,0 +1,49 @@
+package cn.hutool.crypto;
+
+import javax.crypto.Cipher;
+
+/**
+ * Cipher模式的枚举封装
+ *
+ * @author looly
+ * @since 5.4.3
+ */
+public enum CipherMode {
+	/**
+	 * 加密模式
+	 */
+	encrypt(Cipher.ENCRYPT_MODE),
+	/**
+	 * 解密模式
+	 */
+	decrypt(Cipher.DECRYPT_MODE),
+	/**
+	 * 包装模式
+	 */
+	wrap(Cipher.WRAP_MODE),
+	/**
+	 * 拆包模式
+	 */
+	unwrap(Cipher.UNWRAP_MODE);
+
+
+	/**
+	 * 构造
+	 *
+	 * @param value 见{@link Cipher}
+	 */
+	CipherMode(int value) {
+		this.value = value;
+	}
+
+	private final int value;
+
+	/**
+	 * 获取枚举值对应的int表示
+	 *
+	 * @return 枚举值对应的int表示
+	 */
+	public int getValue() {
+		return this.value;
+	}
+}

+ 9 - 1
hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java

@@ -618,11 +618,19 @@ public class KeyUtil {
 	 */
 	public static String getAlgorithmAfterWith(String algorithm) {
 		Assert.notNull(algorithm, "algorithm must be not null !");
+
+		if(StrUtil.startWithIgnoreCase(algorithm, "ECIESWith")){
+			return "EC";
+		}
+
 		int indexOfWith = StrUtil.lastIndexOfIgnoreCase(algorithm, "with");
 		if (indexOfWith > 0) {
 			algorithm = StrUtil.subSuf(algorithm, indexOfWith + "with".length());
 		}
-		if ("ECDSA".equalsIgnoreCase(algorithm) || "SM2".equalsIgnoreCase(algorithm)) {
+		if ("ECDSA".equalsIgnoreCase(algorithm)
+				|| "SM2".equalsIgnoreCase(algorithm)
+				|| "ECIES".equalsIgnoreCase(algorithm)
+		) {
 			algorithm = "EC";
 		}
 		return algorithm;

+ 125 - 59
hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/AsymmetricCrypto.java

@@ -3,6 +3,7 @@ package cn.hutool.crypto.asymmetric;
 import cn.hutool.core.codec.Base64;
 import cn.hutool.core.io.FastByteArrayOutputStream;
 import cn.hutool.crypto.CryptoException;
+import cn.hutool.crypto.KeyUtil;
 import cn.hutool.crypto.SecureUtil;
 import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
 
@@ -10,38 +11,52 @@ 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;
+import java.security.spec.AlgorithmParameterSpec;
 
 /**
  * 非对称加密算法
- * 
+ *
  * <pre>
  * 1、签名:使用私钥加密,公钥解密。
  * 用于让所有公钥所有者验证私钥所有者的身份并且用来防止私钥所有者发布的内容被篡改,但是不用来保证内容不被他人获得。
- * 
+ *
  * 2、加密:用公钥加密,私钥解密。
  * 用于向公钥所有者发布信息,这个信息可能被他人篡改,但是无法被他人获得。
  * </pre>
- * 
- * @author Looly
  *
+ * @author Looly
  */
 public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto> {
 
-	/** Cipher负责完成加密或解密工作 */
+	/**
+	 * Cipher负责完成加密或解密工作
+	 */
 	protected Cipher cipher;
 
-	/** 加密的块大小 */
+	/**
+	 * 加密的块大小
+	 */
 	protected int encryptBlockSize = -1;
-	/** 解密的块大小 */
+	/**
+	 * 解密的块大小
+	 */
 	protected int decryptBlockSize = -1;
 
+	/**
+	 * 算法参数
+	 */
+	private AlgorithmParameterSpec algorithmParameterSpec;
+
 	// ------------------------------------------------------------------ Constructor start
+
 	/**
 	 * 构造,创建新的私钥公钥对
-	 * 
+	 *
 	 * @param algorithm {@link SymmetricAlgorithm}
 	 */
 	@SuppressWarnings("RedundantCast")
@@ -51,7 +66,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
 
 	/**
 	 * 构造,创建新的私钥公钥对
-	 * 
+	 *
 	 * @param algorithm 算法
 	 */
 	@SuppressWarnings("RedundantCast")
@@ -62,10 +77,10 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
 	/**
 	 * 构造 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
 	 * 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密
-	 * 
-	 * @param algorithm {@link SymmetricAlgorithm}
+	 *
+	 * @param algorithm     {@link SymmetricAlgorithm}
 	 * @param privateKeyStr 私钥Hex或Base64表示
-	 * @param publicKeyStr 公钥Hex或Base64表示
+	 * @param publicKeyStr  公钥Hex或Base64表示
 	 */
 	public AsymmetricCrypto(AsymmetricAlgorithm algorithm, String privateKeyStr, String publicKeyStr) {
 		this(algorithm.getValue(), SecureUtil.decode(privateKeyStr), SecureUtil.decode(publicKeyStr));
@@ -74,10 +89,10 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
 	/**
 	 * 构造 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
 	 * 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密
-	 * 
-	 * @param algorithm {@link SymmetricAlgorithm}
+	 *
+	 * @param algorithm  {@link SymmetricAlgorithm}
 	 * @param privateKey 私钥
-	 * @param publicKey 公钥
+	 * @param publicKey  公钥
 	 */
 	public AsymmetricCrypto(AsymmetricAlgorithm algorithm, byte[] privateKey, byte[] publicKey) {
 		this(algorithm.getValue(), privateKey, publicKey);
@@ -86,10 +101,10 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
 	/**
 	 * 构造 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
 	 * 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密
-	 * 
-	 * @param algorithm {@link SymmetricAlgorithm}
+	 *
+	 * @param algorithm  {@link SymmetricAlgorithm}
 	 * @param privateKey 私钥
-	 * @param publicKey 公钥
+	 * @param publicKey  公钥
 	 * @since 3.1.1
 	 */
 	public AsymmetricCrypto(AsymmetricAlgorithm algorithm, PrivateKey privateKey, PublicKey publicKey) {
@@ -99,10 +114,10 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
 	/**
 	 * 构造 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
 	 * 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密
-	 * 
-	 * @param algorithm 非对称加密算法
+	 *
+	 * @param algorithm        非对称加密算法
 	 * @param privateKeyBase64 私钥Base64
-	 * @param publicKeyBase64 公钥Base64
+	 * @param publicKeyBase64  公钥Base64
 	 */
 	public AsymmetricCrypto(String algorithm, String privateKeyBase64, String publicKeyBase64) {
 		this(algorithm, Base64.decode(privateKeyBase64), Base64.decode(publicKeyBase64));
@@ -110,30 +125,30 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
 
 	/**
 	 * 构造
-	 * 
+	 * <p>
 	 * 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
 	 * 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密
-	 * 
-	 * @param algorithm 算法
+	 *
+	 * @param algorithm  算法
 	 * @param privateKey 私钥
-	 * @param publicKey 公钥
+	 * @param publicKey  公钥
 	 */
 	public AsymmetricCrypto(String algorithm, byte[] privateKey, byte[] publicKey) {
 		this(algorithm, //
-				SecureUtil.generatePrivateKey(algorithm, privateKey), //
-				SecureUtil.generatePublicKey(algorithm, publicKey)//
+				KeyUtil.generatePrivateKey(algorithm, privateKey), //
+				KeyUtil.generatePublicKey(algorithm, publicKey)//
 		);
 	}
 
 	/**
 	 * 构造
-	 * 
+	 * <p>
 	 * 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
 	 * 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密
-	 * 
-	 * @param algorithm 算法
+	 *
+	 * @param algorithm  算法
 	 * @param privateKey 私钥
-	 * @param publicKey 公钥
+	 * @param publicKey  公钥
 	 * @since 3.1.1
 	 */
 	public AsymmetricCrypto(String algorithm, PrivateKey privateKey, PublicKey publicKey) {
@@ -143,7 +158,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
 
 	/**
 	 * 获取加密块大小
-	 * 
+	 *
 	 * @return 加密块大小
 	 */
 	public int getEncryptBlockSize() {
@@ -152,7 +167,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
 
 	/**
 	 * 设置加密块大小
-	 * 
+	 *
 	 * @param encryptBlockSize 加密块大小
 	 */
 	public void setEncryptBlockSize(int encryptBlockSize) {
@@ -161,7 +176,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
 
 	/**
 	 * 获取解密块大小
-	 * 
+	 *
 	 * @return 解密块大小
 	 */
 	public int getDecryptBlockSize() {
@@ -170,13 +185,35 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
 
 	/**
 	 * 设置解密块大小
-	 * 
+	 *
 	 * @param decryptBlockSize 解密块大小
 	 */
 	public void setDecryptBlockSize(int decryptBlockSize) {
 		this.decryptBlockSize = decryptBlockSize;
 	}
 
+	/**
+	 * 获取{@link AlgorithmParameterSpec}<br>
+	 * 在某些算法中,需要特别的参数,例如在ECIES中,此处为IESParameterSpec
+	 *
+	 * @return {@link AlgorithmParameterSpec}
+	 * @since 5.4.3
+	 */
+	public AlgorithmParameterSpec getAlgorithmParameterSpec() {
+		return algorithmParameterSpec;
+	}
+
+	/**
+	 * 设置{@link AlgorithmParameterSpec}<br>
+	 * 在某些算法中,需要特别的参数,例如在ECIES中,此处为IESParameterSpec
+	 *
+	 * @param algorithmParameterSpec {@link AlgorithmParameterSpec}
+	 * @since 5.4.3
+	 */
+	public void setAlgorithmParameterSpec(AlgorithmParameterSpec algorithmParameterSpec) {
+		this.algorithmParameterSpec = algorithmParameterSpec;
+	}
+
 	@Override
 	public AsymmetricCrypto init(String algorithm, PrivateKey privateKey, PublicKey publicKey) {
 		super.init(algorithm, privateKey, publicKey);
@@ -185,10 +222,11 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
 	}
 
 	// --------------------------------------------------------------------------------- Encrypt
+
 	/**
 	 * 加密
-	 * 
-	 * @param data 被加密的bytes
+	 *
+	 * @param data    被加密的bytes
 	 * @param keyType 私钥或公钥 {@link KeyType}
 	 * @return 加密后的bytes
 	 */
@@ -197,12 +235,12 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
 		final Key key = getKeyByType(keyType);
 		lock.lock();
 		try {
-			cipher.init(Cipher.ENCRYPT_MODE, key);
+			initCipher(Cipher.ENCRYPT_MODE, key);
 
-			if(this.encryptBlockSize < 0){
+			if (this.encryptBlockSize < 0) {
 				// 在引入BC库情况下,自动获取块大小
 				final int blockSize = this.cipher.getBlockSize();
-				if(blockSize > 0){
+				if (blockSize > 0) {
 					this.encryptBlockSize = blockSize;
 				}
 			}
@@ -216,10 +254,11 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
 	}
 
 	// --------------------------------------------------------------------------------- Decrypt
+
 	/**
 	 * 解密
-	 * 
-	 * @param data 被解密的bytes
+	 *
+	 * @param data    被解密的bytes
 	 * @param keyType 私钥或公钥 {@link KeyType}
 	 * @return 解密后的bytes
 	 */
@@ -228,12 +267,12 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
 		final Key key = getKeyByType(keyType);
 		lock.lock();
 		try {
-			cipher.init(Cipher.DECRYPT_MODE, key);
+			initCipher(Cipher.DECRYPT_MODE, key);
 
-			if(this.decryptBlockSize < 0){
+			if (this.decryptBlockSize < 0) {
 				// 在引入BC库情况下,自动获取块大小
 				final int blockSize = this.cipher.getBlockSize();
-				if(blockSize > 0){
+				if (blockSize > 0) {
 					this.decryptBlockSize = blockSize;
 				}
 			}
@@ -250,16 +289,28 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
 
 	/**
 	 * 获得加密或解密器
-	 * 
+	 *
 	 * @return 加密或解密
+	 * @deprecated 拼写错误,请使用{@link #getCipher()}
 	 */
+	@Deprecated
 	public Cipher getClipher() {
 		return cipher;
 	}
 
 	/**
+	 * 获得加密或解密器
+	 *
+	 * @return 加密或解密
+	 * @since 5.4.3
+	 */
+	public Cipher getCipher() {
+		return cipher;
+	}
+
+	/**
 	 * 初始化{@link Cipher},默认尝试加载BC库
-	 * 
+	 *
 	 * @since 4.5.2
 	 */
 	protected void initCipher() {
@@ -268,13 +319,13 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
 
 	/**
 	 * 加密或解密
-	 * 
-	 * @param data 被加密或解密的内容数据
+	 *
+	 * @param data         被加密或解密的内容数据
 	 * @param maxBlockSize 最大块(分段)大小
 	 * @return 加密或解密后的数据
 	 * @throws IllegalBlockSizeException 分段异常
-	 * @throws BadPaddingException padding错误异常
-	 * @throws IOException  IO异常,不会被触发
+	 * @throws BadPaddingException       padding错误异常
+	 * @throws IOException               IO异常,不会被触发
 	 */
 	private byte[] doFinal(byte[] data, int maxBlockSize) throws IllegalBlockSizeException, BadPaddingException, IOException {
 		// 模长
@@ -291,19 +342,18 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
 
 	/**
 	 * 分段加密或解密
-	 * 
-	 * @param data 数据
+	 *
+	 * @param data         数据
 	 * @param maxBlockSize 最大分段的段大小,不能为小于1
 	 * @return 加密或解密后的数据
 	 * @throws IllegalBlockSizeException 分段异常
-	 * @throws BadPaddingException padding错误异常
-	 * @throws IOException IO异常,不会被触发
+	 * @throws BadPaddingException       padding错误异常
+	 * @throws IOException               IO异常,不会被触发
 	 */
 	private byte[] doFinalWithBlock(byte[] data, int maxBlockSize) throws IllegalBlockSizeException, BadPaddingException, IOException {
 		final int dataLength = data.length;
-		@SuppressWarnings("resource")
-		final FastByteArrayOutputStream out = new FastByteArrayOutputStream();
-		
+		@SuppressWarnings("resource") final FastByteArrayOutputStream out = new FastByteArrayOutputStream();
+
 		int offSet = 0;
 		// 剩余长度
 		int remainLength = dataLength;
@@ -316,7 +366,23 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
 			offSet += blockSize;
 			remainLength = dataLength - offSet;
 		}
-		
+
 		return out.toByteArray();
 	}
+
+	/**
+	 * 初始化{@link Cipher}
+	 *
+	 * @param mode 模式,可选{@link Cipher#ENCRYPT_MODE}或者{@link Cipher#DECRYPT_MODE}
+	 * @param key  密钥
+	 * @throws InvalidAlgorithmParameterException 异常算法错误
+	 * @throws InvalidKeyException                异常KEY错误
+	 */
+	private void initCipher(int mode, Key key) throws InvalidAlgorithmParameterException, InvalidKeyException {
+		if (null != this.algorithmParameterSpec) {
+			cipher.init(mode, key, this.algorithmParameterSpec);
+		} else {
+			cipher.init(mode, key);
+		}
+	}
 }

+ 1 - 1
hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/BaseAsymmetric.java

@@ -196,6 +196,6 @@ public class BaseAsymmetric<T extends BaseAsymmetric<T>> {
 				}
 				return this.publicKey;
 		}
-		throw new CryptoException("Uknown key type: " + type);
+		throw new CryptoException("Unsupported key type: " + type);
 	}
 }

+ 35 - 1
hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/KeyType.java

@@ -1,5 +1,7 @@
 package cn.hutool.crypto.asymmetric;
 
+import javax.crypto.Cipher;
+
 /**
  * 密钥类型
  * 
@@ -7,5 +9,37 @@ package cn.hutool.crypto.asymmetric;
  *
  */
 public enum KeyType {
-	PrivateKey, PublicKey
+	/**
+	 * 公钥
+	 */
+	PublicKey(Cipher.PUBLIC_KEY),
+	/**
+	 * 私钥
+	 */
+	PrivateKey(Cipher.PRIVATE_KEY),
+	/**
+	 * 密钥
+	 */
+	SecretKey(Cipher.SECRET_KEY);
+
+
+	/**
+	 * 构造
+	 *
+	 * @param value 见{@link Cipher}
+	 */
+	KeyType(int value) {
+		this.value = value;
+	}
+
+	private final int value;
+
+	/**
+	 * 获取枚举值对应的int表示
+	 *
+	 * @return 枚举值对应的int表示
+	 */
+	public int getValue() {
+		return this.value;
+	}
 }

+ 15 - 0
hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java

@@ -31,4 +31,19 @@ public class KeyUtilTest {
 		final PublicKey rsaPublicKey = KeyUtil.getRSAPublicKey(aPrivate);
 		Assert.assertEquals(rsaPublicKey, keyPair.getPublic());
 	}
+
+	/**
+	 * 测试EC和ECIES算法生成的KEY是一致的
+	 */
+	@Test
+	public void generateECIESKeyTest(){
+		final KeyPair ecies = KeyUtil.generateKeyPair("ECIES");
+		Assert.assertNotNull(ecies.getPrivate());
+		Assert.assertNotNull(ecies.getPublic());
+
+		byte[] privateKeyBytes = ecies.getPrivate().getEncoded();
+
+		final PrivateKey privateKey = KeyUtil.generatePrivateKey("EC", privateKeyBytes);
+		Assert.assertEquals(ecies.getPrivate(), privateKey);
+	}
 }

+ 23 - 2
hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/ECIESTest.java

@@ -1,6 +1,7 @@
 package cn.hutool.crypto.test.asymmetric;
 
 import cn.hutool.core.util.StrUtil;
+import cn.hutool.crypto.asymmetric.AsymmetricCrypto;
 import cn.hutool.crypto.asymmetric.ECIES;
 import cn.hutool.crypto.asymmetric.KeyType;
 import org.junit.Assert;
@@ -12,6 +13,25 @@ public class ECIESTest {
 	public void eciesTest(){
 		final ECIES ecies = new ECIES();
 
+		doTest(ecies, ecies);
+	}
+
+	@Test
+	public void eciesTest2(){
+		final ECIES ecies = new ECIES();
+		final byte[] privateKeyBytes = ecies.getPrivateKey().getEncoded();
+		final ECIES ecies2 = new ECIES(privateKeyBytes, null);
+
+		doTest(ecies, ecies2);
+	}
+
+	/**
+	 * 测试用例
+	 *
+	 * @param cryptoForEncrypt 加密的Crypto
+	 * @param cryptoForDecrypt 解密的Crypto
+	 */
+	private void doTest(AsymmetricCrypto cryptoForEncrypt, AsymmetricCrypto cryptoForDecrypt){
 		String textBase = "我是一段特别长的测试";
 		StringBuilder text = new StringBuilder();
 		for (int i = 0; i < 10; i++) {
@@ -19,8 +39,9 @@ public class ECIESTest {
 		}
 
 		// 公钥加密,私钥解密
-		String encryptStr = ecies.encryptBase64(text.toString(), KeyType.PublicKey);
-		String decryptStr = StrUtil.utf8Str(ecies.decrypt(encryptStr, KeyType.PrivateKey));
+		String encryptStr = cryptoForEncrypt.encryptBase64(text.toString(), KeyType.PublicKey);
+
+		String decryptStr = StrUtil.utf8Str(cryptoForDecrypt.decrypt(encryptStr, KeyType.PrivateKey));
 		Assert.assertEquals(text.toString(), decryptStr);
 	}
 }