前言
国密即国家密码局认定的国产密码算法。主要有SM1,SM2,SM3,SM4。密钥长度和分组长度均为128位。
SM1 为对称加密。其加密强度与AES相当。该算法不公开,调用该算法时,需要通过加密芯片的接口进行调用。
SM2为非对称加密,基于ECC。该算法已公开。由于该算法基于ECC,故其签名速度与秘钥生成速度都快于RSA。ECC 256位(SM2采用的就是ECC 256位的一种)安全强度比RSA 2048位高,但运算速度快于RSA。
SM3 加密不可逆算法。可以用MD5作为对比理解,但MD5已被破解,故安全性比MD5高。该算法已公开。校验结果为256位。
SM4 无线局域网标准的分组数据算法。对称加密,密钥长度和分组长度均为128位。
一、SM2、SM3:
1、SM2是一种非对称加密,类似RSA,既需要使用公私钥对来进行加密;
SM2相比RSA:公私钥对更短的同时,安全性更高,加解密比RSA更快;
2、SM3是一种散列算法,类似MD5,散列后得到固定长度的密文
具体使用流程如下:1、获取原文str
(1.5、很多公司会把原文进行一次SM3散列(加密)一次得到固定长度的字符串作为新的原文)
2、对原文str使用私钥进行加密,得到密文sign
3、使用公钥、原文str、密文sign进行验证
在这里插入代码片
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.math.ec.custom.gm.SM2P256V1Curve;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.Base64;
public class SmxUtils {
/**
* 以下为SM2推荐曲线参数
*/
public static final SM2P256V1Curve CURVE = new SM2P256V1Curve();
public final static BigInteger SM2_ECC_N = CURVE.getOrder();
public final static BigInteger SM2_ECC_H = CURVE.getCofactor();
public final static BigInteger SM2_ECC_GX = new BigInteger(
"32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16);
public final static BigInteger SM2_ECC_GY = new BigInteger(
"BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16);
public static final ECPoint G_POINT = CURVE.createPoint(SM2_ECC_GX, SM2_ECC_GY);
public static final ECDomainParameters DOMAIN_PARAMS = new ECDomainParameters(CURVE, G_POINT,
SM2_ECC_N, SM2_ECC_H);
/**
* End: SM2推荐曲线参数
*/
public static final String FLAG_BIT = "04";
/**
* 加签
* @param signData sm3散列的密文
* @return
*/
public static String sign(String privateKey, String signData) {
byte[] sm2Cipher = Base64.getDecoder().decode(signData);
ECPrivateKeyParameters priKeyParameters = new ECPrivateKeyParameters(new BigInteger(privateKey,16), DOMAIN_PARAMS);
byte[] signByte = null;
try {
signByte = sign(priKeyParameters,sm2Cipher);
} catch (CryptoException e) {
System.out.println(e.getMessage());
}
return Base64.getEncoder().encodeToString(signByte);
}
private static byte[] sign(ECPrivateKeyParameters priKeyParameters, byte[] srcData) throws CryptoException {
SM2Signer signer = new SM2Signer();
CipherParameters param = new ParametersWithRandom(priKeyParameters, new SecureRandom());
signer.init(true, param);
signer.update(srcData, 0, srcData.length);
return signer.generateSignature();
}
/**
* 验签
* @param srcData sm3散列后的明文
* @param sign sm2签名
*/
public static boolean verify(String publicKey, String srcData, String sign) {
if(publicKey.startsWith(FLAG_BIT)){
publicKey = publicKey.substring(2);
}
assert publicKey != null;
String publicKeyX = publicKey.substring(0,publicKey.length()/2);
String publicKeyY = publicKey.substring(publicKey.length()/2);
byte[] srcDataByte = Base64.getDecoder().decode(srcData);
byte[] signByte = Base64.getDecoder().decode(sign);
ECPublicKeyParameters pubKeyParameters = createECPublicKeyParameters(publicKeyX, publicKeyY, CURVE, DOMAIN_PARAMS);
return verify(pubKeyParameters, srcDataByte, signByte);
}
private static boolean verify(ECPublicKeyParameters pubKeyParameters, byte[] srcData, byte[] sign) {
SM2Signer signer = new SM2Signer();
CipherParameters param = pubKeyParameters;
signer.init(false, param);
signer.update(srcData, 0, srcData.length);
return signer.verifySignature(sign);
}
private static ECPublicKeyParameters createECPublicKeyParameters(String xHex, String yHex,
ECCurve curve, ECDomainParameters domainParameters) {
return createECPublicKeyParameters(ByteUtils.fromHexString(xHex), ByteUtils.fromHexString(yHex),
curve, domainParameters);
}
private static ECPublicKeyParameters createECPublicKeyParameters(byte[] xBytes, byte[] yBytes,
ECCurve curve, ECDomainParameters domainParameters) {
final byte uncompressedFlag = 0x04;
int curveLength = (domainParameters.getCurve().getFieldSize() + 7) / 8;
xBytes = fixToCurveLengthBytes(curveLength, xBytes);
yBytes = fixToCurveLengthBytes(curveLength, yBytes);
byte[] encodedPubKey = new byte[1 + xBytes.length + yBytes.length];
encodedPubKey[0] = uncompressedFlag;
System.arraycopy(xBytes, 0, encodedPubKey, 1, xBytes.length);
System.arraycopy(yBytes, 0, encodedPubKey, 1 + xBytes.length, yBytes.length);
return new ECPublicKeyParameters(curve.decodePoint(encodedPubKey), domainParameters);
}
private static byte[] fixToCurveLengthBytes(int curveLength, byte[] src) {
if (src.length == curveLength) {
return src;
}
byte[] result = new byte[curveLength];
if (src.length > curveLength) {
System.arraycopy(src, src.length - result.length, result, 0, result.length);
} else {
System.arraycopy(src, 0, result, result.length - src.length, src.length);
}
return result;
}
/**
* 生成ECC密钥对
*
* @return ECC密钥对
*/
public static void generateKeyPairParameter() {
SecureRandom random = new SecureRandom();
AsymmetricCipherKeyPair keyPair = generateKeyPairParameter(DOMAIN_PARAMS, random);
ECPrivateKeyParameters priKey = (ECPrivateKeyParameters) keyPair.getPrivate();
ECPublicKeyParameters pubKey = (ECPublicKeyParameters) keyPair.getPublic();
String privateKey = ByteUtils.toHexString(priKey.getD().toByteArray()).toUpperCase();
String publicKey = ByteUtils.toHexString(pubKey.getQ().getEncoded(false)).toUpperCase();
System.out.println(privateKey);
System.out.println(publicKey);
}
public static AsymmetricCipherKeyPair generateKeyPairParameter(ECDomainParameters domainParameters,
SecureRandom random) {
ECKeyGenerationParameters keyGenerationParams = new ECKeyGenerationParameters(domainParameters,
random);
ECKeyPairGenerator keyGen = new ECKeyPairGenerator();
keyGen.init(keyGenerationParams);
return keyGen.generateKeyPair();
}
/**
* sm3哈希
* @param srcData
* @return
*/
public static String sm3Hash(byte[] srcData) {
SM3Digest digest = new SM3Digest();
digest.update(srcData, 0, srcData.length);
byte[] hash = new byte[digest.getDigestSize()];
digest.doFinal(hash, 0);
return ByteUtils.toHexString(hash);
}
}
接下来我们看看如何使用这个工具类
1、生成SM2公私钥对:
注:生成的公钥都以04开头,且比私钥长
public static void main(String[] args) {
generateKeyPairParameter();
}
结果:
私钥:00994EAF2CFCD81798B33E3F28528B2171A84A830580E7A82B4BCACF55535EE3F9
公钥:04987C490335D2283B2128F9A29E57F63D89D1954060F12B82201D2C8A3E307406DA715360D842E62F38C978B8E05FC7CEC6F06FFB66856D253FD8713739F09ECF
2、进行加签验签
以下示例是搭配sm3散列(加密)的标准使用流程,如无需要,可以不对原文进行散列:
public static void main(String[] args) {
String privateKey = "00991AAA8FFB2D4A2FB233DBBA9C6955E89C040D23A19A5C28B9584361F84D9E80";
String publicKey = "049F294C6D8DB6C5660012EF80C8EC4A5411AB2AC6AA64DD367027F4AEF0E6F97D43B362BDD56204BCDFC53C7151F9F23BFC8671AB088724C88F4F44A69080E9D6";
System.out.println();
System.out.println("私钥:" + privateKey);
System.out.println("公钥:" + publicKey);
System.out.println("私钥长度--------------" + privateKey.getBytes().length);
System.out.println("公钥长度--------------" + publicKey.getBytes().length);
String requestContent = "{\"outTradeNo\":\"jkjh20231228024545123456\",\"payWay\":\"cmpay\",\"scene\":\"wap\",\"buyerId\":\"\",\"totalAmount\":0.01,\"realAmount\":0.01,\"discountableAmount\":0,\"subject\":\"test\",\"productCode\":\"000001\",\"productName\":\"test\",\"productDesc\":\"test\",\"productUrl\":\"http://\",\"clientIp\":\"8.8.8.8\",\"wechatOpenId\":\"\",\"tradeDate\":\"20231228\",\"authCode\":\"\",\"hallAreaCode\":\"0001\",\"hallCode\":\"0002\",\"terminalCode\":\"sb001\",\"clerkCode\":\"0003\",\"hallWindowCode\":\"0004\",\"notifyUrl\":\"http://\",\"operatorId\":\"99900000002\",\"timeoutExpress\":\"30m\",\"pageNotifyUrl\":\"http://\",\"extra\":\"test\",\"appId\":\"\",\"bankAbbreviation\":\"ICBC\",\"bankCardType\":\"\",\"mobileNumber\":\"\",\"packageCode\":\"\",\"provinceCode\":\"\",\"packageDiscountAmount\":0.01,\"packagePeriod\":\"12\",\"packageLevel\":128,\"packageProductAmount\":0.01,\"creditChannel\":\"\",\"specifiedChannel\":\"\",\"rechargeIp\":\"8.8.8.8\",\"discountCode\":\"\",\"payChannelFlag\":\"\",\"promoParams\":\"\",\"subMerchant\":\"\",\"settlementDept\":\"\",\"settlementItem\":\"\",\"merchantChannelType\":\"\",\"merchantId\":\"7770000000096133\",\"method\":\"trade.payment\",\"format\":\"JSON\",\"signType\":\"SM2\",\"version\":\"1.0.0\"}";
// 对原文进行一次sm3加密
String sm3Sign = SmxUtils.sm3Hash(requestContent.getBytes());
System.out.println("sm3加密后的值为:"+ sm3Sign);
String sm2Sign = SmxUtils.sign(privateKey, sm3Sign);
System.out.println("SM2签名后的值为:"+ sm2Sign);
boolean verifyFlag= SmxUtils.verify(publicKey, sm3Sign, sm2Sign);
System.out.println("SM2验签是否通过:"+verifyFlag);
}
以下是结果:
私钥:00991AAA8FFB2D4A2FB233DBBA9C6955E89C040D23A19A5C28B9584361F84D9E80
公钥:049F294C6D8DB6C5660012EF80C8EC4A5411AB2AC6AA64DD367027F4AEF0E6F97D43B362BDD56204BCDFC53C7151F9F23BFC8671AB088724C88F4F44A69080E9D6
私钥长度--------------66
公钥长度--------------130
sm3加密后的值为:3c864cd3d85caef1fe1e4e9cb4eb4a374718f4fdfccd6c98ffe5ae7b94fe9f5b
SM2签名后的值为:MEUCIQDLssy7EUdcNcX6J/+0pU9Ky414g3EJ2Nu98td7yW9YpgIgF5SsjLfH70vq1IU8NSjPD3IQt3tgoNJrMBa54w3CTOg=
SM2验签是否通过:true
在真实使用过程中,己方使用己方的私钥进行加签,公钥给对方进行验签
二、SM4:
SM4是一种对称加密,即使用一个密钥,对一个原文进行加密,同时也可以用这个密钥进行解密;
这个密钥的生成方法网上有很多,16位英文、数字、某些特定字符组合而成
以下是工具类:
import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.*;
/**
* @date 2024-01-09 21:45
* @Author Wu_cm
* @Version 1.0
*/
public class Sm4Util {
public static final String ALGORITHM_NAME = "SM4";
public static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS5Padding";
/**
* 加密
* 加密模式:SM4/ECB/PKCS5Padding
*
* @param data 需要加密的内容
* @param key 加密密码
* @return
*/
public static String encrypt(String data, String key) {
try {
byte[] byteData = data.getBytes(StandardCharsets.UTF_8);
byte[] byteKey = key.getBytes(StandardCharsets.UTF_8);
byte[] plainText = encryptEcbPadding(byteKey, byteData);
return new String(Base64.encodeBase64(plainText));
} catch (Exception e) {
System.out.println("加密失败");
}
return null;
}
/**
* 解密
* 密文加密模式:SM4/ECB/PKCS5Padding
*
* @param data 需要解密的密文
* @param key 加密密码
* @return
*/
public static String decrypt(String data, String key) {
try {
byte[] byteData = Base64.decodeBase64(data);
byte[] byteKey = key.getBytes(StandardCharsets.UTF_8);
byte[] cipherText = decryptEcbPadding(byteKey, byteData);
return new String(cipherText, StandardCharsets.UTF_8);
} catch (Exception e) {
System.out.println("解密失败");
}
return null;
}
public static byte[] encryptEcbPadding(byte[] key, byte[] data)
throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException,
NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(data);
}
public static byte[] decryptEcbPadding(byte[] key, byte[] cipherText)
throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException,
NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException {
Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.DECRYPT_MODE, key);
return cipher.doFinal(cipherText);
}
private static Cipher generateEcbCipher(String algorithmName, int mode, byte[] key)
throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException,
InvalidKeyException {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
cipher.init(mode, sm4Key);
return cipher;
}
}
接下来看看如何使用:
public static void main(String[] args) {
String smxkey="0X163905EF10AC00";
String privateEncrypt = encrypt("abcdefg", smxkey);
String publicEncrypt = decrypt("zEQnadqzCLF4DR/VKB2+Uw==", smxkey);
System.out.println("加密后的密文:" + privateEncrypt);
System.out.println("解密后的明文:" + publicEncrypt);
}
SM4加解密,输出结果:
加密后的密文:zEQnadqzCLF4DR/VKB2+Uw==
解密后的明文:abcdefg
总结:SM1依赖物理芯片,不公开使用(非民用)
SM2是一种非对称加密,比RSA快,比RSA安全
SM3是一种加密不可逆算法,类似MD5,比MD5安全
SM4是一种对称加密,使用一个密钥对原文加密得到密文,也可以用密钥对密文解密得到原文
工具类&spm=1001.2101.3001.5002&articleId=135489783&d=1&t=3&u=11bdda078c224704ba7774cf5c222dba)
1904

被折叠的 条评论
为什么被折叠?



