加解密过程
加密:
- 生成加密方SM2密钥对用于签名
- 使用生成的SM2私钥生成数字签名
- 生成SM4对称密钥对明文进行对称加密
- 使用与解密方提前约定好的SM2公钥对第三步中的SM4对称密钥进行非对称加密
- 把【加密方SM2公钥】、【数字签名】、【SM4对称加密后的密文】和【SM2非对称加密后的SM4密钥密文】四个参数封装成数字信封发送给解密方
解密:
- 获取加密方发送过来的数字信封
- 使用与加密方提前约定好的SM2私钥解密数字信封中的【SM2非对称加密后的SM4密钥密文】,得到SM4密钥
- 使用SM4密钥解密数字信封中的【SM4对称加密后的密文】,得到数据明文
- 使用数字信封中的【数字签名】、【加密方SM2公钥】对得到的数据明文进行数字签名验证,得到验证结果
- 如为true,表明数据未被篡改;否则,数据可能已被篡改
运行结果截图
下面为具体实现代码
DigitalEnvelopeUtilsTest.java
package utils;
import utils.crypto.Constant;
import utils.crypto.exception.BadRequestException;
import org.bouncycastle.crypto.engines.SM4Engine;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.util.encoders.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.*;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Base64;
/**
* @className: DigitalEnvelopeUtilsTest
* @description: 数字信封工具类测试
* @author: liuzijian
* @date: 2024/7/27 18:28
* @version: 1.0
**/
public class DigitalEnvelopeUtilsTest {
private static final Logger logger = LoggerFactory.getLogger(DigitalEnvelopeUtilsTest.class);
private static String privateKey = "ANmPM+GwxdM7Yo/YhftxmfEqqjiHl9qKuMU55+lEfcEW";
private static String publicKey = "BBQgpX+nrdNbyVmhQYlcED631eIYGKFj05+AoPMthp3pnum04Na8LG8sMul4Ro2W+DZaO/7XZvRievGY8DG2OoU=";
static {
Security.addProvider(new BouncyCastleProvider());
}
// /**
// * 数字信封加密
// * @param originalData
// * @return encParam
// */
// public static EncParam enc(byte[] originalData){
// try {
// // 1. 生成数字签名和签名公钥
// // 生成SM2密钥对用于签名
// KeyPair keyPair = generateSm2KeyPair();
// PrivateKey signPrivateKey = keyPair.getPrivate();
// PublicKey signPublicKey = keyPair.getPublic();
// // 生成数字签名
// byte[] signature = signWithSm3(originalData, signPrivateKey);
//
// // 2. 数字信封(生成数据密文+对称密钥密文)
// // SM4对称加密
// byte[] sm4Key = generateKey();
// byte[] iv = generateIV();
// byte[] encryptedData = encryptWithSm4(originalData, sm4Key, iv);
// byte[] encryptionSm4Key = SM2Utils.asymmetricKeyEncrypt(Base64.getDecoder().decode(publicKey), Constant.SM2_ALGORITHM, sm4Key);
//
// return new EncParam(signature, signPublicKey, encryptedData, encryptionSm4Key, iv);
// }catch (Exception e){
// logger.error("数字信封加密失败", e);
// return null;
// }
// }
//
// /**
// * 数字信封解密
// * @param encParam
// * @return decParam
// */
// public static DecParam dec(EncParam encParam) throws Exception {
// try {
// // 1. SM2解密获取SM4密钥
// byte[] deserializeSm4Key = SM2Utils.asymmetricKeyDecrypt(Base64.getDecoder().decode(privateKey), Constant.SM2_ALGORITHM, encParam.getEncryptionSm4Key());
//
// // 2. SM4解密
// byte[] decryptedData = decryptWithSm4(encParam.getEncryptedData(), deserializeSm4Key, encParam.getIv());
//
// // 3. 验证签名
// boolean isCorrect = verifyWithSm3(decryptedData, encParam.getSignature(), encParam.getSignPublicKey());
// return new DecParam(decryptedData, isCorrect);
// }catch (Exception e){
// logger.error("数字信封解密失败", e);
// throw new Exception("数字信封解密失败", e);
// }
// }
/**
* 生成SM2密钥对
* @return SM2密钥对
*/
private static KeyPair generateSm2KeyPair() throws Exception{
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(Constant.EC_ALGORITHM, Constant.BC_ALGORITHM);
ECNamedCurveParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec(Constant.SM2_CURVES_NAME);
keyPairGenerator.initialize(ecSpec);
return keyPairGenerator.generateKeyPair();
} catch (Exception e) {
logger.error("生成SM2密钥对失败", e);
throw new Exception("生成SM2密钥对失败", e);
}
}
/**
* 使用SM3签名
* @param data
* @param privateKey
* @return 签名
*/
private static byte[] signWithSm3(String data, PrivateKey privateKey) throws Exception{
try {
Signature signature = Signature.getInstance(Constant.SIGN_ALGORITHM, Constant.BC_ALGORITHM);
signature.initSign(privateKey);
signature.update(data.getBytes());
return signature.sign();
} catch (Exception e) {
logger.error("使用SM3签名失败", e);
throw new Exception("使用SM3签名失败", e);
}
}
/**
* 使用SM3验证签名
* @param input
* @param signature
* @param publicKey
* @return 验证成功返回true
*/
private static boolean verifyWithSm3(String input, byte[] signature,PublicKey publicKey) throws Exception{
try {
Signature signature2 = Signature.getInstance(Constant.SIGN_ALGORITHM, Constant.BC_ALGORITHM);
signature2.initVerify(publicKey);
signature2.update(input.getBytes());
return signature2.verify(signature);
}catch (Exception e) {
logger.error("使用SM3验证签名失败", e);
throw new Exception("使用SM3验证签名失败", e);
}
}
/**
* SM4加密
* @param plainText
* @param key
* @param iv
* @return 密文
*/
private static String encryptWithSm4(String plainText, byte[] key, byte[] iv) throws Exception {
try {
SM4Engine engine = new SM4Engine();
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(engine));
cipher.init(true, new ParametersWithIV(new KeyParameter(key), iv));
byte[] input = plainText.getBytes(Constant.UTF8_CHARSET);
byte[] output = new byte[cipher.getOutputSize(input.length)];
int len = cipher.processBytes(input, 0, input.length, output, 0);
cipher.doFinal(output, len);
return Hex.toHexString(output);
} catch (Exception e) {
logger.error("SM4加密失败", e);
throw new Exception("SM4加密失败", e);
}
}
/**
* SM4解密
* @param cipherText
* @param key
* @param iv
* @return 明文
*/
private static String decryptWithSm4(String cipherText, byte[] key, byte[] iv) throws Exception {
try {
SM4Engine engine = new SM4Engine();
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(engine));
cipher.init(false, new ParametersWithIV(new KeyParameter(key), iv));
byte[] input = Hex.decode(cipherText);
byte[] output = new byte[cipher.getOutputSize(input.length)];
int len = cipher.processBytes(input, 0, input.length, output, 0);
cipher.doFinal(output, len);
return new String(output, Constant.UTF8_CHARSET).trim();
}catch (Exception e) {
logger.error("SM4解密失败", e);
throw new Exception("SM4解密失败", e);
}
}
/**
* 生成SM4密钥
* @return SM4密钥
* @throws Exception
*/
private static byte[] generateKey() throws Exception {
try {
KeyGenerator keyGenerator = KeyGenerator.getInstance(Constant.SM4_ALGORITHM, Constant.BC_ALGORITHM);
// SM4密钥长度固定为128位
keyGenerator.init(128, new SecureRandom());
SecretKey secretKey = keyGenerator.generateKey();
return secretKey.getEncoded();
}catch (Exception e){
logger.error("生成SM4密钥失败", e);
throw new Exception("生成SM4密钥失败", e);
}
}
/**
* 生成IV
* @return IV
*/
private static byte[] generateIV() throws Exception {
try {
// SM4的块大小是128位(16字节)
byte[] iv = new byte[16];
new SecureRandom().nextBytes(iv);
return iv;
}catch (Exception e){
logger.error("生成IV失败", e);
throw new Exception("生成IV失败", e);
}
}
public static PublicKey generatePublicKeyByKeyValue(String algorithm, KeySpec keySpec){
try {
return KeyFactory
.getInstance(algorithm, Constant.BOUNCY_CASTLE_PROVIDER)
.generatePublic(keySpec);
} catch (InvalidKeySpecException e) {
throw new BadRequestException(Constant.INVALID_KEY_SPEC_EXCEPTION_TIPS,e);
} catch (NoSuchAlgorithmException e) {
throw new BadRequestException(Constant.NO_SUCH_ALGORITHM_EXCEPTION_TIPS,e);
}
}
public static PrivateKey generatePrivateKeyByKeyValue(String algorithm, KeySpec keySpec){
try {
return KeyFactory
.getInstance(algorithm, Constant.BOUNCY_CASTLE_PROVIDER)
.generatePrivate(keySpec);
} catch (InvalidKeySpecException e) {
throw new BadRequestException(Constant.INVALID_KEY_SPEC_EXCEPTION_TIPS,e);
} catch (NoSuchAlgorithmException e) {
throw new BadRequestException(Constant.NO_SUCH_ALGORITHM_EXCEPTION_TIPS,e);
}
}
/**
* 功能:获取 Cipher 实例
*
* @param transformations 转换信息
* @return {@link Cipher}
*/
private static Cipher getCipherInstance(String transformations) {
try {
return Cipher.getInstance(transformations, Constant.BOUNCY_CASTLE_PROVIDER);
} catch (NoSuchAlgorithmException e) {
throw new BadRequestException(Constant.NO_SUCH_ALGORITHM_EXCEPTION_TIPS, e);
} catch (NoSuchPaddingException e) {
throw new BadRequestException(Constant.NO_SUCH_PADDING_EXCEPTION_TIPS, e);
}
}
/**
* 功能:非对称加密
* @param publicKeyValue 公钥值
* @param keyAlgorithm 密钥算法
* @param message 待加密数据
* @return 密文
*/
public static byte[] asymmetricKeyEncrypt(byte[] publicKeyValue, String keyAlgorithm, byte[] message) {
PublicKey publicKey;
String algorithm;
if(Constant.EC_ALGORITHM.equalsIgnoreCase(keyAlgorithm)){
algorithm = Constant.SM2_ALGORITHM;
}else {
algorithm = keyAlgorithm;
}
if ((Constant.SM2_ALGORITHM.equalsIgnoreCase(algorithm))) {
final ECParameterSpec spec = ECNamedCurveTable.getParameterSpec(Constant.SM2_CURVES_NAME);
final ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(spec.getCurve().decodePoint(publicKeyValue), spec);
publicKey = generatePublicKeyByKeyValue(Constant.EC_ALGORITHM,ecPublicKeySpec);
} else if (Constant.RSA_ALGORITHM.equalsIgnoreCase(algorithm)) {
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKeyValue);
publicKey = generatePublicKeyByKeyValue(Constant.RSA_ALGORITHM,x509EncodedKeySpec);
}else {
throw new BadRequestException(String.format("not support algorithm %s",keyAlgorithm));
}
Cipher cipher = getCipherInstance(algorithm);
return encryptForAsymmetric(cipher, publicKey, message);
}
/**
* 功能:非对称解密
* @param privateKeyValue 公钥值
* @param keyAlgorithm 密钥算法
* @param cipherText 密文
* @return 明文
*/
public static byte[] asymmetricKeyDecrypt(byte[] privateKeyValue, String keyAlgorithm, byte[] cipherText) {
PrivateKey privateKey;
String algorithm;
if(Constant.EC_ALGORITHM.equalsIgnoreCase(keyAlgorithm)){
algorithm = Constant.SM2_ALGORITHM;
}else {
algorithm = keyAlgorithm;
}
if ((Constant.SM2_ALGORITHM.equalsIgnoreCase(algorithm))) {
final ECParameterSpec spec = ECNamedCurveTable.getParameterSpec(Constant.SM2_CURVES_NAME);
final ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(new BigInteger(1, privateKeyValue), spec);
privateKey = generatePrivateKeyByKeyValue(Constant.EC_ALGORITHM,ecPrivateKeySpec);
} else if (Constant.RSA_ALGORITHM.equalsIgnoreCase(algorithm)) {
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKeyValue);
privateKey = generatePrivateKeyByKeyValue(Constant.RSA_ALGORITHM,pkcs8EncodedKeySpec);
}else {
throw new BadRequestException(String.format("not support algorithm %s",keyAlgorithm));
}
Cipher cipher = getCipherInstance(algorithm);
return decryptForAsymmetric(cipher, privateKey, cipherText);
}
private static byte[] encryptForAsymmetric(Cipher cipher, Key key, byte[] message) {
try {
cipher.init(Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(message);
} catch (InvalidKeyException e) {
throw new BadRequestException(Constant.INVALID_KEY_EXCEPTION_TIPS, e);
} catch (BadPaddingException e) {
throw new BadRequestException(Constant.BAD_PADDING_EXCEPTION_TIPS, e);
} catch (IllegalBlockSizeException e) {
throw new BadRequestException(Constant.ILLEGAL_BLOCK_SIZE_EXCEPTION_TIPS, e);
}
}
private static byte[] decryptForAsymmetric(Cipher cipher, Key key, byte[] cipherText) {
try {
cipher.init(Cipher.DECRYPT_MODE, key);
return cipher.doFinal(cipherText);
} catch (InvalidKeyException e) {
throw new BadRequestException(Constant.INVALID_KEY_EXCEPTION_TIPS, e);
} catch (BadPaddingException e) {
throw new BadRequestException(Constant.BAD_PADDING_EXCEPTION_TIPS, e);
} catch (IllegalBlockSizeException e) {
throw new BadRequestException(Constant.ILLEGAL_BLOCK_SIZE_EXCEPTION_TIPS, e);
}
}
public static void main(String[] args) throws Exception {
// 敏感数据
String originalText = "Hello, SM2 and SM3!";
System.out.println("明文:"+ originalText);
// 发送方
System.out.println("=================发送方start====================");
// 1. 生成数字签名和签名公钥
// 生成SM2密钥对用于签名
KeyPair keyPair = generateSm2KeyPair();
PrivateKey signPrivateKey = keyPair.getPrivate();
PublicKey signPublicKey = keyPair.getPublic();
System.out.println("签名SM2私钥(base64):"+Base64.getEncoder().encodeToString(signPrivateKey.getEncoded()));
System.out.println("签名SM2公钥(base64):"+Base64.getEncoder().encodeToString(signPublicKey.getEncoded()));
// 生成数字签名
byte[] signature = signWithSm3(originalText, signPrivateKey);
// 2. 数字信封(生成数据密文+对称密钥密文)
// SM4对称加密
byte[] sm4Key = generateKey();
byte[] iv = generateIV();
String encryptedText = encryptWithSm4(originalText, sm4Key, iv);
// SM2加密SM4密钥(base64)
// String privateKey = "ANmPM+GwxdM7Yo/YhftxmfEqqjiHl9qKuMU55+lEfcEW";
// String publicKey = "BBQgpX+nrdNbyVmhQYlcED631eIYGKFj05+AoPMthp3pnum04Na8LG8sMul4Ro2W+DZaO/7XZvRievGY8DG2OoU=";
String privateKey = "bOoCZx5bcmoHDbJ+KKFqWBVLSBO7cCcV87cnJ1zCvs8=";
String publicKey = "BJIv8Ctzj0Xb11a1OoeYNTqlDpeMFU0WLL2+vM6JuWqGGO1AseL2wW3lryMKX8sBqUmtXII/7+0QLc3Hb1sWf/8=";
byte[] encryptionSm4Key = asymmetricKeyEncrypt(Base64.getDecoder().decode(publicKey), "SM2", sm4Key);
// 得到以下四个数据
System.out.println("数字签名:"+ Arrays.toString(signature));
System.out.println("签名公钥:"+ signPublicKey);
System.out.println("数据密文:"+ encryptedText);
System.out.println("SM4对称密钥密文:"+ Arrays.toString(encryptionSm4Key));
System.out.println("=================发送方end====================");
// 接收方
System.out.println("=================接收方start====================");
// 1. SM2解密获取SM4密钥
byte[] deserializeSm4Key = asymmetricKeyDecrypt(Base64.getDecoder().decode(privateKey), "SM2", encryptionSm4Key);
// 2. SM4解密
String decryptedText = decryptWithSm4(encryptedText, deserializeSm4Key, iv);
// 3. 验证签名
boolean isCorrect = verifyWithSm3(decryptedText, signature, signPublicKey);
System.out.println("验证签名:"+isCorrect);
System.out.println("解密结果:"+decryptedText);
System.out.println("=================接收方end====================");
}
}
Constant.java
package utils.crypto;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.util.Arrays;
import java.util.List;
public class Constant {
private Constant(){
throw new IllegalStateException("Utility class");
}
//commons
public static final BouncyCastleProvider BOUNCY_CASTLE_PROVIDER = new BouncyCastleProvider();
//curves
public static final String SM2_CURVES_NAME = "sm2p256v1";
//algorithm 算法
public static final String RSA_ALGORITHM = "RSA";
public static final String SM2_ALGORITHM = "SM2";
public static final String EC_ALGORITHM = "EC";
public static final String SM4_ALGORITHM = "SM4";
public static final String BC_ALGORITHM = "BC";
public static final String SIGN_ALGORITHM = "SM3withSM2";
public static final String UTF8_CHARSET = "UTF-8";
//需要确认一下具体使用哪种 hash 算法?
public static final List<String> SUPPORT_HASH_ALGORITHM = Arrays.asList("SM3","SHA256","SHA512");
//需要确认一下具体使用哪种 签名 算法?
public static final List<String> SUPPORT_SIGNATURE_ALGORITHM = Arrays.asList("SM3-SM2","NONE-RSA","SHA256-RSA","SHA512-RSA");
public static final List<String> PADDING = Arrays.asList("NoPadding","ISO10126Padding","PKCS5Padding");
public static final List<String> MODE= Arrays.asList("ECB","CBC");
// exception tips
public static final String NOT_BE_NULL_OR_EMPTY_TIPS =" %s can not be null or empty !";
public static final String NOT_BE_NULL_TIPS =" %s can not be null !";
public static final String NOT_BE_EMPTY_TIPS =" %s can not be empty !";
public static final String ONLY_SUPPORT_FOR_TIPS =" %s only support for %s ";
public static final String KEY_SIZE_NOT_BE_ZERO_AND_MULTIPLE_OF_8_TIPS ="keySize can not be zero and must be a multiple of 8";
public static final String NO_SUCH_ALGORITHM_EXCEPTION_TIPS ="No Such Algorithm Exception ";
public static final String NO_SUCH_PADDING_EXCEPTION_TIPS ="No Such Padding Exception ";
public static final String BAD_PADDING_EXCEPTION_TIPS ="Bad Padding Exception ";
public static final String SIGNATURE_EXCEPTION_TIPS ="Signature Exception ";
public static final String INVALID_KEY_EXCEPTION_TIPS ="Invalid CustomKey Exception ";
public static final String INVALID_KEY_SPEC_EXCEPTION_TIPS ="Invalid CustomKey Spec Exception ";
public static final String INVALID_ALGORITHM_PARAMETER_EXCEPTION_TIPS ="Invalid Algorithm Parameter Exception ";
public static final String ILLEGAL_BLOCK_SIZE_EXCEPTION_TIPS ="Illegal Block Size Exception ";
}
BadRequestException.java
package utils.crypto.exception;
public class BadRequestException extends BasicException{
private static final String CODE = "400";
public BadRequestException(String message){
super(CODE,message);
}
public BadRequestException(String message,Throwable cause){
super(cause,CODE,message);
}
public BadRequestException(String code, String message) {
super(code, message);
}
public BadRequestException(Throwable cause, String code, String message) {
super(cause, code, message);
}
}