国密SM2/3/4 JAVA & PHP互通

背景知识

国密即国家密码局认定的国产密码算法。主要有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&timestamp=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&timestamp=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)

https://cloud.tencent.com/developer/article/2421525

LOOPER'S DAILY
LOOPER'S DAILY
LOOPER'S DAILY
LOOPER'S DAILY
LOOPER'S DAILY
LOOPER'S DAILY

Leave a Comment

您的邮箱地址不会被公开。 必填项已用 * 标注