背景知识
国密即国家密码局认定的国产密码算法。主要有SM1,SM2,SM3,SM4。密钥长度和分组长度均为128位。国密算法是指国家密码管理局认定的一系列国产密码算法,包括SM1-SM9以及ZUC等。其中
- SM1、SM4、SM5、SM6、SM7、SM8、ZUC等属于对称密码
- SM2、SM9等属于公钥密码 (非对称加密)
- SM3属于单向散列函数。
目前我国主要使用公开的SM2、SM3、SM4作为商用密码算法。 其中SM1、SM7算法不公开,调用该算法时,需要通过加密芯片的接口进行调用.
- SM2是基于椭圆曲线的公钥密码算法,包括用于数字签名的SM2-1、用于密钥交换的SM2-2和用于公钥密码的SM2-3。
- SM3是能够计算出256比特的散列值的单向散列函数,主要用于数字签名和消息认证码。
- SM4是属于对称密码的一种分组密码算法,分组长度和密钥长度均为128比特。
使用经验
- 一般数据发送端都是用SM4对数据内容加密,使用SM3对内容进行摘要,再使用SM2对摘要进行签名。
- 一般接收端,先用SM2对摘要进行验签,验签成功后就做到了防抵赖,对发送过来的内容进行SM3摘要,看下生成的摘要和验签后的摘要是否一致,用于防篡改。
- 另外SM4在加密解密需要相同的密钥,这个我们可以通过编写密钥交换模块实现生成相同的密钥。用于SM4对称加密。
关于非对称还要注意几点:
(1)公钥是通过私钥产生的;
(2)公钥加密,私钥解密是加密的过程
(3)私钥加密,公钥解密是签名的过程;
由于SM1、SM4加解密的分组大小为128bit,故对消息进行加解密时,若消息长度过长,需要进行分组,要消息长度不足,则要进行填充。
SM2算法:(非对称加密)
SM2可以理解为国产RSA。非对称加密,基于ECC。该算法已公开。由于该算法基于ECC,故其签名速度与秘钥生成速度都快于RSA。
SM2椭圆曲线公钥密码算法是我国自主设计的公钥密码算法,包括SM2-1椭圆曲线数字签名算法,SM2-2椭圆曲线密钥交换协议,SM2-3椭圆曲线公钥加密算法,分别用于实现数字签名密钥协商和数据加密等功能。SM2算法与RSA算法不同的是,SM2算法是基于椭圆曲线上点群离散对数难题,相对于RSA算法,256位的SM2密码强度已经比2048位的RSA密码强度要高,但运算速度快于RSA。
JAVA 服务端
https://cloud.tencent.com/developer/article/2421525
Hutool针对Bouncy Castle做了简化包装,用于实现国密算法中的SM2、SM3、SM4。
国密算法工具封装包括:
- 非对称加密和签名:SM2
- 摘要签名算法:SM3
- 对称加密:SM4
国密算法需要引入Bouncy Castle库的依赖。
MVN pom.xml配置文件增加依赖: - 说明 bcprov-jdk15to18的版本请前往Maven中央库搜索,查找对应JDK的最新版本。
笔者采用以下2个JAR包(JAVA1.8)

image.png
Maven配置:
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15to18</artifactId>
<version>1.69</version>
</dependency>
SM3HMAC算法:(JAVA中使用Hutool库实现,但在其他语言不一定有现成库,需要转写该逻辑)
public static void main(String[] args) {
// String json = "appId=7f0d19a89e1558cb&data={}&encType=SM4&requestId=1&signType=SM3×tamp=1719017974&transType=PERSON&version=1.0.0&key=7f0d19a89e1558cb";
String json = "appId=7f0d19a89e1558cb&data={\"cardId\":\"\",\"certNo\":\"320723199210042814\",\"certType\":\"01\"}&encType=SM4&requestId=1&signType=SM3×tamp=1719215443112&transType=APP&version=1.0.0&key=7f0d19a89e1558cb";
String d = Sm3Utils.encryptKey("ALpyMppfcUJfN7ttrFvrPWMT0SV8rCvOorbAKVITtfPj",json);
System.out.println("@@@encryptKey:"+d);
}
import java.io.UnsupportedEncodingException;
import java.security.Security;
import java.util.Arrays;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
public class Sm3Utils {
/**
* sm3算法加密(SM3HMAC)
*
* @param sm3Key 加密秘钥
* @param paramStr 待加密字符串
* @return 返回加密后,固定长度=32的16进制字符串
* @explain
*/
public static String encryptKey(String sm3Key, String paramStr) {
// 将返回的hash值转换成16进制字符串
String resultHexString = "";
try {
// 将字符串转换成byte数组
byte[] srcData = paramStr.getBytes(ENCODING);
// 调用hash()
byte[] strKey = sm3Key.getBytes();
byte[] resultHash = hmac(strKey, srcData);
// 将返回的hash值转换成16进制字符串
resultHexString = ByteUtils.toHexString(resultHash);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
System.out.println("SM3秘钥{}"+sm3Key);// true
return resultHexString;
}
}
SM4加解密:
/**
* sm4加密
*
* @param chnlId 渠道id
* @param sm4key 渠道sm4密钥
* @param message 待加密报文
* @return 加密后的报文内容 String
* @throws Exception
*/
public static String sm4Encrypt(String chnlId, String sm4key, String message) throws Exception {
//用appId加密appSecret获取新秘钥
byte[] appSecretEncData = EasyGmUtils.sm4Encrypt(chnlId.substring(0, 16).getBytes("UTF-8"), sm4key.getBytes("UTF-8"));
byte[] secKey = Hex.toHexString(appSecretEncData).toUpperCase().substring(0, 16).getBytes("UTF-8");
System.out.println("NEWKEY:"+Hex.toHexString(appSecretEncData).toUpperCase());
System.out.println("NEWKEY-16:"+Hex.toHexString(appSecretEncData).toUpperCase().substring(0, 16));
//加密数据
String sEn = Hex.toHexString(EasyGmUtils.sm4Encrypt(secKey, message.getBytes("UTF-8"))).toUpperCase();
return sEn;
}
/**
* sm4解密
*
* @param chnlId 渠道id
* @param sm4key 渠道sm4密钥
* @param message 待解密报文
* @return 解密后的报文 String
* @throws Exception
*/
public static String sm4Decrypt(String chnlId, String sm4key, String message) throws Exception {
//生产解密key
byte[] appSecretEncDataDecode = EasyGmUtils.sm4Encrypt(chnlId.substring(0, 16).getBytes("UTF-8"), sm4key.getBytes("UTF-8"));
byte[] secKeyDecode = Hex.toHexString(appSecretEncDataDecode).toUpperCase().substring(0, 16).getBytes("UTF-8");
System.out.println("NEWKEY:"+Hex.toHexString(appSecretEncDataDecode).toUpperCase());
// return new String(EasyGmUtils.sm4Decrypt(secKeyDecode, message.getBytes("UTF-8")));
return new String(EasyGmUtils.sm4Decrypt(secKeyDecode, Hex.decode(message)), StandardCharsets.UTF_8);
}
PHP 客户端
<?php
require_once '../vendor/autoload.php';
use Rtgm\sm\RtSm2;
$publicKey = '043d9d4cc71a285af936b36880fd4d6155c22957cd2c84ea313469065207fb951b9ef1db79d69af8886e91e833da1ebc6bfdde86e70f52923d6e042eaa147624c7'; // 公钥
$privateKey = 'a7763cd4fe7db2a2146fc09bf2d5e5a30e10c51b7e4bed00b3a26ec79ba78ff3'; // 私钥
$publicfile = "./data/sm2pub.pem";
$privatefile = "./data/sm2.pem";
$userId = '1234567812345678';
$document = "我爱你ILOVEYOU!";
define('GK',1);
define('SIGN',1);
define('SIGNPEM',1);
//返回的签名16进制还是base64, 目前可选hex,与base64两种
// __construct($formatSign='hex', $randFixed = true)
$sm2 = new RtSm2('base64',true);
if(GK){
echo "\n----------生成明文密钥对--------------------------\n";
print_r($sm2->generatekey()); //生成明文密钥
echo "\n----------生成pem密钥对--------------------------\n";
print_r($sm2->generatePemkey()); //生成pem密钥,请放到相应的文件中
}
if(SIGN){
echo "\n---------明文密钥签名---------------------------\n";
$sign = $sm2->doSign( $document, $privateKey, $userId);
print_r($sign);
echo "\n---------明文密钥验签---------------------------\n";
var_dump($sm2->verifySign( $document, $sign, $publicKey, $userId ));
}
if(SIGNPEM){
echo "\n---------PEM密钥签名---------------------------\n";
$sign = $sm2->doSignOutKey( $document, $privatefile, $userId);
print_r($sign);
echo "\n---------PEM密钥验签---------------------------\n";
var_dump($sm2->verifySignOutKey( $document, $sign, $publicfile, $userId ));
}
SM3算法:
SM3可以理解为国产MD5。消息摘要。可以用MD5作为对比理解。该算法已公开。校验结果为256位。
<?php
require_once '../vendor/autoload.php';
use Rtgm\sm\RtSm3;
$sm3 = new RtSm3();
$data = '我爱你ILOVEYOU!';
print_r($sm3->digest($data,1));
SM3HMAC算法PHP实现:(理解JAVA中SM3HMAC算法,使用PHP方法进行转写)
// 需要实现hamc-sm3国密定制算法
// composer require axios/tools
// @see https://github.com/AxiosLeo/php-tools/tree/cd576f058db7b633750d62bb6d7d1840f0ab8d5e
$hmac = new HMac();
$hmac->registerAlgorithm('sm3', function ($str) {
// SM3签名函数
$sm3 = new SM3();
$sm3->encode($str);
return $sm3->getHex();
});
$s_crypto= $hmac->count('sm3', $s_return, $this->Prvkey);
SM4算法:对称加密:
可以理解为国产AES。无线局域网标准的分组数据算法。对称加密,密钥长度和分组长度均为128位。
<?php
require_once '../vendor/autoload.php';
use Rtgm\sm\RtSm4;
$key = "0123456789abcdef";
$iv = '1234567887654321';
$sm4 = new RtSm4($key);
$data = '我1爱你ILOVEYOU!!!';
$data = str_repeat('abc',7);
//sm4 的ecb 与cbc加密有补齐(16*n)l
// sm4->encrypt($data, $type = 'sm4', $iv = '', $format = 'hex')
// openssl_encrypt ,和服务器openssl版本,PHP版本有关,有些服务器可能不支持sm4-* 相关的对称加密算法,
echo "==== test sm4 sm4-cbc============";
echo "\nphp sm4: ".$hex = $sm4->encrypt($data,'sm4',$iv); //default is cbc
echo "\nphp decode: ".$sm4->decrypt($hex,'sm4',$iv,'hex');
echo "\n==== test sm4-ecb============";
echo "\nphp sm4-ecb: ".$hex = $sm4->encrypt($data,'sm4-ecb');
echo "\nphp decode: ".$sm4->decrypt($hex,'sm4-ecb','','hex');
echo "\n==== test sm4-ofb============";
echo "\nphp sm4-ofb: ".$hex = $sm4->encrypt($data,'sm4-ofb',$iv);
echo "\nphp decode: ".$sm4->decrypt($hex,'sm4-ofb',$iv,'hex');
echo "\n==== test sm4-cfb============";
echo "\nphp sm4-cfb: ".$hex = $sm4->encrypt($data,'sm4-cfb',$iv);
echo "\nphp decode: ".$sm4->decrypt($hex,'sm4-cfb',$iv,'hex');
echo "\n==== test sm4-ctr============";
echo "\nphp sm4-ctr: ".$hex = $sm4->encrypt($data,'sm4-ctr',$iv);
echo "\nphp decode: ".$sm4->decrypt($hex,'sm4-ctr',$iv,'hex');
参考文档:
国密招商银行对接 | Laravel China 社区 (learnku.com)
lpilp/phpsm2sm3sm4: php版本,支持国密SM2的签名算法,非对称加解密,SM3的hash, SM4的对称加解密 (github.com)