国密SM2算法简介
国密SM2算法是一种椭圆曲线公钥密码算法,其安全性基于椭圆曲线离散对数难题。该算法由国家密码管理局设计并公开,用于国家关键信息系统的数据加密、解密和数字签名等操作,是我国自主创新的一种密码算法。
一、SM2算法概述
SM2算法是一种基于椭圆曲线密码的公钥密码算法,其安全性主要基于椭圆曲线离散对数难题。该算法由国家密码管理局设计并公开,是我国自主创新的一种密码算法,可应用于数据加密、解密、数字签名等操作。SM2算法包括密钥生成算法、加密算法、解密算法和数字签名算法等部分。
二、SM2算法的应用场景
SM2算法作为一种公钥密码算法,可广泛应用于各种场景,如:
- 数据加密:SM2算法可用于加密和解密数据,保障数据传输的安全性。
- 数字签名:SM2算法可用于生成数字签名,验证文档或数据的完整性和真实性。
- 身份认证:SM2算法可用于身份认证,验证用户的身份信息。
- 网络安全:SM2算法可用于网络安全,保护网络传输的数据,防止被黑客攻击。
三、SM2算法的优势
SM2算法作为一种自主创新的密码算法,具有以下优势:
- 安全性高:SM2算法基于椭圆曲线离散对数难题,安全性较高,能够有效地防止黑客攻击。
- 效率高:SM2算法具有较高的运算效率,能够满足大量数据加密、解密和数字签名的需求。
- 灵活性好:SM2算法支持多种密钥长度,可根据实际需求灵活选择密钥长度,适用于不同的应用场景。
- 自主创新:SM2算法是我国自主创新的密码算法,具有独立的知识产权,能够保障国家关键信息系统的信息安全。
## 四、SM2算法的未来发展
总之,SM2算法是一种安全、高效的公钥密码算法,具有广泛的应用前景和未来发展潜力。在我国自主创新的道路上,SM2算法将继续发挥重要作用,为保障国家关键信息系统的信息安全做出更大的贡献。
国密算法Java版本
pom.xml 文件加入引用
<dependency><groupId>org.bouncycastle</groupId><artifactId>bcprov-jdk15to18</artifactId><version>1.69</version></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.10</version></dependency><!--以下包 可以适当引用--><dependency><groupId>com.squareup.okhttp3</groupId><artifactId>okhttp</artifactId><version>4.9.3</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.26</version></dependency>
SM2扩展类
这个类主要的的作用是对SM2 的扩展。
- 扩展SM2 加密排列方式C1C2C3 还是C1C3C2
- 扩展SM2 加签和验签算法SM3Digest
package com.yunce.ycsm;/** @Auther:徐志强* @Date:2023/9/11* @Description:* @VERSON:1.0*/import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.math.ec.ECConstants;
import org.bouncycastle.math.ec.ECFieldElement;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.BigIntegers;
import java.math.BigInteger;
import java.security.SecureRandom;/*** 对org.bouncycastle:bcprov-jdk15on:1.57扩展* <br/>BC库加密结果是按C1C2C3,国密标准是C1C3C2(加密芯片也是这个排列),* <br/>本扩展主要实现加密结果排列方式可选**/
public class SM2EngineExtend {private final Digest digest;/**是否为加密模式*/private boolean forEncryption;private ECKeyParameters ecKey;private ECDomainParameters ecParams;private int curveLength;private SecureRandom random;/**密文排序方式*/private int cipherMode;/**BC库默认排序方式-C1C2C3*/public static int CIPHERMODE_BC = 0;/**国密标准排序方式-C1C3C2*/public static int CIPHERMODE_NORM = 1;public SM2EngineExtend() {this(new SM3Digest());}public SM2EngineExtend(Digest digest) {this.digest = digest;}/*** 设置密文排序方式* @param cipherMode*/public void setCipherMode(int cipherMode){this.cipherMode = cipherMode;}/*** 默认初始化方法,使用国密排序标准* @param forEncryption - 是否以加密模式初始化* @param param - 曲线参数*/public void init(boolean forEncryption, CipherParameters param) {init(forEncryption, CIPHERMODE_NORM, param);}/*** 默认初始化方法,使用国密排序标准* @param forEncryption 是否以加密模式初始化* @param cipherMode 加密数据排列模式:1-标准排序;0-BC默认排序* @param param 曲线参数*/public void init(boolean forEncryption, int cipherMode, CipherParameters param) {this.forEncryption = forEncryption;this.cipherMode = cipherMode;if (forEncryption) {ParametersWithRandom rParam = (ParametersWithRandom) param;ecKey = (ECKeyParameters) rParam.getParameters();ecParams = ecKey.getParameters();ECPoint s = ((ECPublicKeyParameters) ecKey).getQ().multiply(ecParams.getH());if (s.isInfinity()) {throw new IllegalArgumentException("invalid key: [h]Q at infinity");}random = rParam.getRandom();} else {ecKey = (ECKeyParameters) param;ecParams = ecKey.getParameters();}curveLength = (ecParams.getCurve().getFieldSize() + 7) / 8;}/*** 加密或解密输入数据* @param in* @param inOff* @param inLen* @return* @throws InvalidCipherTextException*/public byte[] processBlock( byte[] in, int inOff, int inLen) throws InvalidCipherTextException {if (forEncryption) {// 加密return encrypt(in, inOff, inLen);} else {return decrypt(in, inOff, inLen);}}/*** 加密实现,根据cipherMode输出指定排列的结果,默认按标准方式排列* @param in* @param inOff* @param inLen* @return* @throws InvalidCipherTextException*/private byte[] encrypt(byte[] in, int inOff, int inLen)throws InvalidCipherTextException {byte[] c2 = new byte[inLen];System.arraycopy(in, inOff, c2, 0, c2.length);byte[] c1;ECPoint kPB;do {BigInteger k = nextK();ECPoint c1P = ecParams.getG().multiply(k).normalize();c1 = c1P.getEncoded(false);kPB = ((ECPublicKeyParameters) ecKey).getQ().multiply(k).normalize();kdf(digest, kPB, c2);}while (notEncrypted(c2, in, inOff));byte[] c3 = new byte[digest.getDigestSize()];addFieldElement(digest, kPB.getAffineXCoord());digest.update(in, inOff, inLen);addFieldElement(digest, kPB.getAffineYCoord());digest.doFinal(c3, 0);if (cipherMode == CIPHERMODE_NORM){return Arrays.concatenate(c1, c3, c2);}return Arrays.concatenate(c1, c2, c3);}/*** 解密实现,默认按标准排列方式解密,解密时解出c2部分原文并校验c3部分* @param in* @param inOff* @param inLen* @return* @throws InvalidCipherTextException*/private byte[] decrypt(byte[] in, int inOff, int inLen)throws InvalidCipherTextException {byte[] c1 = new byte[curveLength * 2 + 1];System.arraycopy(in, inOff, c1, 0, c1.length);ECPoint c1P = ecParams.getCurve().decodePoint(c1);ECPoint s = c1P.multiply(ecParams.getH());if (s.isInfinity()) {throw new InvalidCipherTextException("[h]C1 at infinity");}c1P = c1P.multiply(((ECPrivateKeyParameters) ecKey).getD()).normalize();byte[] c2 = new byte[inLen - c1.length - digest.getDigestSize()];if (cipherMode == CIPHERMODE_BC) {System.arraycopy(in, inOff + c1.length, c2, 0, c2.length);}else{// C1 C3 C2System.arraycopy(in, inOff + c1.length + digest.getDigestSize(), c2, 0, c2.length);}kdf(digest, c1P, c2);byte[] c3 = new byte[digest.getDigestSize()];addFieldElement(digest, c1P.getAffineXCoord());digest.update(c2, 0, c2.length);addFieldElement(digest, c1P.getAffineYCoord());digest.doFinal(c3, 0);int check = 0;// 检查密文输入值C3部分和由摘要生成的C3是否一致if (cipherMode == CIPHERMODE_BC) {for (int i = 0; i != c3.length; i++) {check |= c3[i] ^ in[c1.length + c2.length + i];}}else{for (int i = 0; i != c3.length; i++) {check |= c3[i] ^ in[c1.length + i];}}clearBlock(c1);clearBlock(c3);if (check != 0) {clearBlock(c2);throw new InvalidCipherTextException("invalid cipher text");}return c2;}private boolean notEncrypted(byte[] encData, byte[] in, int inOff) {for (int i = 0; i != encData.length; i++) {if (encData[i] != in[inOff]) {return false;}}return true;}private void kdf(Digest digest, ECPoint c1, byte[] encData) {int ct = 1;int v = digest.getDigestSize();byte[] buf = new byte[digest.getDigestSize()];int off = 0;for (int i = 1; i <= ((encData.length + v - 1) / v); i++) {addFieldElement(digest, c1.getAffineXCoord());addFieldElement(digest, c1.getAffineYCoord());digest.update((byte) (ct >> 24));digest.update((byte) (ct >> 16));digest.update((byte) (ct >> 8));digest.update((byte) ct);digest.doFinal(buf, 0);if (off + buf.length < encData.length) {xor(encData, buf, off, buf.length);} else {xor(encData, buf, off, encData.length - off);}off += buf.length;ct++;}}private void xor(byte[] data, byte[] kdfOut, int dOff, int dRemaining) {for (int i = 0; i != dRemaining; i++) {data[dOff + i] ^= kdfOut[i];}}private BigInteger nextK() {int qBitLength = ecParams.getN().bitLength();BigInteger k;do {k = new BigInteger(qBitLength, random);}while (k.equals(ECConstants.ZERO) || k.compareTo(ecParams.getN()) >= 0);return k;}private void addFieldElement(Digest digest, ECFieldElement v) {byte[] p = BigIntegers.asUnsignedByteArray(curveLength, v.toBigInteger());digest.update(p, 0, p.length);}/*** clear possible sensitive data*/private void clearBlock(byte[] block) {for (int i = 0; i != block.length; i++) {block[i] = 0;}}}
SM2 工具类
工具类实现以下功能
- 加密(排列算法设定)
- 解密
- 加签
- 验签
package yc.util.sm;
/** @Auther:徐志强* @Date:2023/9/11* @Description:* @VERSON:1.0*/import cn.hutool.crypto.asymmetric.SM2;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex;import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.SecureRandom;public class SM2Utils {/**sm2曲线参数名称*/public static final String CRYPTO_NAME_SM2 = "sm2p256v1";/*** SM2加密算法* @param publicKey 公钥* @param data 待加密的数据* @return 密文,BC库产生的密文带由04标识符,与非BC库对接时需要去掉开头的04*/public static String encrypt(String publicKey, String data) throws InvalidCipherTextException {// 按国密排序标准加密return encrypt(publicKey, data, SM2EngineExtend.CIPHERMODE_BC);}/*** SM2加密算法 (设置密文排列方式)* @param publicKey 公钥* @param data 待加密的数据* @param cipherMode 密文排列方式0-C1C2C3;1-C1C3C2;* @return 密文,BC库产生的密文带由04标识符,与非BC库对接时需要去掉开头的04*/public static String encrypt(String publicKey, String data, int cipherMode) throws InvalidCipherTextException {// 获取一条SM2曲线参数X9ECParameters sm2ECParameters = GMNamedCurves.getByName(CRYPTO_NAME_SM2);// 构造ECC算法参数,曲线方程、椭圆曲线G点、大整数NECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());//提取公钥点ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(Hex.decode(publicKey));// 公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥, 04的时候,可以去掉前面的04ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pukPoint, domainParameters);SM2EngineExtend sm2Engine = new SM2EngineExtend();// 设置sm2为加密模式sm2Engine.init(true, cipherMode, new ParametersWithRandom(publicKeyParameters, new SecureRandom()));byte[] arrayOfBytes = null;try {byte[] in = data.getBytes();arrayOfBytes = sm2Engine.processBlock(in, 0, in.length);} catch (Exception e) {throw e;}return Hex.toHexString(arrayOfBytes);}/*** SM2解密算法* @param privateKey 私钥* @param cipherData 密文数据* @description 密文数据以04开头,传入的密文前面没有04则补上* @return*/public static String decrypt(String privateKey, String cipherData) throws InvalidCipherTextException {// // 按国密排序标准解密return decrypt(privateKey, cipherData, SM2EngineExtend.CIPHERMODE_BC);}/*** SM2解密算法* @param privateKey 私钥* @param cipherData 密文数据* @param cipherMode 密文排列方式0-C1C2C3;1-C1C3C2;* @return*/public static String decrypt(String privateKey, String cipherData, int cipherMode) throws InvalidCipherTextException {// 使用BC库加解密时密文以04开头,传入的密文前面没有04则补上if (!cipherData.startsWith("04")){cipherData = "04" + cipherData;}byte[] cipherDataByte = Hex.decode(cipherData);//获取一条SM2曲线参数X9ECParameters sm2ECParameters = GMNamedCurves.getByName(CRYPTO_NAME_SM2);//构造domain参数ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());BigInteger privateKeyD = new BigInteger(privateKey, 16);ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(privateKeyD, domainParameters);SM2EngineExtend sm2Engine = new SM2EngineExtend();// 设置sm2为解密模式sm2Engine.init(false, cipherMode, privateKeyParameters);String result = "";try {byte[] arrayOfBytes = sm2Engine.processBlock(cipherDataByte, 0, cipherDataByte.length);return new String(arrayOfBytes);} catch (Exception e) {throw e;}}/*** SM2 加签* @param privateKey 承建商私钥* @param id 授权码* @param source 16进制加密原文* @return 16进制签名*/public static String sign(String privateKey,String id, String source) throws UnsupportedEncodingException {SM2 sm2= new SM2(privateKey,null);String id16=Hex.toHexString(id.getBytes("utf-8"));String source16=Hex.toHexString(source.getBytes("utf-8"));String sign= sm2.signHex(source16 ,id16);return sign;}/*** 校验签名* @param publicKey 民科提供公钥* @param data 16进制加密原文* @param signHex 16进制签名* @param id 授权码* @return*/public static boolean verifysign(String publicKey,String data, String signHex, String id) throws UnsupportedEncodingException {SM2 sm = new SM2(null,publicKey);String id16=Hex.toHexString(id.getBytes("utf-8"));String source16=Hex.toHexString(data.getBytes("utf-8"));boolean isSign= sm.verifyHex(data,signHex,id);return isSign;}
}
加密示例(单元测试)
1.对“Hello Word”进行一系列的SM2操作。
加密-单元测试
package yc.util.sm;/** @Auther:徐志强* @Date:2023/12/18* @Description:* @VERSON:1.0*/
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;public class SM2UtilsTest {@Testpublic void testEncrypt() {try {String publicKey = "04979c0b359aba90a846e7724ab66621c79af8095a459e5ca981845952d865403f8644c5e79eb7d8ea0bcb9c2fb53f82e8e1108a85d96a3e1d4e687bb91a622ea4";String data = "hello word";/*密文排列方式0-C1C2C3;1-C1C3C2;*/String result = SM2Utils.encrypt(publicKey, data, 1);System.out.println("加密后:");System.out.println(result);} catch (InvalidCipherTextException e) {// Handle exception if necessary}}
}
加密后:
044e6f524065ebd5a758eaa9a8278a4bf51a49bc043ddfcf7b7ed5fe4475fd36b66077618af044acff157b065ba02045baaa75ff23efa7d366228ed5006f3b8b52f72db1811064e8485337708044b2d30d13691513ba0a939c19d1e8dc3faa4089862cc33f01d4b3e6b818
解密-单元测试
String privateKey = "00e5f6e0d04985ba1ced8c6fef49c5dd3dd84f6542d64cafb6ffde94a433392e3c";String cipherData = "04972df81d2685884f0dd3a20cf9dc743203bf66e00c98b9761b7a569b27c1231ce271329c6121704e8e59df9a080f982390b8901829f3e6fc1ac3a2d0b7bfac7c0634d8b95dfdbf316922762a874b3708dc2ce7e7dd5a49a667d15f878ef85437f68f10d8bc05d5b5aede";int cipherMode = 1;String result = SM2Utils.decrypt(privateKey, cipherData, cipherMode);System.out.println("解密后:");System.out.println(result);
加签-单元测试
@Testpublic void testSign() throws UnsupportedEncodingException, UnsupportedEncodingException {String privateKey = "00e5f6e0d04985ba1ced8c6fef49c5dd3dd84f6542d64cafb6ffde94a433392e3c";String id = "admin";String source = "Hello Word";String actualSign = SM2Utils.sign(privateKey, id, source);System.out.println("加签:");System.out.println(actualSign );}
验签-单元测试
@Testpublic void testVerifysign() throws UnsupportedEncodingException {String publicKey = "04979c0b359aba90a846e7724ab66621c79af8095a459e5ca981845952d865403f8644c5e79eb7d8ea0bcb9c2fb53f82e8e1108a85d96a3e1d4e687bb91a622ea4";String data = "Hello Word";String signHex = "3046022100d8edc9c5f73d7845cec3e29b7a3196b4b21c9a1f2b9edf93d33fe4cb4f8c45bd022100b6c382a4bb72e3290fea140787a09df4e6ad5174debe03b8026bcc7e43369c79";String id = "admin";boolean result = SM2Utils.verifysign(publicKey, data, signHex, id);System.out.println(result);Assert.isTrue(result);}