基于Jakarta,TypeScript,Golong的国密SM2、3、4


前言

最近还要深度研究hutools底层实现,一定要搞透澈,本章将会是持续更新 所有密钥由Jakarta统一生成,因为没测试其他语言生成是否可以
参考资料:
Java代码实现SM2算法以及注意点总结(踩坑记录)
国密算法工具Smutil

一. Jakarta代码

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.29</version>
        </dependency>

        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk15to18</artifactId>
            <version>1.69</version>
        </dependency>

import cn.hutool.core.util.HexUtil;
import cn.hutool.crypto.ECKeyUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.SM2;
import cn.hutool.crypto.symmetric.SM4;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

import java.nio.charset.StandardCharsets;
import java.security.KeyPair;

/**
 * @author YuanJie
 * @date 2024/8/6 上午8:44
 * 加密方式
 * 加密方式里边,有两种 一种是 C1C3C2另一种是C1C2C3,
 * 这两种加密方式不同,当时找的资料说是,旧版本的标准上,
 * 使用的是C1C2C3,但是后续应该是更新过,使用的是C1C3C2,
 * 其他语言,比如Python或者Golang或者C的实现大多直接就是C1C3C2的,
 * 但是如果java中使用的bouncycastle的包,默认使用的是C1C2C3,
 * 就会发生与其他语言的加密结果不能相互解密的情况,但是可能你跟其他人的Java系统加解密又没有问题。
 * <p>
 * 导出
 * Java生成的密钥,在导出的时候,最好使用 PrivateKey.getD().toByteArray() 和 PublicKey.getQ().getEncoded() 导出密钥,然后再转成Base64或者Hex给其他系统;
 * <p>
 * 导入
 * 导入的时候,请使用我上边的代码转换成对应的公钥或者私钥的对象,
 * 这里有另一个容易出错的地方,在私钥byte[]转私钥对象的时候,
 * 有些人会使用new BigInteger(byte[])这个方法将byte[]转为BigInteger,
 * 然后调用 keyFactory.generatePrivate(new ECPrivateKeySpec(BigInteger, ECParameterSpec)),
 * 但是实测这样在一些情况下会报错,这个时间长了记不清具体原因了,好像是因为第一位为负数的情况下会报错,
 * 我的方法是把byte[]转为Hex,然后再使用new BigInteger(hexStr, int)这种方式转为BigInteger,这个需要注意。
 * <p>
 * 公钥压缩
 * publicKey.getQ().getEncoded()这个方法,
 * 有一个boolean参数,控制输出的密钥是否为压缩后的密钥,
 * 输出内容转为Hex之后,02和03开头的为压缩后的密钥,04表示未经压缩的密钥。
 * Java里边压缩为压缩的调用同一个方法就能转为公钥对象,但是其他语言的目前不清楚,所以导出的时候,最好标注一下压缩或者未压缩。
 * <p>
 * Sm2签名时,有一个userId的概念,这个东西一般直接用CipherParameters对象是带不进去的,
 * 如果没有,默认是 Hex.decodeStrict(“31323334353637383132333435363738”) ,
 * 也就是1234567812345678的Ascii值,如果要使用自定义的userId,则需要使用ParametersWithID这个对象调用Signer的init,
 * 这个对象可以传入一个CipherParameters,然后再传入一个userId,可以把制定的userId带进去。
 * <p>
 * 签名验签RS
 * 我们平常接触的算法,一般我们调用加解密算法只返回一个值,
 * 但是Sm2算法,签名其实是有两个值,一个R,一个S,
 * 两个值构成一个签名结果,Java中bouncycastle的返回虽然也是一个值,
 * 但是大概看了一下算法的实现代码,其实得出的结果也是两个值,
 * 一个R,一个S,然后通过一个方法拼接成一个值(不确定这个方法的转换方式是不是有标准的)。
 * 在我们与C程序一块测试的时候,他们反馈了这个问题,
 * 然后我对着bouncycastle包内的org.bouncycastle.crypto.signers.SM2Signer类,
 * 摘出来了R、S和单一返回值的转换代码,已经提取到上边的代码里,可以直接使用。
 */
@Configuration
@Slf4j
public class NationalSecretsUtils {

    public static String privateKey2;

    public static String publicKey2;

    public static String secretKey4;

    public NationalSecretsUtils(@Value("${sm2.privateKey}") String privateKey2,
                                @Value("${sm2.publicKey}") String publicKey2,
                                @Value("${sm4.secretKey}") String secretKey4) {
        NationalSecretsUtils.privateKey2 = privateKey2;
        NationalSecretsUtils.publicKey2 = publicKey2;
        NationalSecretsUtils.secretKey4 = secretKey4;
    }

    public static void main(String[] args) {
        KeyPair keyPair = SecureUtil.generateKeyPair("SM2");
        // 2. 获取公私钥
        //这里公钥不压缩  公钥的第一个字节用于表示是否压缩  可以不要 65字节变64字节
        byte[] publicKey = ((BCECPublicKey) keyPair.getPublic()).getQ().getEncoded(false);
        byte[] privateKey = ((BCECPrivateKey) keyPair.getPrivate()).getD().toByteArray();
        String sm2pub = HexUtil.encodeHexStr(publicKey);
        String sm2pri = HexUtil.encodeHexStr(privateKey);
        NationalSecretsUtils.publicKey2 = sm2pub;
        NationalSecretsUtils.privateKey2 = sm2pri;

        NationalSecretsUtils.secretKey4 = HexUtil.encodeHexStr(SecureUtil.generateKey("SM4").getEncoded());
        log.info("sm2公钥:{}", sm2pub);
        log.info("sm2私钥:{}", sm2pri);
        log.info("sm4密钥:{}", NationalSecretsUtils.secretKey4);
        // 测试sm2加解密
        log.info("sm2签名:{}", HexUtil.encodeHexStr(generateSM2Sign("我是sm2数据")));
        log.info("sm2验证:{}", verifySM2Sign("我是sm2数据", generateSM2Sign("我是sm2数据")));
        // 测试sm4加解密
        log.info("sm4加密:{}", HexUtil.encodeHexStr(generateSM4Sign("我是sm4数据")));
        log.info("sm4解密:{}", decryptSM4Sign(generateSM4Sign("我是sm4数据")));
        // 测试sm3哈希算法
        log.info("sm3哈希算法:{}", generateSM3Sign("我是sm3数据"));
        log.info("sm3验证:{}", verifySM3Sign("我是sm3数据", generateSM3Sign("我是sm3数据")));
    }

    /**
     * SM2签名 使用私钥D值签名
     * 默认C1C3C2
     *
     */
    public static byte[] generateSM2Sign(String text) {
        SM2 sm2 = new SM2(privateKey2, null, null);
        sm2.usePlainEncoding();
        return sm2.sign(text.getBytes(StandardCharsets.UTF_8), null);
    }

    /**
     * SM2验证 使用公钥Q值验证签名
     */
    public static boolean verifySM2Sign(String text, byte[] sign) {
        SM2 sm2 = new SM2(null, ECKeyUtil.toSm2PublicParams(publicKey2));
        sm2.usePlainEncoding();
        return sm2.verify(text.getBytes(StandardCharsets.UTF_8), sign);
    }

    /**
     * SM3 哈希算法 3是能够计算出256比特的散列值的单向散列函数,主要用于数字签名和消息认证码。
     * base16
     */
    public static String generateSM3Sign(String text) {
        return SmUtil.sm3(text);
    }

    /**
     * SM3验证
     *
     */
    public static boolean verifySM3Sign(String text, String sign) {
        return SmUtil.sm3(text).equals(sign);
    }

    /**
     * SM4 是一种对称加密算法,它使用128位密钥,使用分组密码模式,使用CBC模式加密。
     * SM4加密
     */
    public static String generateSM4Sign(String text) {
        SM4 sm4 = SmUtil.sm4(HexUtil.decodeHex(secretKey4));
        return sm4.encryptHex(text);
    }

    /**
     * SM4解密
     */
    public static String decryptSM4Sign(String text) {
        SM4 sm4 = SmUtil.sm4(HexUtil.decodeHex(secretKey4));
        return sm4.decryptStr(text, StandardCharsets.UTF_8);
    }
}

二. TypeScript

npm install sm-crypto
/**
 * npm install sm-crypto
 * https://www.npmjs.com/package/sm-crypto
 */
import {sm2, sm3, sm4} from 'sm-crypto';

class NationalSecretsUtils {
    static privateKey2: string;
    static publicKey2: string;
    static secretKey4: string;

    constructor(privateKey2: string, publicKey2: string, secretKey4: string) {
        NationalSecretsUtils.privateKey2 = privateKey2;
        NationalSecretsUtils.publicKey2 = publicKey2;
        NationalSecretsUtils.secretKey4 = secretKey4;
    }

    static main() {
        console.log('sm2公钥:', NationalSecretsUtils.publicKey2);
        console.log('sm2私钥:', NationalSecretsUtils.privateKey2);
        console.log('sm4密钥:', NationalSecretsUtils.secretKey4);

        const sm2Sign = NationalSecretsUtils.generateSM2Sign("我是sm2数据");
        console.log('sm2签名:', sm2Sign);
        console.log('sm2验证:', NationalSecretsUtils.verifySM2Sign("我是sm2数据", sm2Sign));

        const sm4Encrypt = NationalSecretsUtils.generateSM4Sign("我是sm4数据");
        console.log('sm4加密:', sm4Encrypt);
        console.log('sm4解密:', NationalSecretsUtils.decryptSM4Sign(sm4Encrypt));

        const sm3Hash = NationalSecretsUtils.generateSM3Sign("我是sm3数据");
        console.log('sm3哈希算法:', sm3Hash);
        console.log('sm3验证:', NationalSecretsUtils.verifySM3Sign("我是sm3数据", sm3Hash));
    }

    static generateSM2Sign(text: string): string {
        return sm2.doSignature(text, NationalSecretsUtils.privateKey2);
    }

    static verifySM2Sign(text: string, sign: string): boolean {
        return sm2.doVerifySignature(text, sign, NationalSecretsUtils.publicKey2);
    }

    static generateSM3Sign(text: string): string {
        return sm3(text);
    }

    static verifySM3Sign(text: string, sign: string): boolean {
        return sm3(text) === sign;
    }

    static generateSM4Sign(text: string): string {
        const key = NationalSecretsUtils.secretKey4;
        return sm4.encrypt(text,key);
    }

    static decryptSM4Sign(text: string): string {
        const key = NationalSecretsUtils.secretKey4;
        return sm4.decrypt(text,key);
    }
}

// 示例调用
const nsu = new NationalSecretsUtils('privateKey2',
    'publicKey2',
    'secretKey4');
NationalSecretsUtils.main();

三.golang

package main

import (
	"bytes"
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"github.com/tjfoc/gmsm/sm2"
	"github.com/tjfoc/gmsm/sm3"
	"github.com/tjfoc/gmsm/sm4"
	"log"
	"math/big"
)

/**
* go get  -u github.com/tjfoc/gmsm@v1.4.1
 */
type NationalSecretsUtils struct {
	PrivateKey2 *sm2.PrivateKey
	PublicKey2  *sm2.PublicKey
	SecretKey4  []byte
}

func NewNationalSecretsUtils(privateKeyHex2, publicKeyHex2, secretKeyHex string) *NationalSecretsUtils {
	// Decode SM2 private key
	privateKeyBytes, err := hex.DecodeString(privateKeyHex2)
	if err != nil {
		log.Fatalf("Failed to decode SM2 private key: %v", err)
	}
	privateKey := new(sm2.PrivateKey)
	privateKey.D = new(big.Int).SetBytes(privateKeyBytes)
	// Initialize the rest of the private key fields
	privateKey.Curve = sm2.P256Sm2()
	privateKey.PublicKey.Curve = privateKey.Curve
	privateKey.PublicKey.X, privateKey.PublicKey.Y = privateKey.Curve.ScalarBaseMult(privateKey.D.Bytes())

	// Decode SM2 public key
	publicKeyBytes, err := hex.DecodeString(publicKeyHex2)
	if err != nil {
		log.Fatalf("Failed to decode SM2 public key: %v", err)
	}
	publicKey := new(sm2.PublicKey)
	publicKey.Curve = sm2.P256Sm2()
	publicKey.X = new(big.Int).SetBytes(publicKeyBytes[1:33])
	publicKey.Y = new(big.Int).SetBytes(publicKeyBytes[33:65])

	// Decode SM4 secret key
	secretKey, err := hex.DecodeString(secretKeyHex)
	if err != nil {
		log.Fatalf("Failed to decode SM4 secret key: %v", err)
	}
	if len(secretKey) != 16 {
		log.Fatalf("Invalid SM4 secret key length: expected 16 bytes, got %d bytes", len(secretKey))
	}

	return &NationalSecretsUtils{
		PrivateKey2: privateKey,
		PublicKey2:  publicKey,
		SecretKey4:  secretKey,
	}
}

func (nsu *NationalSecretsUtils) GenerateSM2Sign(text string) []byte {
	signature, err := nsu.PrivateKey2.Sign(rand.Reader, []byte(text), nil)
	if err != nil {
		log.Fatalf("Failed to generate SM2 signature: %v", err)
	}
	return signature
}

func (nsu *NationalSecretsUtils) VerifySM2Sign(text string, signature []byte) bool {
	ok := nsu.PublicKey2.Verify([]byte(text), signature)
	return ok
}

func (nsu *NationalSecretsUtils) GenerateSM3Sign(text string) string {
	hash := sm3.New()
	hash.Write([]byte(text))
	return hex.EncodeToString(hash.Sum(nil))
}

func (nsu *NationalSecretsUtils) VerifySM3Sign(text string, sign string) bool {
	return nsu.GenerateSM3Sign(text) == sign
}

func (nsu *NationalSecretsUtils) GenerateSM4Sign(text string) string {
	cipher, err := sm4.NewCipher(nsu.SecretKey4)
	if err != nil {
		log.Fatalf("Failed to create SM4 cipher: %v", err)
	}

	plaintext := []byte(text)
	padding := sm4.BlockSize - len(plaintext)%sm4.BlockSize
	padText := bytes.Repeat([]byte{byte(padding)}, padding)
	plaintext = append(plaintext, padText...)

	ciphertext := make([]byte, len(plaintext))
	cipher.Encrypt(ciphertext, plaintext)
	return hex.EncodeToString(ciphertext)
}

func (nsu *NationalSecretsUtils) DecryptSM4Sign(ciphertextHex string) string {
	cipher, err := sm4.NewCipher(nsu.SecretKey4)
	if err != nil {
		log.Fatalf("Failed to create SM4 cipher: %v", err)
	}

	ciphertext, err := hex.DecodeString(ciphertextHex)
	if err != nil {
		log.Fatalf("Failed to decode hex string: %v", err)
	}

	plaintext := make([]byte, len(ciphertext))
	cipher.Decrypt(plaintext, ciphertext)

	padding := int(plaintext[len(plaintext)-1])
	return string(plaintext[:len(plaintext)-padding])
}

func main() {
	// 自定义的密钥,可以替换成实际使用的密钥
	privateKeyHex2 := "00b7e7af970096f98954bc2ef91a8b389fce9d79cf3eccec180db9ebf3782433e8"
	publicKeyHex2 := "046113da828b8e20d86db677663ba4886e2dbfdd6e44c2c356243c71d9efc204cf590523f3ef1f53bd19c8235876647b235883744f058e35b37ec6e14f6c9040c3"
	secretKeyHex := "07bcea7a6dc9154b145e2eb43c231817"

	nsu := NewNationalSecretsUtils(privateKeyHex2, publicKeyHex2, secretKeyHex)

	fmt.Printf("sm2公钥: %s\n", hex.EncodeToString(nsu.PublicKey2.X.Bytes())+hex.EncodeToString(nsu.PublicKey2.Y.Bytes()))
	fmt.Printf("sm2私钥: %s\n", hex.EncodeToString(nsu.PrivateKey2.D.Bytes()))
	fmt.Printf("sm4密钥: %s\n", hex.EncodeToString(nsu.SecretKey4))

	// 测试SM2签名和验证
	sm2Sign := nsu.GenerateSM2Sign("我是sm2数据")
	fmt.Printf("sm2签名: %s\n", hex.EncodeToString(sm2Sign))
	fmt.Printf("sm2验证: %v\n", nsu.VerifySM2Sign("我是sm2数据", sm2Sign))

	// 测试SM4加解密
	sm4Encrypt := nsu.GenerateSM4Sign("我是sm4数据")
	fmt.Printf("sm4加密: %s\n", sm4Encrypt)
	fmt.Printf("sm4解密: %s\n", nsu.DecryptSM4Sign(sm4Encrypt))

	// 测试SM3哈希算法
	sm3Hash := nsu.GenerateSM3Sign("我是sm3数据")
	fmt.Printf("sm3哈希算法: %s\n", sm3Hash)
	fmt.Printf("sm3验证: %v\n", nsu.VerifySM3Sign("我是sm3数据", sm3Hash))
}

  • 7
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
国密SM2是中国自主研发的一种椭圆曲线公钥密码算法,广泛应用于数字签名、密钥交换、加密等领域。下面简单介绍一下基于国密SM2的数字签名技术。 数字签名是一种用于保证数字信息真实性、完整性和不可抵赖性的技术。基于国密SM2的数字签名技术采用了非对称加密算法,即使用一对公钥和私钥进行加密和解密。具体流程如下: 1. 密钥生成 首先,签名方需要生成一对公钥和私钥。公钥可以公开,私钥必须保密。在国密SM2算法中,公钥的长度为256位,私钥的长度为256位,其中一部分为随机数。 2. 数字签名 数字签名的过程可以分为四步: (1)计算消息的摘要值。使用SHA-256算法计算出消息的摘要值,摘要值的长度为256位。 (2)随机数生成。生成一个随机数k,保证其在区间[1,n-1]之间,n为SM2算法中的一个大素数。 (3)计算椭圆曲线上的点。根据摘要值和随机数k计算出椭圆曲线上的点R,其中R = [k]G,G为椭圆曲线上的基点。 (4)计算数字签名。根据摘要值、随机数k、私钥d和点R计算数字签名S,其中S = (hash(m) + x(R)*d)/(1+k)^(-1) mod n,x(R)表示点R的x坐标。 3. 数字签名验证 数字签名验证也可以分为四步: (1)计算消息的摘要值。使用SHA-256算法计算出消息的摘要值,摘要值的长度为256位。 (2)计算椭圆曲线上的点。根据签名中的点R和数字签名S计算出椭圆曲线上的点V,其中V = [h]P + [s]Q,h为摘要值的哈希值,P为公钥,Q为点R。 (3)验证数字签名。如果V的x坐标等于R的x坐标,则认为数字签名有效,否则认为数字签名无效。 (4)安全性检查。如果验证通过,还需要进行安全性检查,检查签名是否被篡改或重放攻击。 基于国密SM2的数字签名技术具有高强度、高安全性、高效率等优点,广泛应用于电子商务、金融支付、政府机构等领域。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

最难不过坚持丶渊洁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值