欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 八卦 > JAVA使用SM2算法生成密钥对加密解密加签验签

JAVA使用SM2算法生成密钥对加密解密加签验签

2024/10/25 1:25:51 来源:https://blog.csdn.net/qq_50801874/article/details/142795016  浏览:    关键词:JAVA使用SM2算法生成密钥对加密解密加签验签

简介

SM2是非对称加密算法,一提非对称加密算法,第一想到的是RSA,没错,这个就是替代RSA的。它是基于椭圆曲线密码的公钥密码算法标准,其秘钥长度256bit,包含数字签名、密钥交换和公钥加密,用于替换RSA/DH/ECDSA/ECDH等国际算法。可以满足电子认证服务系统等应用需求,由国家密码管理局于2010年12月17号发布。SM2采用的是ECC 256位的一种,其安全强度比RSA 2048位高,且运算速度快于RSA。随着密码技术和计算技术的发展,目前常用的1024位RSA算法面临严重的安全威胁,我们国家密码管理部门经过研究,决定采用SM2椭圆曲线算法替换RSA算法。SM2算法在安全性、性能上都具有优势。

用途

可以用于前后端传输数据加密解密。可以用于对数据加签验签,确保报文的安全性和完整性。比如,生成一套前端公私钥密钥对,生成一套后端服务器公私钥密钥对。前端把参数json字符串通过服务器公钥用sm2算法加密,服务器后端接收到请求后用服务器私钥解密,拿到原始参数,处理数据并生成响应数据,把响应数据用前端公钥加密,前端接收到响应加密后数据,用前端私钥解密,拿到响应json。这个过程是快速且安全的。(一般这个过程在网关上公共实现)

代码实现

引入依赖(maven引入bcprov-jdk15on jar包,截止发文,最新版本是1.70)

<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on -->
<dependency><groupId>org.bouncycastle</groupId><artifactId>bcprov-jdk15on</artifactId><version>1.70</version>
</dependency>

新建StandardSM2Engine实体类

package com.zhaohy.app.utils;import java.io.IOException;
import java.math.BigInteger;
import java.security.SecureRandom;import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
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.engines.SM2Engine.Mode;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECKeyParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.math.ec.ECFieldElement;
import org.bouncycastle.math.ec.ECMultiplier;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.math.ec.FixedPointCombMultiplier;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.BigIntegers;
import org.bouncycastle.util.Memoable;
import org.bouncycastle.util.Pack;/*** 自定义SM2Engine类,对加密解密数据进行了ASN.1编码*/
public class StandardSM2Engine {private final Digest digest;private final Mode mode;private boolean forEncryption;private ECKeyParameters ecKey;private ECDomainParameters ecParams;private int curveLength;private SecureRandom random;public StandardSM2Engine() {this(new SM3Digest());}public StandardSM2Engine(Mode mode) {this(new SM3Digest(), mode);}public StandardSM2Engine(Digest digest) {this(digest, Mode.C1C2C3);}public StandardSM2Engine(Digest digest, Mode mode) {if (mode == null) {throw new IllegalArgumentException("mode cannot be NULL");}this.digest = digest;this.mode = mode;}public void init(boolean forEncryption, CipherParameters param) {this.forEncryption = forEncryption;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;}public byte[] processBlock(byte[] in, int inOff, int inLen) throws InvalidCipherTextException {if (forEncryption) {return encrypt(in, inOff, inLen);} else {return decrypt(in, inOff, inLen);}}public int getOutputSize(int inputLen) {return (1 + 2 * curveLength) + inputLen + digest.getDigestSize();}protected ECMultiplier createBasePointMultiplier() {return new FixedPointCombMultiplier();}/*** 加密* * @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);ECMultiplier multiplier = createBasePointMultiplier();ECPoint c1P;ECPoint kPB;do {BigInteger k = nextK();c1P = multiplier.multiply(ecParams.getG(), k).normalize();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);return convertToASN1(c1P, c2, c3);}/*** 解密* * @param in* @param inOff* @param inLen* @return* @throws InvalidCipherTextException*/private byte[] decrypt(byte[] in, int inOff, int inLen) throws InvalidCipherTextException {byte[] decryptData = new byte[inLen];System.arraycopy(in, inOff, decryptData, 0, decryptData.length);BigInteger x;BigInteger y;byte[] originC3;byte[] c2;ECPoint c1P;byte[] c1;try (ASN1InputStream aIn = new ASN1InputStream(decryptData)) {ASN1Sequence seq;try {seq = (ASN1Sequence) aIn.readObject();} catch (IOException e) {throw new InvalidCipherTextException();}x = ASN1Integer.getInstance(seq.getObjectAt(0)).getValue();y = ASN1Integer.getInstance(seq.getObjectAt(1)).getValue();c1P = ecParams.getCurve().validatePoint(x, y);c1 = c1P.getEncoded(false);if (mode == Mode.C1C3C2) {originC3 = ASN1OctetString.getInstance(seq.getObjectAt(2)).getOctets();c2 = ASN1OctetString.getInstance(seq.getObjectAt(3)).getOctets();} else {c2 = ASN1OctetString.getInstance(seq.getObjectAt(2)).getOctets();originC3 = ASN1OctetString.getInstance(seq.getObjectAt(3)).getOctets();}} catch (IOException e) {throw new InvalidCipherTextException();}ECPoint s = c1P.multiply(ecParams.getH());if (s.isInfinity()) {throw new InvalidCipherTextException("[h]C1 at infinity");}c1P = c1P.multiply(((ECPrivateKeyParameters) ecKey).getD()).normalize();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;for (int i = 0; i != c3.length; i++) {check |= c3[i] ^ originC3[i];}Arrays.fill(c1, (byte) 0);Arrays.fill(c3, (byte) 0);if (check != 0) {Arrays.fill(c2, (byte) 0);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 + i]) {return false;}}return true;}private void kdf(Digest digest, ECPoint c1, byte[] encData) {int digestSize = digest.getDigestSize();byte[] buf = new byte[Math.max(4, digestSize)];int off = 0;Memoable memo = null;Memoable copy = null;if (digest instanceof Memoable) {addFieldElement(digest, c1.getAffineXCoord());addFieldElement(digest, c1.getAffineYCoord());memo = (Memoable) digest;copy = memo.copy();}int ct = 0;while (off < encData.length) {if (memo != null) {memo.reset(copy);} else {addFieldElement(digest, c1.getAffineXCoord());addFieldElement(digest, c1.getAffineYCoord());}Pack.intToBigEndian(++ct, buf, 0);digest.update(buf, 0, 4);digest.doFinal(buf, 0);int xorLen = Math.min(digestSize, encData.length - off);xor(encData, buf, off, xorLen);off += xorLen;}}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 = BigIntegers.createRandomBigInteger(qBitLength, random);} while (k.equals(BigIntegers.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);}private byte[] convertToASN1(ECPoint c1P, byte[] c2, byte[] c3) {ASN1Integer x = new ASN1Integer(c1P.getXCoord().toBigInteger());ASN1Integer y = new ASN1Integer(c1P.getYCoord().toBigInteger());DEROctetString derDig = new DEROctetString(c3);DEROctetString derEnc = new DEROctetString(c2);ASN1EncodableVector v = new ASN1EncodableVector();switch (mode) {case C1C3C2:v.add(x);v.add(y);v.add(derDig);v.add(derEnc);break;default:v.add(x);v.add(y);v.add(derEnc);v.add(derDig);}DERSequence seq = new DERSequence(v);try {return seq.getEncoded();} catch (IOException e) {throw new RuntimeException(e);}}}

新建Sm2Utils工具类

package com.zhaohy.app.utils;import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;/*** SM2工具类*/
public class Sm2Utils {/*** 加签* @param plainText* @return*/public static String sign(String plainText, String privateKeyStr) {BouncyCastleProvider provider = new BouncyCastleProvider();try {   // 获取椭圆曲线KEY生成器KeyFactory keyFactory = KeyFactory.getInstance("EC", provider);byte[] privateKeyData = Base64.getDecoder().decode(privateKeyStr);PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKeyData);Signature rsaSignature = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), provider);rsaSignature.initSign(keyFactory.generatePrivate(privateKeySpec));rsaSignature.update(plainText.getBytes());byte[] signed = rsaSignature.sign();return Base64.getEncoder().encodeToString(signed);} catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException | SignatureException e) {throw new RuntimeException(e);}}/*** 验签* @param plainText* @param signatureValue* @return*/public static boolean verify(String plainText, String signatureValue, String publicKeyStr) {BouncyCastleProvider provider = new BouncyCastleProvider();try {// 获取椭圆曲线KEY生成器KeyFactory keyFactory = KeyFactory.getInstance("EC", provider);byte[] publicKeyData = Base64.getDecoder().decode(publicKeyStr);X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyData);// 初始化为验签状态Signature signature = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), provider);signature.initVerify(keyFactory.generatePublic(publicKeySpec));signature.update(Hex.decodeHex(plainText.toCharArray()));return signature.verify(Hex.decodeHex(signatureValue.toCharArray()));} catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException | SignatureException e) {throw new RuntimeException(e);} catch (IllegalArgumentException e) {LogUtils.error("验签失败", e);return false;} catch (DecoderException e) {LogUtils.error("验签失败", e);return false;}}/*** 加密* @param plainText* @return*/public static byte[] encrypt(String plainText, String publicKeyStr) throws Exception {Security.addProvider(new BouncyCastleProvider());try {   // 获取椭圆曲线KEY生成器KeyFactory keyFactory = KeyFactory.getInstance("EC");byte[] publicKeyData = Base64.getDecoder().decode(publicKeyStr);X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyData);PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);CipherParameters publicKeyParamerters = ECUtil.generatePublicKeyParameter(publicKey);//数据加密StandardSM2Engine engine = new StandardSM2Engine(new SM3Digest(), SM2Engine.Mode.C1C3C2);engine.init(true, new ParametersWithRandom(publicKeyParamerters));byte[] encryptData = engine.processBlock(plainText.getBytes(), 0, plainText.getBytes().length);return encryptData;} catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException | InvalidCipherTextException e) {throw new RuntimeException(e);}}/*** 解密* @param encryptedText* @return*/public static String decrypt(byte[] encryptedData, String privateKeyStr) {Security.addProvider(new BouncyCastleProvider());try {   // 获取椭圆曲线KEY生成器KeyFactory keyFactory = KeyFactory.getInstance("EC");byte[] privateKeyData = Base64.getDecoder().decode(privateKeyStr);PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKeyData);PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);CipherParameters privateKeyParamerters = ECUtil.generatePrivateKeyParameter(privateKey);//数据解密StandardSM2Engine engine = new StandardSM2Engine(new SM3Digest(), SM2Engine.Mode.C1C3C2);engine.init(false, privateKeyParamerters);byte[] plainText = engine.processBlock(encryptedData, 0, encryptedData.length);return new String(plainText);} catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException | InvalidCipherTextException e) {throw new RuntimeException(e);}}/*** SM2算法生成密钥对* @return 密钥对信息*/public static KeyPair generateSm2KeyPair() {try {final ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");// 获取一个椭圆曲线类型的密钥对生成器final KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());SecureRandom random = new SecureRandom();// 使用SM2的算法区域初始化密钥生成器kpg.initialize(sm2Spec, random);// 获取密钥对KeyPair keyPair = kpg.generateKeyPair();return keyPair;} catch (Exception e) {LogUtils.error("generate sm2 key pair failed:{}", e.getMessage(), e);return null;}}public static void main(String[] args) throws Exception {KeyPair keyPair = generateSm2KeyPair();String privateKey = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded());String publicKey  = Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded());String data = "{\"daId\":\"123456\"}";String encryptedJsonStr =  Hex.encodeHexString(encrypt(data, publicKey)) + "";//16进制字符串String decryptedJsonStr = decrypt(Hex.decodeHex(encryptedJsonStr), privateKey);String sign = Hex.encodeHexString(Base64.getDecoder().decode(sign(data, privateKey)));boolean flag = verify(Hex.encodeHexString(data.getBytes()), sign, publicKey);System.out.println("base64后privateKey:" + privateKey);System.out.println("base64后publicKey:" + publicKey);System.out.println("加密前数据:" + data);System.out.println("公钥加密后16进制字符串:" + encryptedJsonStr);System.out.println("私钥解密后数据:" + decryptedJsonStr);System.out.println("私钥加签后数据(16进制):" + sign);System.out.println("公钥验签结果:" + flag);}}

工具类里已经写好了调用测试main方法,运行结果如下:

base64后privateKey:MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgmze2hmNoi3XpeMPmLNIQBOC7+P1iMbwGMMAdQXko0VWgCgYIKoEcz1UBgi2hRANCAATyF9jHsCvFaLkR4DS+viX9CShZEXc7Nc1OueDaIpzZx/DRTRejSpTOcFmb0B9TsyYutnRWAx46nfkQ289NVXjg
base64后publicKey:MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE8hfYx7ArxWi5EeA0vr4l/QkoWRF3OzXNTrng2iKc2cfw0U0Xo0qUznBZm9AfU7MmLrZ0VgMeOp35ENvPTVV44A==
加密前数据:{"daId":"123456"}
公钥加密后16进制字符串:307b022100f1ab8b4fc8755d2ab84bf530f15ab14f5250d060d52679fd38e822a85eeceb860221009eef85a579ef9e2f27f44461a89046b95f8c630773be7ced1f3f3f48057f4777042064d9b5fb396591e022791858e334fc40b1ee9c93412442d403385266a00dd78704117b30ed38afcb361f8176acbbfbabad71e9
私钥解密后数据:{"daId":"123456"}
私钥加签后数据(16进制):304402201505caa731d9b70987a6c19f40618593e9acd8cad880dc001e52ef4b48638e2302204bdb5c5dc881789e716de7360aa387e5e4caa9f9f2fd6d1fbce35f1227b34440
公钥验签结果:true

可以看到加密解密加签验签都是成功的。上面加密后生成的是byte[],转换成16进制字符串处理的,也可以用base64实现byte[]和字符串的转换处理,但是要保证对应加解密都是用base64实现byte[]和字符串的转换,对应的上就行。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com