国密SM2签名完整流程
20240617更新:经李刚提醒,上一版代码使用openssl3有问题,本地尝试复现,发现是openssl3生成公钥时出现问题,导致公钥文件内没有内容。现增加openssl3生成公钥的命令,同时修改代码中的read方法,不再需要删除公私钥文件的起止行。
使用openssl(1.1.1版本以上)生成sm2公私钥
shell
#生成私钥,私钥默认是pem文件格式
openssl ecparam -genkey -name SM2 -noout -out pri.pem
#将pem文件格式的私钥,转成java可解析的pkcs8格式
openssl pkcs8 -topk8 -inform PEM -in pri.pem -nocrypt -out pri_pkcs8.pem
#生成公钥
openssl ec -in pri_pkcs8.pem -pubout -out pub.pem
使用openssl3生成公私钥
shell
# 生成私钥、私钥转格式的命令不变,只有生成公钥命令有变化
openssl pkey -in pri_pkcs8.pem -pubout -out pub.pem
使用Java程序解析公私钥
maven
<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk18on -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.78.1</version>
</dependency>
Java
import org.apache.tomcat.util.codec.binary.Base64;
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 java.io.*;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
public class SM2 {
private static X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");
private static ECDomainParameters ecDomainParameters = new ECDomainParameters(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN());
private final static BouncyCastleProvider bc = new BouncyCastleProvider();
private final static String KEY_ALGORITHM = "EC";
private final static String SIGNATURE_ALGORITHM = "SM3withSm2";
public static String sign(byte[] data, String privateKeyStr) throws Exception {
byte[] keyBytes = Base64.decodeBase64(privateKeyStr);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM, bc);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
PrivateKey priKey = keyFactory.generatePrivate(pkcs8KeySpec);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM, bc);
signature.initSign(priKey);
signature.update(data);
return Base64.encodeBase64String(signature.sign());
}
public static boolean verify(byte[] data, String publicKeyStr, String sign) throws Exception {
byte[] keyBytes = Base64.decodeBase64(publicKeyStr);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM, bc);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
PublicKey pubKey = keyFactory.generatePublic(keySpec);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM, bc);
signature.initVerify(pubKey);
signature.update(data);
return signature.verify(Base64.decodeBase64(sign));
}
/**
* c1||c3||c2 私钥解密
*
* @param data
* @param privateKeyStr
* @return
* @throws Exception
*/ public static byte[] decryptByPrivateKey(byte[] data, String privateKeyStr) throws Exception {
return sm2DecryptOld(changeC1C3C2ToC1C2C3(data), privateKeyStr);
}
/**
* c1||c3||c2 公钥加密
*
* @param data
* @param publicKeyStr
* @return
* @throws Exception
*/ public static byte[] encryptByPublicKey(byte[] data, String publicKeyStr) throws Exception {
return changeC1C2C3ToC1C3C2(sm2EncryptOld(data, publicKeyStr));
}
/**
* bc加解密使用旧标c1||c2||c3,此方法在加密后调用,将结果转化为c1||c3||c2
* * @param c1c2c3
* @return
*/
private static byte[] changeC1C2C3ToC1C3C2(byte[] c1c2c3) {
final int c1Len = (x9ECParameters.getCurve().getFieldSize() + 7) / 8 * 2 + 1;
final int c3Len = 32;
byte[] result = new byte[c1c2c3.length];
System.arraycopy(c1c2c3, 0, result, 0, c1Len);
System.arraycopy(c1c2c3, c1c2c3.length - c3Len, result, c1Len, c3Len);
System.arraycopy(c1c2c3, c1Len, result, c1Len + c3Len, c1c2c3.length - c1Len - c3Len);
return result;
}
/**
* bc加解密使用旧标c1||c2||c3,此方法在解密前调用,将c1||c3||c2密文转化为c1||c2||c3再去解密
*
* @param c1c3c2
* @return
*/
private static byte[] changeC1C3C2ToC1C2C3(byte[] c1c3c2) {
final int c1Len = (x9ECParameters.getCurve().getFieldSize() + 7) / 8 * 2 + 1;
final int c3Len = 32;
byte[] result = new byte[c1c3c2.length];
System.arraycopy(c1c3c2, 0, result, 0, c1Len);
System.arraycopy(c1c3c2, c1Len + c3Len, result, c1Len, c1c3c2.length - c1Len - c3Len);
System.arraycopy(c1c3c2, c1Len, result, c1c3c2.length - c3Len, c3Len);
return result;
}
/**
* c1||c2||c3 * * @param data
* @param publicKeyStr
* @return
* @throws Exception
*/ private static byte[] sm2EncryptOld(byte[] data, String publicKeyStr) throws Exception {
byte[] keyBytes = Base64.decodeBase64(publicKeyStr);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM, bc);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
PublicKey pubKey = keyFactory.generatePublic(keySpec);
BCECPublicKey localECPublicKey = (BCECPublicKey) pubKey;
ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(localECPublicKey.getQ(), ecDomainParameters);
SM2Engine sm2Engine = new SM2Engine();
sm2Engine.init(true, new ParametersWithRandom(ecPublicKeyParameters, new SecureRandom()));
return sm2Engine.processBlock(data, 0, data.length);
}
/**
* c1||c2||c3 * * @param data
* @param privateKeyStr
* @return
* @throws Exception
*/ private static byte[] sm2DecryptOld(byte[] data, String privateKeyStr) throws Exception {
byte[] keyBytes = Base64.decodeBase64(privateKeyStr);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM, bc);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
PrivateKey priKey = keyFactory.generatePrivate(pkcs8KeySpec);
BCECPrivateKey localECPrivateKey = (BCECPrivateKey) priKey;
ECPrivateKeyParameters ecPrivateKeyParameters = new ECPrivateKeyParameters(localECPrivateKey.getD(), ecDomainParameters);
SM2Engine sm2Engine = new SM2Engine();
sm2Engine.init(false, ecPrivateKeyParameters);
return sm2Engine.processBlock(data, 0, data.length);
}
public static String read(String filePath) throws IOException {
StringBuilder fileContentSb = new StringBuilder();
try (LineNumberReader lnr = new LineNumberReader(new FileReader(filePath));) {
String thisLine = "";
while ((thisLine = lnr.readLine()) != null) {
if (thisLine.startsWith("----")) {
continue;
}
fileContentSb.append(thisLine);
}
}
return fileContentSb.toString();
}
public static void main(String[] args) throws Exception {
String priPath = "/Users/sun/Desktop/sm2/pri_pkcs8.pem";
String pubPath = "/Users/sun/Desktop/sm2/pub.pem";
String data = "sunxiaolong";
String sign = sign(data.getBytes(), read(priPath));
boolean verify = verify(data.getBytes(), read(pubPath), sign);
System.out.println(verify);
byte[] encryptByPublicKey = encryptByPublicKey(data.getBytes(), read(pubPath));
byte[] decryptByPrivateKey = decryptByPrivateKey(encryptByPublicKey, read(priPath));
System.out.println(new String(decryptByPrivateKey));
}
}
https://www.gushiciku.cn/pl/pe8y
https://github.com/openssl/openssl/issues/18707