This commit is contained in:
Looly 2020-09-14 19:06:06 +08:00
parent 7d81b20e2b
commit 999c1d80d4
7 changed files with 257 additions and 64 deletions

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}