SM2 加密工具和密钥对生成
SM2 国密SM2算法是中国国家密码管理局(CNCA)发布的一种非对称加密算法。它采用椭圆曲线密码体系(Elliptic Curve Cryptography,ECC)进行密钥交换、数字签名和公钥加密等操作。SM2算法和RSA算法都是公钥密码算法,SM2算法是一种更先进安全的算法,在我们国家商用密码体系中被用来替换RSA算法。
在本文中,我们将探讨两个用于 SM2 加密的实用工具:Sm2Utils 和Sm2KeyPairUtil。这两个工具可以帮助您生成 SM2 加密密钥对、使用 SM2 算法进行加密和解密。
1. SM2 简介
SM2 国密SM2算法是中国国家密码管理局(CNCA)发布的一种非对称加密算法。它采用椭圆曲线密码体系(Elliptic Curve Cryptography,ECC)进行密钥交换、数字签名和公钥加密等操作。SM2算法和RSA算法都是公钥密码算法,SM2算法是一种更先进安全的算法,在我们国家商用密码体系中被用来替换RSA算法。
2. 添加BouncyCastle依赖
使用BouncyCastle库进行加解密操作,如果您使用Maven,则需要将以下依赖项添加到pom.xml文件中:
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.57</version>
</dependency>
3. SM2 密钥对生成
首先,我们来看一下 Sm2KeyPairUtil 类,它负责生成 SM2 密钥对。
import org.bouncycastle.jce.interfaces.ECPrivateKey;
import org.bouncycastle.jce.interfaces.ECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
/**
* author ks
*
* @version v0.9.0
* @Package : com.xxxx.encrypt.utils.sm2
* @Description : A utility class for generating and handling SM2 key pairs.
* @Create on : 2024/6/20 16:03
**/
public class Sm2KeyPairUtil {
static {
// Add BouncyCastle provider to the Security framework
Security.addProvider(new BouncyCastleProvider());
}
/**
* Generates an SM2 key pair.
*
* @return KeyPair containing SM2 public and private keys
*/
public static KeyPair generateSM2KeyPair() {
try {
// Specify the SM2 algorithm parameter set
ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");
// Initialize the KeyPairGenerator with the SM2 algorithm and BouncyCastle provider
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC", "BC");
keyPairGenerator.initialize(sm2Spec, new SecureRandom());
// Generate and return the key pair
return keyPairGenerator.generateKeyPair();
} catch (Exception e) {
// Print the stack trace if an exception occurs
e.printStackTrace();
return null;
}
}
/**
* Converts the SM2 public key to a hexadecimal string.
*
* @param keyPair The SM2 KeyPair containing the public key
* @return Hexadecimal string representation of the SM2 public key
*/
public static String getSM2PublicKeyHex(KeyPair keyPair) {
if (keyPair == null || keyPair.getPublic() == null) {
return null;
}
// Get the public key from the key pair and extract its ECPoint
ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();
ECPoint q = publicKey.getQ();
// Encode the ECPoint to a byte array and convert it to a hexadecimal string
byte[] publicKeyBytes = q.getEncoded(false);
return Hex.toHexString(publicKeyBytes);
}
/**
* Converts the SM2 private key to a hexadecimal string.
*
* @param keyPair The SM2 KeyPair containing the private key
* @return Hexadecimal string representation of the SM2 private key
*/
public static String getSM2PrivateKeyHex(KeyPair keyPair) {
if (keyPair == null || keyPair.getPrivate() == null) {
return null;
}
// Get the private key from the key pair and convert its D value to a hexadecimal string
ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate();
return privateKey.getD().toString(16);
}
public static void main(String[] args) {
// Generate an SM2 key pair
KeyPair keyPair = generateSM2KeyPair();
// Get the hexadecimal string representations of the public and private keys
String sm2PublicKeyHex = getSM2PublicKeyHex(keyPair);
String sm2PrivateKeyHex = getSM2PrivateKeyHex(keyPair);
// Print the keys
System.out.println("SM2 Public Key (Hex): " + sm2PublicKeyHex);
System.out.println("SM2 Private Key (Hex): " + sm2PrivateKeyHex);
}
}
说明
- generateSM2KeyPair: 使用 BouncyCastle 提供程序生成 SM2 密钥对
- getSM2PublicKeyHex: 将公钥转换为十六进制字符串
- getSM2PrivateKeyHex: 将私钥转换为十六进制字符串
4. SM2 加密工具类
接下来,我们将探讨 Sm2Utils 类,它提供了使用 SM2 密钥进行数据加密和解密的方法。
package com.taryartar.encrypt.utils.sm2;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import org.bouncycastle.util.encoders.Hex;
import java.security.Security;
import java.security.spec.InvalidKeySpecException;
/**
* author ks
*
* @version v0.9.0
* @Package : com.xxxx.encrypt.utils.sm2
* @Description : Utility class for SM2 encryption and decryption.
* @Create on : 2024/6/21 00:23
**/
public class Sm2Util {
private static final X9ECParameters X9_EC_PARAMETERS = GMNamedCurves.getByName("sm2p256v1");
private static final ECParameterSpec EC_DOMAIN_PARAMETERS = new ECParameterSpec(X9_EC_PARAMETERS.getCurve(), X9_EC_PARAMETERS.getG(), X9_EC_PARAMETERS.getN());
static {
Security.addProvider(new BouncyCastleProvider());
}
/**
* Encrypts data using an SM2 public key.
*
* @param publicKeyHex SM2 public key in hexadecimal format
* @param plainText Data to be encrypted
* @return Encrypted data
*/
public static String encrypt(String publicKeyHex, String plainText) throws InvalidCipherTextException, InvalidKeySpecException {
ECPublicKey publicKey = convertHexToPublicKey(publicKeyHex);
ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(publicKey.getQ(), new ECDomainParameters(publicKey.getParameters().getCurve(), publicKey.getParameters().getG(), publicKey.getParameters().getN()));
SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
sm2Engine.init(true, new ParametersWithRandom(publicKeyParameters, new SecureRandom()));
byte[] plainBytes = plainText.getBytes();
byte[] cipherBytes = sm2Engine.processBlock(plainBytes, 0, plainBytes.length);
return Hex.toHexString(cipherBytes);
}
/**
* Decrypts data using an SM2 private key.
*
* @param privateKeyHex SM2 private key in hexadecimal format
* @param cipherText Data to be decrypted
* @return Decrypted data
*/
public static String decrypt(String privateKeyHex, String cipherText) throws InvalidKeySpecException {
BCECPrivateKey privateKey = convertHexToPrivateKey(privateKeyHex);
ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(privateKey.getD(), new ECDomainParameters(privateKey.getParameters().getCurve(), privateKey.getParameters().getG(), privateKey.getParameters().getN()));
SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
sm2Engine.init(false, privateKeyParameters);
try {
byte[] cipherBytes = Hex.decode(cipherText);
byte[] plainBytes = sm2Engine.processBlock(cipherBytes, 0, cipherBytes.length);
return new String(plainBytes);
} catch (Exception e) {
System.err.println("Error occurred during SM2 decryption: " + e.getMessage());
e.printStackTrace();
return null;
}
}
/**
* Converts a hexadecimal string to a BCECPublicKey.
*
* @param publicKeyHex Public key in hexadecimal format
* @return BCECPublicKey instance
*/
private static BCECPublicKey convertHexToPublicKey(String publicKeyHex) throws InvalidKeySpecException {
if (publicKeyHex.length() > 128) {
publicKeyHex = publicKeyHex.substring(publicKeyHex.length() - 128);
}
String xCoord = publicKeyHex.substring(0, 64);
String yCoord = publicKeyHex.substring(64);
BigInteger x = new BigInteger(xCoord, 16);
BigInteger y = new BigInteger(yCoord, 16);
ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(X9_EC_PARAMETERS.getCurve().createPoint(x, y), EC_DOMAIN_PARAMETERS);
return new BCECPublicKey("EC", publicKeySpec, BouncyCastleProvider.CONFIGURATION);
}
/**
* Converts a hexadecimal string to a BCECPrivateKey.
*
* @param privateKeyHex Private key in hexadecimal format
* @return BCECPrivateKey instance
*/
private static BCECPrivateKey convertHexToPrivateKey(String privateKeyHex) throws InvalidKeySpecException {
BigInteger d = new BigInteger(privateKeyHex, 16);
ECPrivateKeySpec privateKeySpec = new ECPrivateKeySpec(d, EC_DOMAIN_PARAMETERS);
return new BCECPrivateKey("EC", privateKeySpec, BouncyCastleProvider.CONFIGURATION);
}
/**
* Sign data with a private key using SM2 and SM3 algorithms.
*
* @param data The data to be signed.
* @param privateKey The private key used for signing.
* @return The signature bytes.
* @throws Exception If an error occurs during the signing process.
*/
public static byte[] signWithPrivateKey(byte[] data, PrivateKey privateKey) throws Exception {
Signature signature = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), BouncyCastleProvider.PROVIDER_NAME);
signature.initSign(privateKey);
signature.update(data);
return signature.sign();
}
/**
* Verify the signature of the data using a public key with SM2 and SM3 algorithms.
*
* @param data The data whose signature needs to be verified.
* @param publicKey The public key used for verification.
* @param signature The signature bytes to be verified.
* @return True if the signature is valid, false otherwise.
* @throws Exception If an error occurs during the verification process.
*/
public static boolean verifyWithPublicKey(byte[] data, PublicKey publicKey, byte[] signature) throws Exception {
Signature signature1 = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), BouncyCastleProvider.PROVIDER_NAME);
signature1.initVerify(publicKey);
signature1.update(data);
return signature1.verify(signature);
}
public static void main(String[] args) throws Exception {
String publicKey = "04e7c3a338ed9ac8fb0d807bc8dbba3886daf6f7d09143452d3e624d54ad5c3c249408a7ad67caabf4221550f07d6563a1deef1fde2fb88dfe9ae7a2e78e27a693";
String privateKey = "f6cfd33d9376aacf40edfae52a7e5b4b72a94cafc68089bb26cfe7e2b7817cc2";
String plainText = "Hello, World!";
String encryptedText = encrypt(publicKey, plainText);
System.out.println("Encrypted Data: " + encryptedText);
String decryptedText = decrypt(privateKey, "32e212000dab17ef2c4fdebeab603cebb36c5d6e11b08771078a9b0190a9d81f41ac8d61b976e0abcb0c9f1fa570b478f6c0faf51989d52fc9b3b64796a74efd60fdce3379e18e087fedf3647ea638d7c1c0e3608060be63d163b4f0d1c5b91fce8ba5aa26c0230af592b101c21e7c83b225f19fbf7397c71235869282");
System.out.println("Decrypted Data: " + decryptedText);
// Sign the data
byte[] signature = Sm2Utils.signWithPrivateKey(plainText.getBytes(StandardCharsets.UTF_8), (PrivateKey) convertHexToPrivateKey(privateKey));
System.out.println("signature Bytes (Hex): " + Hex.toHexString(signature));
// Verify the signature
boolean isValid = Sm2Utils.verifyWithPublicKey(plainText.getBytes(StandardCharsets.UTF_8), (PublicKey) convertHexToPublicKey(publicKey), signature);
// Print the signature for debugging
System.out.println("Signature: " + Base64.getEncoder().encodeToString(signature));
System.out.println("Signature verify result: " + isValid);
// Modify the data slightly
byte[] modifiedData = "Hello, this is a tampered message.".getBytes();
// Verify the signature with the modified data
boolean isInvalid = Sm2Utils.verifyWithPublicKey(modifiedData, (PublicKey) convertHexToPublicKey(publicKey), signature);
System.out.println("Signature verify result: " + isInvalid);
}
}
说明
- encrypt: 使用十六进制格式的 SM2 公钥加密数据
- decrypt: 使用十六进制格式的 SM2 私钥解密数据
- convertHexToPublicKey: 将十六进制字符串转换为 BCECPublicKey
- convertHexToPrivateKey: 将十六进制字符串转换为 BCECPrivateKey
- signWithPrivateKey: 使用带有SM2和SM3算法的私钥进行数据进行签名
- verifyWithPublicKey: 使用带有SM2和SM3算法的公钥验证数据的签名
5. 结论
通过使用 Sm2KeyPairUtil,你可以轻松生成 SM2 密钥对,并且通过 Sm2Util,您可以使用 SM2 密钥进行数据加密和解密。这些实用工具简化了使用 SM2 算法的过程。
6. 问题集
- 前端采用sm-crypto进行加解密,需要仔细检查补码“04”的问题。
例如:
后端加密字符串:
0432e212000dab17ef2c4fdebeab603cebb36c5d6e11b08771078a9b0190a9d81f41ac8d61b976e0abcb0c9f1fa570b478f6c0faf51989d52fc9b3b64796a74efd60fdce3379e18e087fedf3647ea638d7c1c0e3608060be63d163b4f0d1c5b91fce8ba5aa26c0230af592b101c21e7c83b225f19fbf7397c71235869282
前端解密字符串:
32e212000dab17ef2c4fdebeab603cebb36c5d6e11b08771078a9b0190a9d81f41ac8d61b976e0abcb0c9f1fa570b478f6c0faf51989d52fc9b3b64796a74efd60fdce3379e18e087fedf3647ea638d7c1c0e3608060be63d163b4f0d1c5b91fce8ba5aa26c0230af592b101c21e7c83b225f19fbf7397c71235869282
备注:解密时需要把16进制前端的04取消掉,按照如下字符串进行解密:
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)