说明
抛开hutool这个不算太好用的工具,自己翻阅了大量资料,借鉴网上的例子,优雅的实现了一个rsa以及sm2的加密处理类,这也是从json script rule 中摘选出来的代码,放弃锁处理后性能比hutool加锁的方式快两倍多。此外虽然采用了内部类包裹参数提高了效率,但同时一种加密算法也只能支持一次调用init方法,不适合做通用的工具类,仅适合调用该工具类的所有地方都使用同一套加密参数的场景,参数如密钥、AES的iv等等,也就是说如果两个调用的地方采用不同的密钥进行初始化,则会相互影响,代码如下
package edi.rule.util;
import edi.rule.config.JSRuleContext;
import edi.rule.work.custom.JSRuleException;
import edi.rule.work.enums.JSRuleSecurityEnum;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.function.BiFunction;
import java.util.function.Consumer;
/**
* @author 摩拉克斯
* @date 2022年3月17日 下午4:41:56
*/
@Slf4j
public class ZSSecurity {
private static final String provider = "BC";
static {
Security.addProvider(new BouncyCastleProvider());
}
/**
* <p>判断当前系统是否是安全模式,true是,false不是
* @param type 待判断的安全模式
* */
public static boolean isSecurityMode(JSRuleSecurityEnum type){
return JSRuleSecurityEnum.NONE != type;
}
/**
* <p>判断当前系统是否是安全模式,true是,false不是
* */
public static boolean isSecurityMode(){
return isSecurityMode(JSRuleContext.getSecurity());
}
/**
* <p>获取签名原文,后台配置的秘钥与文本进行组合
* @param plainText 文本
* @return 返回组合后的签名文本
* */
public static String joinSignKey(String plainText){
if (ZSString.isBlank(JSRuleContext.getProperties().security.signKey)){
return plainText;
}
return plainText+JSRuleContext.getProperties().security.signKey;
}
/*--------------------------------AES--------------------------------*/
public static String aesEncrypt(String plainText){
try {
return Base64.getEncoder().encodeToString(getAESCipher(Cipher.ENCRYPT_MODE).doFinal(plainText.getBytes(StandardCharsets.UTF_8)));
} catch (IllegalBlockSizeException | BadPaddingException e) {
throw new JSRuleException(e);
}
}
public static String aesDecrypt(String encryptText) {
try {
return new String(getAESCipher(Cipher.DECRYPT_MODE).doFinal(Base64.getDecoder().decode(encryptText)));
} catch (IllegalBlockSizeException | BadPaddingException e) {
throw new JSRuleException(e);
}
}
public static SecretKey aesGenerate(){
return AESParam.keyGenerator.generateKey();
}
public static void initAES(String secretKey,String vector){
AESParam.secretKey = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8),AESParam.keyInstance);
AESParam.ivSpec = new IvParameterSpec(vector.getBytes(StandardCharsets.UTF_8));
}
public static String initAES(String vector){
String secretKey = Base64.getEncoder().encodeToString(aesGenerate().getEncoded());
initAES(secretKey,vector);
return secretKey;
}
private static final class AESParam {
private static final String algorithm = "AES/CBC/PKCS5Padding";
private static final String keyInstance = "AES";
private static final KeyGenerator keyGenerator;
private static IvParameterSpec ivSpec;
private static SecretKeySpec secretKey;
static{
try {
keyGenerator = KeyGenerator.getInstance(keyInstance);
keyGenerator.init(128);
} catch (NoSuchAlgorithmException e) {
throw new JSRuleException(e);
}
}
}
private static Cipher getAESCipher(int mode){
try {
Cipher cipher = Cipher.getInstance(AESParam.algorithm);
cipher.init(mode,AESParam.secretKey,AESParam.ivSpec);
return cipher;
} catch (NoSuchAlgorithmException | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException e) {
throw new JSRuleException(e);
}
}
/*--------------------------------SM2--------------------------------*/
/**
* <p>公钥加密
* @param plainText 原文
* */
public static String sm2Encrypt(String plainText) {
return base64Encrypt(plainText,SM2Param.publicKey,getCipher(SM2Param.transformation),(d,c)->{
try {
return c.doFinal(d);
} catch (IllegalBlockSizeException | BadPaddingException e) {
throw new JSRuleException(e);
}
});
}
/**
* <p>私钥解密
* @param encryptText 为公钥加密密文
* */
public static String sm2Decrypt(String encryptText) {
return base64Decrypt(encryptText,SM2Param.privateKey,getCipher(SM2Param.transformation),(d,c)->{
try {
return c.doFinal(d);
} catch (IllegalBlockSizeException | BadPaddingException e) {
throw new JSRuleException(e);
}
});
}
/**
* <p>私钥签名
* @param text 文本
* */
public static String sm2Sign(String text) {
return base64Sign(joinSignKey(text),SM2Param.privateKey,SM2Param.algorithm);
}
public static String sm2Sign(String text, String signKey) {
return base64Sign(text+signKey,SM2Param.privateKey,SM2Param.algorithm);
}
/**
* <p>公钥验签,自动取后台配置的秘钥对原文文本进行组合,得到组合文本后再与签名后的密文进行比对验签
* @param text 待做校验的原文文本,可以是密文也可以不是
* @param signText 需要比对验证的签名文本(进行签名算法后得到的文本)
* @return true校验签名通过,false校验签名不通过
* */
public static boolean sm2Verify(String text,String signText) {
return base64Verify(joinSignKey(text),signText,SM2Param.publicKey,SM2Param.algorithm);
}
public static boolean sm2Verify(String text,String signKey,String signText) {
return base64Verify(text+signKey,signText,SM2Param.publicKey,SM2Param.algorithm);
}
/**
* <p>生成sm2秘钥对儿</>
* */
public static KeyPair sm2Generate() {
try{
// 实例化KeyPairGenerator对象,并指定算法为EC(椭圆曲线),提供者为BC(Bouncy Castle)
KeyPairGenerator kpg = KeyPairGenerator.getInstance(SM2Param.keyInstance, provider);
//设置椭圆曲线参数为sm2p256v1,这是SM2算法所使用的特定椭圆曲线,初始化KeyPairGenerator对象,使用随机数生成器以增加密钥的随机性
kpg.initialize(new ECGenParameterSpec("sm2p256v1"), new SecureRandom());
return kpg.generateKeyPair();
} catch (Exception e) {
throw new JSRuleException(e);
}
}
/**
* <p>初始化SM2对象
* */
public static void initSM2(String pubQKeyBase64,String prvDKeyBase64){
SM2Param.pubQKeyBase64 = pubQKeyBase64;
SM2Param.prvDKeyBase64 = prvDKeyBase64;
SM2Param.publicKey = initPublicKey(SM2Param.pubQKeyBase64,SM2Param.keyFactory);
SM2Param.privateKey = initPrivateKey(SM2Param.prvDKeyBase64,SM2Param.keyFactory);
}
public static String[] initSM2(){
KeyPair keyPair = sm2Generate();
String pubQKeyBase64 = Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded());
String prvDKeyBase64 = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded());
initSM2(pubQKeyBase64,prvDKeyBase64);
return new String[]{pubQKeyBase64,prvDKeyBase64};
}
private static final class SM2Param {
private static final String algorithm = "SM3withSM2";
private static final String transformation = "SM2";
private static final String keyInstance = "EC";
private static final KeyFactory keyFactory;
private static String pubQKeyBase64;
private static String prvDKeyBase64;
private static PublicKey publicKey;
private static PrivateKey privateKey;
static {
try {
keyFactory = KeyFactory.getInstance(SM2Param.keyInstance, new BouncyCastleProvider());
} catch (Exception e) {
throw new JSRuleException(e);
}
}
}
/*--------------------------------RSA--------------------------------*/
public static String rsaEncrypt(String plainText) {
return base64Encrypt(plainText,RSAParam.publicKey,getCipher(RSAParam.transformation), ZSSecurity::rsaEncryptSection);
}
public static String rsaDecrypt(String encryptText) {
return base64Decrypt(encryptText,RSAParam.privateKey,getCipher(RSAParam.transformation), ZSSecurity::rsaDecryptSection);
}
private static byte[] rsaEncryptSection(byte[] data,Cipher cipher){
return writeRsaSection(data,cipher,RSAParam.rsaEncryptSize);
}
private static byte[] rsaDecryptSection(byte[] data,Cipher cipher){
return writeRsaSection(data,cipher,RSAParam.rsaDecryptSize);
}
private static byte[] writeRsaSection(byte[] data,Cipher cipher,int size){
try(ByteArrayOutputStream out = new ByteArrayOutputStream()){
int length = data.length;int offSet = 0;byte[] cache;int i = 0;
while (length - offSet > 0) {
if (length - offSet > size) {
cache = cipher.doFinal(data, offSet, size);
} else {
cache = cipher.doFinal(data, offSet, length - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * size;
}
byte[] encryptedData = out.toByteArray();
out.close();
return encryptedData;
} catch (IOException | IllegalBlockSizeException | BadPaddingException e) {
throw new JSRuleException(e);
}
}
public static String rsaSign(String text) {
return base64Sign(joinSignKey(text),RSAParam.privateKey,RSAParam.algorithm);
}
public static String rsaSign(String text,String signKey) {
return base64Sign(text+signKey,RSAParam.privateKey,RSAParam.algorithm);
}
public static boolean rsaVerify(String text,String signText) {
return base64Verify(joinSignKey(text),signText,RSAParam.publicKey,RSAParam.algorithm);
}
public static boolean rsaVerify(String text,String signKey,String signText) {
return base64Verify(text+signKey,signText,RSAParam.publicKey,RSAParam.algorithm);
}
public static KeyPair rsaGenerate() {
try{
KeyPairGenerator kpg = KeyPairGenerator.getInstance(RSAParam.keyInstance, provider);
kpg.initialize(RSAParam.rsaKeySize);
return kpg.generateKeyPair();
} catch (Exception e) {
throw new JSRuleException(e);
}
}
public static void initRSA(String pubQKeyBase64,String prvDKeyBase64){
RSAParam.pubQKeyBase64 = pubQKeyBase64;
RSAParam.prvDKeyBase64 = prvDKeyBase64;
RSAParam.publicKey = initPublicKey(RSAParam.pubQKeyBase64,RSAParam.keyFactory);
RSAParam.privateKey = initPrivateKey(RSAParam.prvDKeyBase64,RSAParam.keyFactory);
}
public static String[] initRSA(){
KeyPair keyPair = rsaGenerate();
String pubQKeyBase64 = Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded());
String prvDKeyBase64 = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded());
initRSA(pubQKeyBase64,prvDKeyBase64);
return new String[]{pubQKeyBase64,prvDKeyBase64};
}
private static final class RSAParam {
private static final String algorithm = "SHA256withRSA";
//如果只写RSA,在正常加密算法下解密后明文前面会出现乱码,因此此处写全transformation,PKCS1Padding通常用于非对称加密,PKCS5Padding通常用于对称加密
private static final String transformation = "RSA";
private static final String keyInstance = "RSA";
private static final KeyFactory keyFactory;
private static String pubQKeyBase64;
private static String prvDKeyBase64;
private static PublicKey publicKey;
private static PrivateKey privateKey;
private static final int rsaKeySize = 512;
private static final int rsaDecryptSize = rsaKeySize/8;
private static final int rsaEncryptSize = rsaDecryptSize-1;
static {
try {
keyFactory = KeyFactory.getInstance(RSAParam.keyInstance);
} catch (Exception e) {
throw new JSRuleException(e);
}
}
}
/*由于Cipher是有状态的,因此每次加解密都需要重新创建Cipher对象*/
private static Cipher getCipher(String transformation){
try {
return Cipher.getInstance(transformation,provider);
} catch (NoSuchAlgorithmException | NoSuchProviderException | NoSuchPaddingException e) {
throw new JSRuleException(e);
}
}
private static String base64Encrypt(String plainText,Key key,Cipher cipher,BiFunction<byte[],Cipher,byte[]> doFinal) {
return Base64.getEncoder().encodeToString(encrypt(plainText.getBytes(StandardCharsets.UTF_8),key,cipher,doFinal));
}
private static String base64Decrypt(String encryptText,Key key,Cipher cipher,BiFunction<byte[],Cipher,byte[]> doFinal) {
return new String(decrypt(Base64.getDecoder().decode(encryptText),key,cipher,doFinal));
}
private static String base64Sign(String text,PrivateKey privateKey,String algorithm) {
return Base64.getEncoder().encodeToString(sign(text.getBytes(StandardCharsets.UTF_8),privateKey,algorithm));
}
private static boolean base64Verify(String text,String signText,PublicKey publicKey,String algorithm) {
return verify(text,Base64.getDecoder().decode(signText),publicKey,algorithm);
}
private static PublicKey initPublicKey(String pubQKeyBase64,KeyFactory keyFactory) {
try {
return keyFactory.generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(pubQKeyBase64)));
} catch (InvalidKeySpecException e) {
throw new JSRuleException(e);
}
}
private static PrivateKey initPrivateKey(String prvDKeyBase64,KeyFactory keyFactory) {
try {
return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(prvDKeyBase64)));
} catch (InvalidKeySpecException e) {
throw new JSRuleException(e);
}
}
private static byte[] cipherFinal(byte[] data, int mode, Key key, Cipher cipher,BiFunction<byte[],Cipher,byte[]> doFinal){
try{
cipher.init(mode, key);
}catch (Exception e){
throw new JSRuleException(e);
}
return doFinal.apply(data,cipher);
}
private static byte[] encrypt(byte[] data,Key key, Cipher cipher, BiFunction<byte[],Cipher,byte[]> doFinal) {
return cipherFinal(data,Cipher.ENCRYPT_MODE, key,cipher,doFinal);
}
private static byte[] decrypt(byte[] encryptData,Key key,Cipher cipher, BiFunction<byte[],Cipher,byte[]> doFinal) {
return cipherFinal(encryptData,Cipher.DECRYPT_MODE, key,cipher,doFinal);
}
private static byte[] sign(byte[] data,PrivateKey privateKey,String algorithm) {
try {
return getSignature(data,signature->{
try {
signature.initSign(privateKey);
} catch (InvalidKeyException e) {
throw new JSRuleException(e);
}
},algorithm).sign();
} catch (SignatureException e) {
throw new JSRuleException(e);
}
}
private static boolean verify(String data, byte[] signatureBytes,PublicKey publicKey,String algorithm) {
try {
return getSignature(data.getBytes(StandardCharsets.UTF_8),signature->{
try {
signature.initVerify(publicKey);
} catch (InvalidKeyException e) {
throw new JSRuleException(e);
}
},algorithm).verify(signatureBytes);
} catch (SignatureException e) {
throw new JSRuleException(e.getMessage());
}
}
private static Signature getSignature(byte[] data, Consumer<Signature> behavior,String algorithm) {
try{
// 初始化签名对象,指定使用SM3withSM2算法和BC提供者。
Signature signature = Signature.getInstance(algorithm, provider);
behavior.accept(signature);
signature.update(data);
return signature;
} catch (Exception e) {
throw new JSRuleException(e.getMessage());
}
}
}
可以把JSRuleException替换成自己的异常类,rsaKeySize经大文本和小文本加解密的反复比较,介于安全和性能来考量,默认值最好在512,通常来说设置的越小,性能越好,不过密钥过短会导致安全性减弱
下面是使用例子,非常简单
ZSSecurity.initRSA(rsaPublicKey,rsaPrivateKey);//也可以通过String[] keyPair = ZSSecurity.initRSA();来创建动态密钥对儿并同时初始化,详情见源码
String miwen = ZSSecurity.rsaEncrypt(json);
System.out.println(ZSSecurity.rsaDecrypt(miwen));
String qianming = ZSSecurity.rsaSign(json,"vvv");
System.out.println(ZSSecurity.rsaVerify(json,"vvv",qianming));
第二个参数vvv是签名的密钥,你可以理解为盐,是一个自定义的字符串
注意:在做rsa分段加解密的时候,两端的分段加密算法相关参数要相同,如rsaKeySIze,encryptSize,deCryptSize等,否则会有解码出来的明文前面多出一部分乱码字符的情况
上面的代码巧用内部类,一来可以隐藏相关配置,二来可以禁止外部随意访问和篡改,三来包裹各自的加密算法的参数,让代码看起来更加的??榛?。