Fork me on GitHub

SM2国密算法

SM2国密算法

SM2是中华人民共和国政府采用的一种公开密钥加密标准,由国家密码管理局于2010年12月17日发布,相关标准为“GM/T 0003-2012 《SM2椭圆曲线公钥密码算法》”。2016年,成为中国国家密码标准(GB/T 32918-2016)。

SM2算法和RSA算法都是公钥密码算法,SM2算法是一种更先进安全的算法,在我们国家商用密码体系中被用来替换RSA算法。
随着密码技术和计算机技术的发展,目前常用的1024位RSA算法面临严重的安全威胁,我们国家密码管理部门经过研究,决定采用SM2椭圆曲线算法替换RSA算法。

SM2和RSA对比

SM2性能更优更安全:密码复杂度高、处理速度快、机器性能消耗更小。
image

X.509公钥、PKCS#8私钥 与 裸公钥、裸私钥的区别

裸公钥与裸私钥

使用场景:
• 协议内部直接传输(如密钥交换)。
• 不包含算法和曲线信息,仅适合算法已知的上下文中使用。
优点:
• 简单高效,无额外开销。
缺点:
• 无法直接应用于需要算法标识的场景(如证书或加密工具)。

X.509 格式公钥

定义:
• 一种标准化的公钥封装格式,封装了裸公钥及其相关元数据(如算法标识)。
• 广泛用于数字证书、公钥基础设施(PKI)中。

SubjectPublicKeyInfo ::= SEQUENCE {
    algorithm AlgorithmIdentifier,     -- 算法标识
    subjectPublicKey BIT STRING        -- 裸公钥内容
}

• algorithm:指定算法和参数,例如: SM2 的算法标识 OID 为 1.2.156.10197.1.301。
• subjectPublicKey:封装的裸公钥数据,通常为未压缩格式。

编码形式:
• DER 格式:ASN.1 定义的二进制编码。
• PEM 格式:Base64 编码的 DER 数据,带有 -----BEGIN PUBLIC KEY----- 标签。

使用场景:
• 广泛应用于数字证书、SSL/TLS 等场景,需要明确公钥算法和曲线参数。

优点:
• 标准化,便于与 PKI 系统和加密工具集成。
• 包含算法信息,可用于多种上下文。

缺点:
• 相较裸公钥稍大,封装开销略高。

PKCS#8 格式私钥

定义:
• 一种标准化的私钥封装格式,封装了裸私钥及其元数据(如算法标识)。
• 常用于私钥文件存储和传输。

结构:
• 使用 ASN.1 格式定义的 PrivateKeyInfo 结构:

PrivateKeyInfo ::= SEQUENCE {
    version Version,                   -- 版本号(通常为 0)
    privateKeyAlgorithm AlgorithmIdentifier, -- 算法标识
    privateKey OCTET STRING,           -- 裸私钥内容
    attributes [0] IMPLICIT Attributes OPTIONAL -- 可选属性
}

• privateKeyAlgorithm:指定私钥使用的算法,例如:SM2 的算法标识 OID 为 1.2.156.10197.1.301。
• privateKey:封装的裸私钥数据,通常为 32 字节。

编码形式:
• DER 格式:ASN.1 定义的二进制编码。
• PEM 格式:Base64 编码的 DER 数据,带有 -----BEGIN PRIVATE KEY----- 标签。

使用场景:
• 私钥存储文件(如 .pem 文件)。
• 适用于标准化工具和 API 的场景。

优点:
• 标准化,适用于多种加密库和工具。
• 包含算法信息,便于跨平台使用。

缺点:
• 相较裸私钥稍大,封装开销略高。

选择建议

•	裸公钥/裸私钥:效率优先,适合内部使用或上下文明确的场景。
•	X.509 公钥/PKCS#8 私钥:标准化应用场景,适合证书、文件存储和跨平台使用。

Java代码

package com.autumn.util;

import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.crypto.util.PrivateKeyInfoFactory;
import org.bouncycastle.crypto.util.SubjectPublicKeyInfoFactory;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Base64;

import java.math.BigInteger;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

/**
 * SM2国密算法
 *
 * 密钥钥编码形式:
 * 原始公钥 -》 原始私钥
 * PEM文件格式: 公钥X.509 -》 私钥PKCS#8
 * DER文件格式: 私钥按照 ASN.1 规则进行二进制编码,通常结合 PKCS#8 使用
 *
 * PKCS#7 常用的后缀是: .P7B .P7C .SPC
 * PKCS#12 常用的后缀有: .P12 .PFX
 * X.509 DER 二进制编码(ASCII)的后缀是: .DER .CER .CRT
 * X.509 PEM Base64编码(Base64)的后缀是: .PEM .CER .CRT
 * .cer/.crt是用于存放证书,它是2进制形式存放的,不含私钥。
 */
public class Sm2EncryptionUtil {
    //base64裸密钥
    private static final String PUBLIC_KEY = "BJ0GzQWxQK2e2KjFBO/CqZ5/JNwhJge5szW3wxLoiwgKpqF6m/G6sjbuA4syR1fey3yIfnmw9Tmm6344UzyChk4=";
    //base64裸私钥
    private static final String PRIVATE_KEY = "Fd314wICBHdG5MwFFPzYNUb/DUppV6vIYj7hmpLqZPk=";

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    private static final X9ECParameters EC_PARAMETERS = GMNamedCurves.getByName("sm2p256v1");
    private static final ECDomainParameters DOMAIN_PARAMETERS = new ECDomainParameters(
            EC_PARAMETERS.getCurve(),
            EC_PARAMETERS.getG(),
            EC_PARAMETERS.getN(),
            EC_PARAMETERS.getH()
    );

    /**
     * SM2 加密
     *
     * @param data 原始数据
     * @param publicKeyString 公钥的base64字符串
     * @return 加密后的字符串
     * @throws Exception 加密异常
     */
    public static String encrypt(String data, String publicKeyString) throws Exception {
        if (publicKeyString == null || publicKeyString.isEmpty()) {
            throw new IllegalArgumentException("Public key string cannot be null or empty");
        }

        // 解码base64字符串公钥
        byte[] publicKeyBytes = Base64.decode(publicKeyString);
        // 提取 ECPoint
        ECPoint q = EC_PARAMETERS.getCurve().decodePoint(publicKeyBytes);

        if (q == null) {
            throw new IllegalArgumentException("Failed to decode public key point");
        }

        // 设置公钥参数
        ECPublicKeyParameters publicKeyParams = new ECPublicKeyParameters(q, DOMAIN_PARAMETERS);

        // 加密
        SM2Engine engine = new SM2Engine();
        engine.init(true, new ParametersWithRandom(publicKeyParams, new SecureRandom()));
        byte[] encryptedData = engine.processBlock(data.getBytes(), 0, data.getBytes().length);
        // 返回base64加密后的字符串
        return Base64.toBase64String(encryptedData);
    }

    /**
     * SM2 解密
     *
     * @param encryptedData 加密数据
     * @param privateKeyString 私钥的base64字符串
     * @return 解密后的字符串
     * @throws Exception 解密异常
     */
    public static String decrypt(String encryptedData, String privateKeyString) throws Exception {
        if (privateKeyString == null || privateKeyString.isEmpty()) {
            throw new IllegalArgumentException("Private key string cannot be null or empty");
        }

        // 解码私钥
        byte[] privateKeyBytes = Base64.decode(privateKeyString);
        BigInteger d = new BigInteger(1, privateKeyBytes);

        // 设置私钥参数
        ECPrivateKeyParameters privateKeyParams = new ECPrivateKeyParameters(d, DOMAIN_PARAMETERS);

        // 解密
        SM2Engine engine = new SM2Engine();
        engine.init(false, privateKeyParams);
        // 先base64解密,再用sm2解密
        byte[] decryptedData = engine.processBlock(Base64.decode(encryptedData), 0, Base64.decode(encryptedData).length);
        return new String(decryptedData);
    }

    // 从 X.509 格式的公钥中提取 ECPoint
    public static ECPoint getEcPointFromX509PublicKey(String publicKeyBase64) throws Exception {
        // 解码 BASE64 字符串
        byte[] publicKeyBytes = Base64.decode(publicKeyBase64);

        // 使用 KeyFactory 转换为 PublicKey
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
        PublicKey publicKey = keyFactory.generatePublic(keySpec);

        // 将 PublicKey 转换为 ECPublicKeySpec
        ECPublicKeySpec ecPublicKeySpec = (ECPublicKeySpec) keyFactory.getKeySpec(publicKey, ECPublicKeySpec.class);

        // 返回 ECPoint
        return ecPublicKeySpec.getQ();
    }

    /**
     * SM2 X509加密
     * @param data 原始数据
     * @param publicKeyString 公钥的base64字符串
     * @return 加密后的字符串
     * @throws Exception 加密异常
     */
    public static String encryptX509(String data, String publicKeyString) throws Exception {
        if (publicKeyString == null || publicKeyString.isEmpty()) {
            throw new IllegalArgumentException("Public key string cannot be null or empty");
        }

        // 原始的公钥使用base64字符串
        // byte[] publicKeyBytes = Base64.decode(publicKeyString);
        // X509公钥使用
        ECPoint q = getEcPointFromX509PublicKey(publicKeyString);

        if (q == null) {
            throw new IllegalArgumentException("Failed to decode public key point");
        }

        // 设置公钥参数
        ECPublicKeyParameters publicKeyParams = new ECPublicKeyParameters(q, DOMAIN_PARAMETERS);

        // 加密
        SM2Engine engine = new SM2Engine();
        engine.init(true, new ParametersWithRandom(publicKeyParams, new SecureRandom()));
        byte[] encryptedData = engine.processBlock(data.getBytes(), 0, data.getBytes().length);
        // 返回base64加密后的字符串
        return Base64.toBase64String(encryptedData);
    }

    //私钥是 PKCS#8 格式
    public static BigInteger getPrivateKeyScalar(String privateKeyBase64) throws Exception {
        // 解码 Base64 字符串
        byte[] privateKeyBytes = Base64.decode(privateKeyBase64);

        // 使用 KeyFactory 将其解析为 PrivateKey 对象
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
        PrivateKey privateKey = keyFactory.generatePrivate(keySpec);

        // 提取 ECPrivateKeySpec 并返回标量值
        ECPrivateKeySpec ecPrivateKeySpec = (ECPrivateKeySpec) keyFactory.getKeySpec(privateKey, ECPrivateKeySpec.class);
        return ecPrivateKeySpec.getD();
    }

    /**
     * SM2 使用PKCS#8 解密
     * @param encryptedData
     * @param privateKeyString
     * @return
     * @throws Exception
     */
    public static String decryptPKCS8(String encryptedData, String privateKeyString) throws Exception {
        if (privateKeyString == null || privateKeyString.isEmpty()) {
            throw new IllegalArgumentException("Private key string cannot be null or empty");
        }

        // 获取标量值
        BigInteger d = getPrivateKeyScalar(privateKeyString);

        // 验证范围
        if (d.compareTo(BigInteger.ONE) < 0 || d.compareTo(DOMAIN_PARAMETERS.getN().subtract(BigInteger.ONE)) >= 0) {
            throw new IllegalArgumentException("Private key is out of range [1, n-1]");
        }

        // 设置私钥参数
        ECPrivateKeyParameters privateKeyParams = new ECPrivateKeyParameters(d, DOMAIN_PARAMETERS);

        // 解密
        SM2Engine engine = new SM2Engine();
        engine.init(false, privateKeyParams);
        byte[] decryptedData = engine.processBlock(Base64.decode(encryptedData), 0, Base64.decode(encryptedData).length);
        return new String(decryptedData);
    }

    /**
     * 生成 SM2 密钥对 裸公钥 裸私钥
     * @return Map<String, String>
     * @throws Exception
     */
    public static Map<String, String> generateSm2KeyPair() throws Exception {
//        X9ECParameters ecParameters = org.bouncycastle.asn1.sec.SECNamedCurves.getByName("sm2p256v1");
        ECNamedCurveParameterSpec ecParameters = ECNamedCurveTable.getParameterSpec("sm2p256v1");
        if (ecParameters == null) {
            System.out.println("sm2p256v1 curve not found.");
            return null;
        }
        ECDomainParameters domainParameters = new ECDomainParameters(
                ecParameters.getCurve(),
                ecParameters.getG(),
                ecParameters.getN(),
                ecParameters.getH());

        ECKeyPairGenerator keyGen = new ECKeyPairGenerator();
        keyGen.init(new ECKeyGenerationParameters(domainParameters, new SecureRandom()));
        AsymmetricCipherKeyPair keyPair = keyGen.generateKeyPair();

        ECPrivateKeyParameters privateKeyParams = (ECPrivateKeyParameters) keyPair.getPrivate();
        ECPublicKeyParameters publicKeyParams = (ECPublicKeyParameters) keyPair.getPublic();

        String publicKey = Base64.toBase64String(publicKeyParams.getQ().getEncoded(false));
        String privateKey = Base64.toBase64String(privateKeyParams.getD().toByteArray());

        Map<String, String> keyMap = new HashMap<>();
        keyMap.put("publicKey", publicKey);
        keyMap.put("privateKey", privateKey);

        return keyMap;
    }


    /**
     * 生成 SM2 密钥对,并输出 X.509 公钥和 PKCS#8 私钥
     *
     * @return Map<String, String> 包含 X.509 格式的公钥和 PKCS#8 格式的私钥
     * @throws Exception
     */
    public static Map<String, String> generateX509Sm2KeyPair() throws Exception {
        // 获取 SM2 曲线参数
        ECNamedCurveParameterSpec ecParameters = ECNamedCurveTable.getParameterSpec("sm2p256v1");
        if (ecParameters == null) {
            throw new IllegalArgumentException("sm2p256v1 curve not found.");
        }

        ECDomainParameters domainParameters = new ECDomainParameters(
                ecParameters.getCurve(),
                ecParameters.getG(),
                ecParameters.getN(),
                ecParameters.getH());

        // 初始化密钥对生成器
        ECKeyPairGenerator keyGen = new ECKeyPairGenerator();
        keyGen.init(new ECKeyGenerationParameters(domainParameters, new SecureRandom()));
        AsymmetricCipherKeyPair keyPair = keyGen.generateKeyPair();

        // 获取私钥和公钥参数
        ECPrivateKeyParameters privateKeyParams = (ECPrivateKeyParameters) keyPair.getPrivate();
        ECPublicKeyParameters publicKeyParams = (ECPublicKeyParameters) keyPair.getPublic();

        // 转换公钥为 X.509 格式
        SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(publicKeyParams);
        String x509PublicKey =
                Base64.toBase64String(publicKeyInfo.getEncoded());

        // 转换私钥为 PKCS#8 格式
        PrivateKeyInfo privateKeyInfo = PrivateKeyInfoFactory.createPrivateKeyInfo(privateKeyParams);
        String pkcs8PrivateKey =
                Base64.toBase64String(privateKeyInfo.getEncoded());

        // 返回结果
        Map<String, String> keyMap = new HashMap<>();
        keyMap.put("publicKey", x509PublicKey);
        keyMap.put("privateKey", pkcs8PrivateKey);

        return keyMap;
    }

    public static void main(String[] args) throws Exception {
        //1.生成裸公钥 裸私钥
        Map<String, String> keyPair = generateSm2KeyPair();
        System.out.println("Generated Original Public Key: " + keyPair.get("publicKey"));
        System.out.println("Generated Original Private Key: " + keyPair.get("privateKey"));

        String data = "Hello, SM2!";
        String encryptedData = encrypt(data, keyPair.get("publicKey"));
        String decryptedData = decrypt(encryptedData, keyPair.get("privateKey"));

        System.out.println("Original Data: " + data);
        System.out.println("Encrypted Data: " + encryptedData);
        System.out.println("Decrypted Data: " + decryptedData);

        //2.生成X.509公钥和PKCS#8私钥
        Map<String, String> sm2KeyPair = generateX509Sm2KeyPair();
        System.out.println("X.509 公钥:\n" + "-----BEGIN PUBLIC KEY-----\n" +sm2KeyPair.get("publicKey")+"\n-----END PUBLIC KEY-----");
        System.out.println("PKCS#8 私钥:\n" +"-----BEGIN PRIVATE KEY-----\n" + sm2KeyPair.get("privateKey")+"\n-----END PRIVATE KEY-----");

        String encryptedData2 = encryptX509(data, sm2KeyPair.get("publicKey"));
        String decryptedData2 = decryptPKCS8(encryptedData2, sm2KeyPair.get("privateKey"));

        System.out.println("Original Data: " + data);
        System.out.println("X.509 公钥 Encrypted Data: " + encryptedData2);
        System.out.println("PKCS#8 私钥 Decrypted Data: " + decryptedData2);
    }
}

image

posted @ 2024-12-05 21:26  秋夜雨巷  阅读(43)  评论(0编辑  收藏  举报