国密SM2公私钥一般为C语言的结构体转为java对象得到的。该场景一般是通过密码机生成的外部非对称密钥对。运算也是有密码机进行运算,但如果想脱离密码机,使用java语言进行签名验签、加解密等活动,该如何实现?
本文以java的BC库为例,完成国密公私钥格式转换为BC库格式,然后通过Java的JCE框架完成签名、验签、加/解密等功能。
首先我们先了解下SM2公私钥的结构,SM2私钥主要是一个32位的D值,公钥的结构为32位的X变量和Y变量。
我们先看下SM2的国密私钥如果转为BC库的格式:首先在使用BC库的类,加入以下代码,用于密码提供商的注册。
static{
if(Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
Security.addProvider(new BouncyCastleProvider());
}
}
私钥转换:BC库格式的私钥是需要包含公钥的,否则在签名的时候,使用SM3算法无法做数据预处理。
/**
* GM格式ECC私钥转换BC格式私钥
* @param privateKey ECC外部私钥
* @return BouncyCastle格式私钥
*/
public static BCECPrivateKey eccPrivateKeyConverToBC(ECCrefPrivateKey.ByReference privateKey, HsmDef.ECCrefPublicKey.ByReference publicKey){
X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");
// 椭圆曲线公钥或私钥的基本域参数。
ECParameterSpec ecDomainParameters = new ECParameterSpec(x9ECParameters.getCurve(), x9ECParameters.getG(),
x9ECParameters.getN());
BigInteger d = new BigInteger(privateKey.getK());
//获得BC库格式的ECC公钥
BCECPublicKey bcecPublicKey = eccPublicKeyConverToBC(publicKey);
//组装域参数
ECDomainParameters domainParameters = new ECDomainParameters(x9ECParameters.getCurve(), x9ECParameters.getG(),
x9ECParameters.getN(), x9ECParameters.getH());
//组装BC库格式私钥对象
BCECPrivateKey bcPrivateKey = new BCECPrivateKey("EC",new ECPrivateKeyParameters(d, domainParameters),bcecPublicKey,ecDomainParameters,BouncyCastleProvider.CONFIGURATION);
return bcPrivateKey;
}
公钥转换:
/**
* ECC公钥转换BC格式公钥
* @param publicKey ECC外部私钥
* @return BouncyCastle格式私钥
*/
public static BCECPublicKey eccPublicKeyConverToBC(ECCrefPublicKey.ByReference publicKey){
BigInteger x = null;
BigInteger y = null;
if (ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN) {
x = new BigInteger(1,publicKey.getX());
y = new BigInteger(1,publicKey.getY());
} else {
x = new BigInteger(publicKey.getX());
y = new BigInteger(publicKey.getY());
}
X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");
// 椭圆曲线公钥或私钥的基本域参数。
ECParameterSpec ecDomainParameters = new ECParameterSpec(x9ECParameters.getCurve(), x9ECParameters.getG(),
x9ECParameters.getN());
// 通过公钥x、y分量创建椭圆曲线公钥规范
ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(x9ECParameters.getCurve().createPoint(x, y),
ecDomainParameters);
KeyFactory keyFactory = null;
try {
keyFactory = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch(NoSuchProviderException e){
e.printStackTrace();
}
try {
return (BCECPublicKey)keyFactory.generatePublic(ecPublicKeySpec);
} catch (InvalidKeySpecException e) {
e.printStackTrace();
}
return null;
}
SM2签名:签名值为byte数组
/**
* 签名运算
* @param data 待签名数据
* @param signAlg 签名算法(如:SM3WithSM2、SHA256WithSM2)
* @param softPrivateKey 私钥
* @return 签名值
*/
public byte[] sign(byte[] data, String signAlg, PrivateKey softPrivateKey) {
Signature inSignatue = null;
try {
inSignatue = Signature.getInstance(signAlg, BouncyCastleProvider.PROVIDER_NAME);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
throw new CryptoException("未找到算法",e);
}catch (NoSuchProviderException e){
throw new CryptoException("未找到安全程序提供商,原因:"+e.getMessage(),e);
}
try {
inSignatue.initSign(softPrivateKey);
} catch (InvalidKeyException e) {
e.printStackTrace();
throw new CryptoException("无效的密钥",e);
}
try {
inSignatue.update(data);
} catch (SignatureException e) {
e.printStackTrace();
throw new CryptoException("签名更新数据异常",e);
}
byte[] signOut = null;
try {
signOut = inSignatue.sign();
} catch (SignatureException e) {
e.printStackTrace();
throw new CryptoException("签名异常",e);
}
return signOut;
}
SM2验签:
/**
* 验签运算
* @param sign 签名值(签名的结果)
* @param data 数据原文
* @param signAlg 签名算法(与签名时的算法一致)
* @param softPublicKey 公钥
* @return true为验签通过,false为未通过
*/
public boolean verifySign(byte[] sign, byte[] data, String signAlg, PublicKey softPublicKey) {
Signature inSignatue = null;
try {
inSignatue = Signature.getInstance(signAlg, BouncyCastleProvider.PROVIDER_NAME);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
throw new CryptoException("未找到算法",e);
}catch (NoSuchProviderException e){
e.printStackTrace();
throw new CryptoException("当需要特定的安全提供程序但在环境中不可用时,会引发此异常:"+e.getMessage(),e);
}
try {
inSignatue.initVerify(softPublicKey);
} catch (InvalidKeyException e) {
e.printStackTrace();
throw new CryptoException("无效的密钥",e);
}
try {
inSignatue.update(data);
} catch (SignatureException e) {
e.printStackTrace();
throw new CryptoException("验签更新数据异常",e);
}
boolean flag = false;
try {
flag = inSignatue.verify(sign);
} catch (SignatureException e) {
e.printStackTrace();
throw new CryptoException("验签异常",e);
}
return flag;
}
SM2公钥加密:
/**
*@param publicKey SM2公钥
*@param algorithm 加密算法,固定传“SM2”即可
*@param inData 待加密的数据
*@return 密文数据
**/
public byte[] publicKeyEnc(PublicKey publicKey,String algorithm,byte[] inData){
Cipher cipher = null;
try {
cipher = Cipher.getInstance(algorithm, BouncyCastleProvider.PROVIDER_NAME);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
throw new CryptoException("未找到算法,原因:"+e.getMessage(),e);
} catch (NoSuchPaddingException e) {
e.printStackTrace();
throw new CryptoException("未找到填充模式,原因:"+e.getMessage(),e);
}catch (NoSuchProviderException e){
e.printStackTrace();
throw new CryptoException("当需要特定的安全提供程序但在环境中不可用时,会引发此异常:"+e.getMessage(),e);
}
try {
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
} catch (InvalidKeyException e) {
e.printStackTrace();
throw new CryptoException("无效的密钥,原因:"+e.getMessage(),e);
}
byte[] tTemp = null;
try {
tTemp = cipher.doFinal(inData);
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
throw new CryptoException("非法块大小异常",e);
} catch (BadPaddingException e) {
e.printStackTrace();
throw new CryptoException("错误的填充",e);
}
return tTemp;
}
SM2私钥解密:
/**
* 外部密钥解密
* @param privateKey 私钥
* @param algorithm 加密算法(与加密时使用的算法一致)
* @param inData 待解密数据
* @return 明文数据
*/
private byte[] externalDecrypt(PrivateKey privateKey,String algorithm,byte[] inData){
Cipher cipher = null;
try {
cipher = Cipher.getInstance(algorithm, BouncyCastleProvider.PROVIDER_NAME);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
throw new CryptoException("未找到算法",e);
}catch (NoSuchPaddingException e) {
e.printStackTrace();
throw new CryptoException("未找到填充模式",e);
}catch (NoSuchProviderException e){
throw new CryptoException("当需要特定的安全提供程序但在环境中不可用时,会引发此异常:"+e.getMessage(),e);
}
try {
cipher.init(Cipher.DECRYPT_MODE, privateKey);
} catch (InvalidKeyException e) {
e.printStackTrace();
throw new CryptoException("无效的密钥",e);
}
byte[] tResult = null;
try {
tResult = cipher.doFinal(inData);
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
throw new CryptoException("非法块大小异常",e);
} catch (BadPaddingException e) {
e.printStackTrace();
throw new CryptoException("错误的填充",e);
}
return tResult;
}
以上则为SM2算法的相关知识,如有不合理的地方,后期发现后再改。