国密改造已经持续了很长时间了,相信很多从事金融科技类的程序猿都遇到过这个需求。这篇文章就为大家带来笔者对于国密改造的一些经验,主要是代码层面,有兴趣的同学可以研究下国密的算法模型!
注:本文所用到的工具类并非笔者所写!
目录
一、国密简述
二、依赖准备
三、SM2算法应用
1、生成SM2公私钥
工具类
测试Demo
2、数据加解密
国密——国家密码局制定的国家密码算法。主要包含SM1、SM2、SM3、SM4几种方式。
SM1:对称加密,且算法不公开,使用硬件加密,本文不做叙述;
SM2:非对称加密,签名以及生成秘钥速度优于RSA,基于ECC算法,运算效率更高,且更安全;
SM3:摘要,国产杂凑算法,生成长度为256比特,优于MD5以及SHA-1算法;
SM4: 无线局域网标准的分组数据算法。对称加密,密钥长度和分组长度均为128位;
生成SM2 公钥是130 位 前面多了04两个标识符,注意区分!
国密主要用到下面的包
org.bouncycastle
bcpkix-jdk15on
1.57
一定注意版本。实际项目中笔者发现项目其他子工程用到1.56版本的包,所以选择了低版本。
package cn.test.encrypt.utils.sm2;
import cn.test.encrypt.utils.Util;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.math.ec.ECPoint;
import java.math.BigInteger;
public class Cipher {
private int ct;
private ECPoint p2;
private SM3Digest sm3keybase;
private SM3Digest sm3c3;
private byte key[];
private byte keyOff;
public Cipher()
{
this.ct = 1;
this.key = new byte[32];
this.keyOff = 0;
}
private void Reset()
{
this.sm3keybase = new SM3Digest();
this.sm3c3 = new SM3Digest();
byte p[] = Util.byteConvert32Bytes(p2.getX().toBigInteger());
this.sm3keybase.update(p, 0, p.length);
this.sm3c3.update(p, 0, p.length);
p = Util.byteConvert32Bytes(p2.getY().toBigInteger());
this.sm3keybase.update(p, 0, p.length);
this.ct = 1;
NextKey();
}
private void NextKey()
{
SM3Digest sm3keycur = new SM3Digest(this.sm3keybase);
sm3keycur.update((byte) (ct >> 24 & 0xff));
sm3keycur.update((byte) (ct >> 16 & 0xff));
sm3keycur.update((byte) (ct >> 8 & 0xff));
sm3keycur.update((byte) (ct & 0xff));
sm3keycur.doFinal(key, 0);
this.keyOff = 0;
this.ct++;
}
public ECPoint Init_enc(SM2 sm2, ECPoint userKey)
{
AsymmetricCipherKeyPair key = sm2.ecc_key_pair_generator.generateKeyPair();
ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters) key.getPrivate();
ECPublicKeyParameters ecpub = (ECPublicKeyParameters) key.getPublic();
BigInteger k = ecpriv.getD();
ECPoint c1 = ecpub.getQ();
this.p2 = userKey.multiply(k);
Reset();
return c1;
}
public void Encrypt(byte data[])
{
this.sm3c3.update(data, 0, data.length);
for (int i = 0; i < data.length; i++)
{
if (keyOff == key.length)
{
NextKey();
}
data[i] ^= key[keyOff++];
}
}
public void Init_dec(BigInteger userD, ECPoint c1)
{
this.p2 = c1.multiply(userD);
Reset();
}
public void Decrypt(byte data[])
{
for (int i = 0; i < data.length; i++)
{
if (keyOff == key.length)
{
NextKey();
}
data[i] ^= key[keyOff++];
}
this.sm3c3.update(data, 0, data.length);
}
public void Dofinal(byte c3[])
{
byte p[] = Util.byteConvert32Bytes(p2.getY().toBigInteger());
this.sm3c3.update(p, 0, p.length);
this.sm3c3.doFinal(c3, 0);
Reset();
}
}
package cn.test.encrypt.utils.sm2;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECFieldElement;
import org.bouncycastle.math.ec.ECFieldElement.Fp;
import org.bouncycastle.math.ec.ECPoint;
import java.math.BigInteger;
import java.security.SecureRandom;
public class SM2 {
//国密参数
public static String[] ecc_param = {
"FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF",
"FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC",
"28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93",
"FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123",
"32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7",
"BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0"
};
public static SM2 Instance()
{
return new SM2();
}
public final BigInteger ecc_p;
public final BigInteger ecc_a;
public final BigInteger ecc_b;
public final BigInteger ecc_n;
public final BigInteger ecc_gx;
public final BigInteger ecc_gy;
public final ECCurve ecc_curve;
public final ECPoint ecc_point_g;
public final ECDomainParameters ecc_bc_spec;
public final ECKeyPairGenerator ecc_key_pair_generator;
public final ECFieldElement ecc_gx_fieldelement;
public final ECFieldElement ecc_gy_fieldelement;
public SM2()
{
this.ecc_p = new BigInteger(ecc_param[0], 16);
this.ecc_a = new BigInteger(ecc_param[1], 16);
this.ecc_b = new BigInteger(ecc_param[2], 16);
this.ecc_n = new BigInteger(ecc_param[3], 16);
this.ecc_gx = new BigInteger(ecc_param[4], 16);
this.ecc_gy = new BigInteger(ecc_param[5], 16);
this.ecc_gx_fieldelement = new Fp(this.ecc_p, this.ecc_gx);
this.ecc_gy_fieldelement = new Fp(this.ecc_p, this.ecc_gy);
this.ecc_curve = new ECCurve.Fp(this.ecc_p, this.ecc_a, this.ecc_b);
this.ecc_point_g = new ECPoint.Fp(this.ecc_curve, this.ecc_gx_fieldelement, this.ecc_gy_fieldelement);
this.ecc_bc_spec = new ECDomainParameters(this.ecc_curve, this.ecc_point_g, this.ecc_n);
ECKeyGenerationParameters ecc_ecgenparam;
ecc_ecgenparam = new ECKeyGenerationParameters(this.ecc_bc_spec, new SecureRandom());
this.ecc_key_pair_generator = new ECKeyPairGenerator();
this.ecc_key_pair_generator.init(ecc_ecgenparam);
}
}
生成随机秘钥工具类
package cn.test.encrypt.utils.sm2;
import cn.test.encrypt.utils.Util;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.math.ec.ECPoint;
import java.io.IOException;
import java.math.BigInteger;
public class SM2EncDecUtils {
//生成随机秘钥对
public static SM2KeyVO generateKeyPair(){
SM2 sm2 = SM2.Instance();
AsymmetricCipherKeyPair key = null;
while (true){
key=sm2.ecc_key_pair_generator.generateKeyPair();
if(((ECPrivateKeyParameters) key.getPrivate()).getD().toByteArray().length==32){
break;
}
}
ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters) key.getPrivate();
ECPublicKeyParameters ecpub = (ECPublicKeyParameters) key.getPublic();
BigInteger privateKey = ecpriv.getD();
ECPoint publicKey = ecpub.getQ();
SM2KeyVO sm2KeyVO = new SM2KeyVO();
sm2KeyVO.setPublicKey(publicKey);
sm2KeyVO.setPrivateKey(privateKey);
//System.out.println("公钥: " + Util.byteToHex(publicKey.getEncoded()));
//System.out.println("私钥: " + Util.byteToHex(privateKey.toByteArray()));
return sm2KeyVO;
}
//数据加密
public static String encrypt(byte[] publicKey, byte[] data) throws IOException
{
if (publicKey == null || publicKey.length == 0)
{
return null;
}
if (data == null || data.length == 0)
{
return null;
}
byte[] source = new byte[data.length];
//将数组data复制到source
System.arraycopy(data, 0, source, 0, data.length);
Cipher cipher = new Cipher();
SM2 sm2 = SM2.Instance();//new自建类,, SM2 sm2 = new SM2();
ECPoint userKey = sm2.ecc_curve.decodePoint(publicKey);
ECPoint c1 = cipher.Init_enc(sm2, userKey);
cipher.Encrypt(source);
byte[] c3 = new byte[32];
cipher.Dofinal(c3);
// System.out.println("C1 " + Util.byteToHex(c1.getEncoded()));
// System.out.println("C2 " + Util.byteToHex(source));
//System.out.println("C3 " + Util.byteToHex(c3));
//C1 C2 C3拼装成加密字串
// C1 | C2 | C3
//return Util.byteToHex(c1.getEncoded()) + Util.byteToHex(source) + Util.byteToHex(c3);
// C1 | C3 | C2
return Util.byteToHex(c1.getEncoded()) + Util.byteToHex(c3) + Util.byteToHex(source);
}
//数据解密
public static byte[] decrypt(byte[] privateKey, byte[] encryptedData) throws IOException
{
if (privateKey == null || privateKey.length == 0)
{
return null;
}
if (encryptedData == null || encryptedData.length == 0)
{
return null;
}
//加密字节数组转换为十六进制的字符串 长度变为encryptedData.length * 2
String data = Util.byteToHex(encryptedData);
/***分解加密字串 C1 | C2 | C3
* (C1 = C1标志位2位 + C1实体部分128位 = 130)
* (C3 = C3实体部分64位 = 64)
* (C2 = encryptedData.length * 2 - C1长度 - C2长度)
byte[] c1Bytes = Util.hexToByte(data.substring(0,130));
int c2Len = encryptedData.length - 97;
byte[] c2 = Util.hexToByte(data.substring(130,130 + 2 * c2Len));
byte[] c3 = Util.hexToByte(data.substring(130 + 2 * c2Len,194 + 2 * c2Len));
*/
/***分解加密字串 C1 | C3 | C2
* (C1 = C1标志位2位 + C1实体部分128位 = 130)
* (C3 = C3实体部分64位 = 64)
* (C2 = encryptedData.length * 2 - C1长度 - C2长度)
*/
byte[] c1Bytes = Util.hexToByte(data.substring(0,130));
int c2Len = encryptedData.length - 97;
byte[] c3 = Util.hexToByte(data.substring(130,130 + 64));
byte[] c2 = Util.hexToByte(data.substring(194,194 + 2 * c2Len));
SM2 sm2 = SM2.Instance();
BigInteger userD = new BigInteger(1, privateKey);
//通过C1实体字节来生成ECPoint
ECPoint c1 = sm2.ecc_curve.decodePoint(c1Bytes);
Cipher cipher = new Cipher();
cipher.Init_dec(userD, c1);
cipher.Decrypt(c2);
cipher.Dofinal(c3);
//返回解密结果
return c2;
}
}
SM2曲线算法工具类
package cn.test.encrypt.utils.sm2;
import cn.test.encrypt.utils.Util;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECFieldElement;
import org.bouncycastle.math.ec.ECFieldElement.Fp;
import org.bouncycastle.math.ec.ECPoint;
import java.math.BigInteger;
import java.security.SecureRandom;
public class SM2Factory {
/*-----------------------国密算法相关参数begin-----------
* ------------------*/
//A 第一系数
private static final BigInteger a = new BigInteger("fffffffeffffffffffffffffffffffffffffffff00000000fffffffffffffffc",16);
//B 第二系数
private static final BigInteger b = new BigInteger("28e9fa9e9d9f5e344d5a9e4bcf6509a7f39789f515ab8f92ddbcbd414d940e93",16);
//曲线X系数
private static final BigInteger gx = new BigInteger("32c4ae2c1f1981195f9904466a39c9948fe30bbff2660be1715a4589334c74c7",16);
//曲线Y系数
private static final BigInteger gy = new BigInteger("bc3736a2f4f6779c59bdcee36b692153d0a9877cc62a474002df32e52139f0a0",16);
//生产者顺序系数
private static final BigInteger n = new BigInteger("fffffffeffffffffffffffffffffffff7203df6b21c6052b53bbf40939d54123",16);
//素数
private static final BigInteger p = new BigInteger("fffffffeffffffffffffffffffffffffffffffff00000000ffffffffffffffff",16);
//因子系数 1
private static final int h = 1;
/*-----------------------国密算法相关参数end-----------------------------*/
//一些必要类
public final ECFieldElement ecc_gx_fieldelement;
public final ECFieldElement ecc_gy_fieldelement;
public final ECCurve ecc_curve;
public final ECPoint ecc_point_g;
public final ECDomainParameters ecc_bc_spec;
public final ECKeyPairGenerator ecc_key_pair_generator;
/**
* 初始化方法
* @return
*/
public static SM2Factory getInstance(){
return new SM2Factory();
}
public SM2Factory() {
this.ecc_gx_fieldelement = new Fp(this.p,this.gx);
this.ecc_gy_fieldelement = new Fp(this.p, this.gy);
this.ecc_curve = new ECCurve.Fp(this.p, this.a, this.b);
this.ecc_point_g = new ECPoint.Fp(this.ecc_curve, this.ecc_gx_fieldelement,this.ecc_gy_fieldelement);
this.ecc_bc_spec = new ECDomainParameters(this.ecc_curve, this.ecc_point_g, this.n);
ECKeyGenerationParameters ecc_ecgenparam;
ecc_ecgenparam = new ECKeyGenerationParameters(this.ecc_bc_spec, new SecureRandom());
this.ecc_key_pair_generator = new ECKeyPairGenerator();
this.ecc_key_pair_generator.init(ecc_ecgenparam);
}
/**
* 根据私钥、曲线参数计算Z
* @param userId
* @param userKey
* @return
*/
public byte[] sm2GetZ(byte[] userId, ECPoint userKey){
SM3Digest sm3 = new SM3Digest();
int len = userId.length * 8;
sm3.update((byte) (len >> 8 & 0xFF));
sm3.update((byte) (len & 0xFF));
sm3.update(userId, 0, userId.length);
byte[] p = Util.byteConvert32Bytes(this.a);
sm3.update(p, 0, p.length);
p = Util.byteConvert32Bytes(this.b);
sm3.update(p, 0, p.length);
p = Util.byteConvert32Bytes(this.gx);
sm3.update(p, 0, p.length);
p = Util.byteConvert32Bytes(this.gy);
sm3.update(p, 0, p.length);
p = Util.byteConvert32Bytes(userKey.normalize().getXCoord().toBigInteger());
sm3.update(p, 0, p.length);
p = Util.byteConvert32Bytes(userKey.normalize().getYCoord().toBigInteger());
sm3.update(p, 0, p.length);
byte[] md = new byte[sm3.getDigestSize()];
sm3.doFinal(md, 0);
return md;
}
/**
* 签名相关值计算
* @param md
* @param userD
* @param userKey
* @param sm2Result
*/
public void sm2Sign(byte[] md, BigInteger userD, ECPoint userKey, SM2Result sm2Result) {
BigInteger e = new BigInteger(1, md);
BigInteger k = null;
ECPoint kp = null;
BigInteger r = null;
BigInteger s = null;
do {
do {
// 正式环境
AsymmetricCipherKeyPair keypair = ecc_key_pair_generator.generateKeyPair();
ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters) keypair.getPrivate();
ECPublicKeyParameters ecpub = (ECPublicKeyParameters) keypair.getPublic();
k = ecpriv.getD();
kp = ecpub.getQ();
//System.out.println("BigInteger:" + k + "\nECPoint:" + kp);
//System.out.println("计算曲线点X1: "+ kp.getXCoord().toBigInteger().toString(16));
//System.out.println("计算曲线点Y1: "+ kp.getYCoord().toBigInteger().toString(16));
//System.out.println("");
// r
r = e.add(kp.getXCoord().toBigInteger());
r = r.mod(this.n);
} while (r.equals(BigInteger.ZERO) || r.add(k).equals(this.n)||r.toString(16).length()!=64);
// (1 + dA)~-1
BigInteger da_1 = userD.add(BigInteger.ONE);
da_1 = da_1.modInverse(this.n);
// s
s = r.multiply(userD);
s = k.subtract(s).mod(this.n);
s = da_1.multiply(s).mod(this.n);
} while (s.equals(BigInteger.ZERO)||(s.toString(16).length()!=64));
sm2Result.r = r;
sm2Result.s = s;
}
/**
* 验签
* @param md sm3摘要
* @param userKey 根据公钥decode一个ecpoint对象
* @param r 没有特殊含义
* @param s 没有特殊含义
* @param sm2Result 接收参数的对象
*/
public void sm2Verify(byte md[], ECPoint userKey, BigInteger r,
BigInteger s, SM2Result sm2Result) {
sm2Result.R = null;
BigInteger e = new BigInteger(1, md);
BigInteger t = r.add(s).mod(this.n);
if (t.equals(BigInteger.ZERO)) {
return;
} else {
ECPoint x1y1 = ecc_point_g.multiply(sm2Result.s);
//System.out.println("计算曲线点X0: "+ x1y1.normalize().getXCoord().toBigInteger().toString(16));
//System.out.println("计算曲线点Y0: "+ x1y1.normalize().getYCoord().toBigInteger().toString(16));
//System.out.println("");
x1y1 = x1y1.add(userKey.multiply(t));
//System.out.println("计算曲线点X1: "+ x1y1.normalize().getXCoord().toBigInteger().toString(16));
//System.out.println("计算曲线点Y1: "+ x1y1.normalize().getYCoord().toBigInteger().toString(16));
//System.out.println("");
sm2Result.R = e.add(x1y1.normalize().getXCoord().toBigInteger()).mod(this.n);
//System.out.println("R: " + sm2Result.R.toString(16));
return;
}
}
}
SM2对象
package cn.test.encrypt.utils.sm2;
import cn.test.encrypt.utils.Util;
import cn.test.encrypt.test.SecurityTestAll;
import org.bouncycastle.math.ec.ECPoint;
import java.math.BigInteger;
public class SM2KeyVO {
BigInteger privateKey ;
ECPoint publicKey ;
public BigInteger getPrivateKey() {
return privateKey;
}
public void setPrivateKey(BigInteger privateKey) {
this.privateKey = privateKey;
}
public ECPoint getPublicKey() {
return publicKey;
}
public void setPublicKey(ECPoint publicKey) {
this.publicKey = publicKey;
}
//HardPubKey:3059301306072A8648CE3D020106082A811CCF5501822D03420004+X+Y
//SoftPubKey:04+X+Y
public String getPubHexInSoft(){
return Util.byteToHex(publicKey.getEncoded(true));
//System.out.println("公钥: " + );
}
public String getPubHexInHard(){
return SecurityTestAll.SM2PubHardKeyHead +Util.byteToHex(publicKey.getEncoded(true));
}
public String getPriHexInSoft(){
return Util.byteToHex(privateKey.toByteArray());
}
}
public static void main(String[] args) {
SM2KeyVO initKeyVO = SM2EncDecUtils.generateKeyPair();
System.out.println("初始公钥为: "+initKeyVO.getPubHexInSoft());
System.out.println("初始私钥为: "+initKeyVO.getPriHexInSoft());
}
输出结果:
简单业务描述:使用初始公钥加密工作公钥,使用初始私钥解密加密后公钥;
Demo
public static void main(String[] args) throws IOException {
//生成初始秘钥
SM2KeyVO initKeyVO = SM2EncDecUtils.generateKeyPair();
String initPubKey = initKeyVO.getPubHexInSoft();
String initPriKey = initKeyVO.getPriHexInSoft();
System.out.println("初始公钥为: " + initPubKey);
System.out.println("初始私钥为: " + initPriKey);
//生成工作秘钥
SM2KeyVO serverKey = SM2EncDecUtils.generateKeyPair();
String serverPubKey = serverKey.getPubHexInSoft();
String serverPriKey = serverKey.getPriHexInSoft();
System.out.println("服务端公钥为: " + serverPubKey);
System.out.println("服务端私钥为: " + serverPriKey);
//使用初始公钥加密服务端公钥 注意格式字节数组
String encryptServerPubKey = SM2EncDecUtils.encrypt(Convert.hexToBytes(initPubKey), serverPubKey.getBytes());
System.out.println("加密后公钥:"+encryptServerPubKey);
//使用初始私钥解密服务端公钥 注意格式
byte[] decrypt = SM2EncDecUtils.decrypt(Convert.hexToBytes(initPriKey), Convert.hexToBytes(encryptServerPubKey));
System.out.println("解密后服务端公钥:"+new String(decrypt));
if (serverPubKey.equals(new String(decrypt)))
System.out.println("——————————解密成功——————————");
}
结果:
至此,SM2的公私钥生成以及加解密就完成了,下一期内容为生成Sign以及验证。