SM2加签、验签,加密、解密
SM2加签、验签,加密、解密
一、前言
简介
SM2加密方式是一种基于椭圆曲线密码学(ECC)的非对称加密算法,由中国国家密码管理局发布,主要用于保障数据在传输和存储过程中的安全。SM2算法的核心是椭圆曲线方程y^2
= x^3 + ax + b,通过点的倍增操作生成公钥和私钥。私钥是一个秘密整数,公钥由私钥乘以基点得到。SM2算法的安全性基于椭圆曲线离散对数问题,即使知道公钥也无法轻易推导出私钥。
工作原理
SM2算法的工作原理基于椭圆曲线的数学特性。首先,选择一个合适的椭圆曲线和一个基点,然后用户随机生成一个私钥,使用私钥与基点进行标量乘法运算得到公钥。公钥和私钥一一对应,用于加密、解密和数字签名验证。
加密和解密过程
SM2算法本身主要用于数字签名和密钥交换,但它可以与其他对称加密算法结合使用进行数据的加密和解密。在加密过程中,使用共享密钥对数据进行对称加密;在解密过程中,使用相同的共享密钥对加密后的数据进行解密。
安全性
SM2算法的安全性基于椭圆曲线离散对数问题,其破译或求解难度基本上是指数级的,因此安全性较高。SM2推荐使用256位密钥长度,其加密强度等同于3072位RSA证书,而业界普遍采用的RSA证书通常为2048位。此外,SM2在签名和密钥交换方面采取了更为安全的机制,不同于国际标准的ECDSA和ECDH。
应用场景
SM2算法广泛应用于数据加密、数字签名、密钥交换等场景,特别适用于需要高安全性和高效性的场合,如金融、政府和物联网等领域。在实际应用中,SM2可以用于加密敏感数据,确保只有拥有相应私钥的接收方可以解密和访问原始数据。
二、业务流程
三、使用示例
2.1 引入包
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.78.1</version>
</dependency>
2.2 java工具类
public class EnAndDecrypt {
private static final Logger log = LoggerFactory.getLogger(EnAndDecrypt.class);
//生成的私钥
static String privateKey = "1e58de0096c2fbcf7d251d63eb3432d2648b21489fd53d12d8878d55653d62fe";
//生成的公钥
static String publicKey = "046fbfab13134b3360493dfcbc090bf9d1094af8d95692331b3a60adda116ead29f323285e556083aae490a2d58130daf12ae7ca2a39732ada84f2c100ac47d4fa";
//固定写法
private static final X9ECParameters curveParams = GMNamedCurves.getByName("sm2p256v1");
private static final ECDomainParameters domainParams = new ECDomainParameters(curveParams
.getCurve(), curveParams.getG(), curveParams.getN(), curveParams.getH());
public static void main(String[] args) throws Exception {
//生成密钥对(私钥、公钥)
generateKeyPair();
//私钥加签
privateSign();
//验签
verifyData();
//加密
encrypt();
//解密
decryptData();
}
public static void encrypt()
throws Exception
{
LineDTO line = LineDTO.builder()
.type(Integer.valueOf(1))
.startRegionCode("444444")
.startRegionName("成都")
.endRegionCode("454444")
.endRegionName("巴中")
.lineId(UUID.randomUUID().toString().replaceAll("-", ""))
.lineName("成巴")
.lineType(Integer.valueOf(2))
.lineLevel(Integer.valueOf(2))
.firstStationId(UUID.randomUUID().toString().replaceAll("-", ""))
.firstStationName("成都南站")
.lastStationId(UUID.randomUUID().toString().replaceAll("-", ""))
.lastStationName("巴中北站")
.lineStatus(Integer.valueOf(1))
.build();
String bizContent = encrypt(JSON.toJSONString(Arrays.asList(new LineDTO[] { line })));
log.info("加密后密文:{}", bizContent);
}
public static String encrypt(String str) throws Exception {
return encrypt(publicKey, str);
}
public static String encrypt(String publicKeyHex, String plainText) throws Exception {
byte[] publicKeyBytes = Hex.decode(publicKeyHex);
byte[] textBytes = plainText.getBytes(StandardCharsets.UTF_8);
ECPoint point = domainParams.getCurve().decodePoint(publicKeyBytes);
ECPublicKeyParameters publicKeyParams = new ECPublicKeyParameters(point, domainParams);
SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
sm2Engine.init(true, new ParametersWithRandom(publicKeyParams, new SecureRandom()));
byte[] encryptedData = sm2Engine.processBlock(textBytes, 0, textBytes.length);
return Hex.toHexString(encryptedData);
}
public static void privateSign() throws Exception {
String bizContent = "";
CommonDTO dto = CommonDTO.builder()
.Interface("dzky_line")
.encryType("SM2")
.timestamp(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
.format(new Date()))
.bizContent(bizContent)
.build();
String sign = privateSign(dto.toSignText());
System.out.println("私钥签名后的结果:" + sign);
}
public static String privateSign(String str) throws Exception {
return sign(str, privateKey);
}
public static String sign(String plainText, String privateKeyHex) throws Exception {
System.out.println("签名的原字符串:" + plainText);
byte[] textBytes = plainText.getBytes(StandardCharsets.UTF_8);
BigInteger d = new BigInteger(privateKeyHex, 16);
ECPrivateKeyParameters privateKeyParams = new ECPrivateKeyParameters(d, domainParams);
SM2Signer signer = new SM2Signer();
signer.init(true, privateKeyParams);
signer.update(textBytes, 0, textBytes.length);
byte[] sign = signer.generateSignature();
return Hex.toHexString(sign);
}
public static Map<String, String> generateKeyPair() {
ECKeyPairGenerator generator = new ECKeyPairGenerator();
ECKeyGenerationParameters params = new ECKeyGenerationParameters(domainParams, new SecureRandom());
generator.init(params);
AsymmetricCipherKeyPair keyPair = generator.generateKeyPair();
ECPrivateKeyParameters privateKey = (ECPrivateKeyParameters)keyPair.getPrivate();
ECPublicKeyParameters publicKey = (ECPublicKeyParameters)keyPair.getPublic();
String privateKeyHex = privateKey.getD().toString(16);
String publicKeyHex = Hex.toHexString(publicKey.getQ().getEncoded(false));
Map map = new HashMap();
map.put("publicKey", publicKeyHex);
map.put("privateKey", privateKeyHex);
System.out.println("私钥:" + privateKeyHex);
System.out.println("公钥:" + publicKeyHex);
return map;
}
public static void decryptData() throws Exception
{
String decrypt = decryptData("0473b120ad1a22b99a4169c1515eba4e6648880a0c72f667b0177ddb2ffe6e9a3b8388782c944bb7b5360ea659cb383ced3c3704de8f834644211a7e829420d5de2474895eea30d7e254d20d6072d04133cd522a568c9e4430bf7c605343d376ffc095aa15570eedcc7b68ec3abb70c9f90b04ce53452abe7e407a9425b94a04c4ec2eb743d5e06691581b4e9f97f3b74753cfc714db349ae8b6df2f3c13aa10052c8e84e987324e499c60c5e0ab49dccc219d8e62a84199f8e8fd3f1a51d812f8440f934ffa83ef9d1dc0f82d7572f5d2f95a1e68c6777870c7fba088df767195a4d37e1956b78407cc4869b9c40ea5b354e284bfd1c52f6e377f5ca680184f9c4149bef18a840c0223401d3183ec6632f27d8030c5cac8fec44df39b598948116384d11c22340efda462f82ff4b52976766fb98362fa31023d6fef7c913c6dff09bd4f882dbb8607f651d5a18fa7256e6ae37f17d095e3ee733cf4cf7f3d5b1206d2a0f93f0f6417d4b921d5ee9ceb772c4e60bee74a1d624536f8a467bf1d3e4fcd93c5b6bd1980ce8a0f932bd6d63f3aec4eade7fdd6af5214ea62c0af95acaa54bc5115020ad8ff7cc09a68fc60a590fd0bbb510ec022b2844bef03f93e9138a1a9ccf581f2f6796935d6a9931620b9e5828f9c8b4f6102ccfdd3d53488ae4f84245e365d1395");
System.out.println("解密:" + decrypt);
}
public static String decryptData(String str) throws Exception {
String decrypt = decrypt(privateKey, str);
return decrypt;
}
public static String decrypt(String privateKeyHex, String encryptedHex) throws Exception {
byte[] encryptedData = Hex.decode(encryptedHex);
BigInteger d = new BigInteger(privateKeyHex, 16);
ECPrivateKeyParameters privateKeyParams = new ECPrivateKeyParameters(d, domainParams);
SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
sm2Engine.init(false, privateKeyParams);
byte[] decryptedData = sm2Engine.processBlock(encryptedData, 0, encryptedData.length);
return new String(decryptedData, StandardCharsets.UTF_8);
}
public static boolean verifyData() throws Exception
{
boolean verify = verifyData("ak=e511ff01-4737-4edf-b439-568c1b8c538e&encryType=SM2&interface=dzky_line×tamp=2025-01-24 13:48:34", "3045022100d0c62b30c5da211989da8b2a4eb7b7f2d08aee895fd01e1751e0fe1fd9b4fb1702202f5e1f11fd7cae454f21e59382223744485f75152c9ce77eea9a18f7015bb80b");
System.out.println("验签结果:" + verify);
return verify;
}
public static boolean verifyData(String str, String enSign) throws Exception
{
return verify(str, enSign, publicKey);
}
public static boolean verify(String plainText, String signHex, String publicKeyHex)
throws Exception
{
byte[] signature = Hex.decode(signHex);
byte[] publicKeyBytes = Hex.decode(publicKeyHex);
byte[] textBytes = plainText.getBytes(StandardCharsets.UTF_8);
ECPoint point = domainParams.getCurve().decodePoint(publicKeyBytes);
ECPublicKeyParameters publicKeyParams = new ECPublicKeyParameters(point, domainParams);
SM2Signer signer = new SM2Signer();
signer.init(false, publicKeyParams);
signer.update(textBytes, 0, textBytes.length);
return signer.verifySignature(signature);
}
}
2.3 工具dto
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class CommonDTO {
private String ak;
private String Interface;
private String encryType;
private String sign;
private String timestamp;
private String bizContent;
public String toSignText()
{
StringBuilder sb = new StringBuilder();
boolean firstParam = true;
if (StringUtils.isNotBlank(this.ak)) {
sb.append("ak=").append(this.ak);
firstParam = false;
}
if (StringUtils.isNotBlank(this.bizContent)) {
if (!firstParam) {
sb.append("&");
}
sb.append("bizContent=").append(this.bizContent);
firstParam = false;
}
if (StringUtils.isNotBlank(this.encryType)) {
if (!firstParam) {
sb.append("&");
}
sb.append("encryType=").append(this.encryType);
firstParam = false;
}
if (StringUtils.isNotBlank(this.Interface)) {
if (!firstParam) {
sb.append("&");
}
sb.append("interface=").append(this.Interface);
firstParam = false;
}
if (StringUtils.isNotBlank(this.timestamp)) {
if (!firstParam) {
sb.append("&");
}
sb.append("timestamp=").append(this.timestamp);
}
return sb.toString();
}
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class LineDTO {
private Integer type;
private String orgcode;
private String orgname;
private String startRegionCode;
private String startRegionName;
private String endRegionCode;
private String endRegionName;
private String lineId;
private String lineName;
private Integer lineType;
private Integer lineLevel;
private String firstStationId;
private String firstStationName;
private String lastStationId;
private String lastStationName;
private Integer lineStatus;
private Double mileage;
private Double hghwaymile;
private String lineDescription;
private List<Integer> otaNames;
private Integer sendSMS;
private Object reserved;
}