最近看了一个项目的代码,用到了SM2,SM3,SM4
,瞬间懵逼,一会用SM2
,一会用SM3
,一会又用SM4
,SM
???
简单来说,SM2,SM3,SM4
是国家密码局认定的国产密码算法,当然除了这几个算法,还有SM1,SM7,SM9,祖冲之密码算法
等。
本文重点是SM
算法的Java
实现,并不是研究这几种算法的原理以及和其他算法的比较等。
这里简单介绍下这几种SM算法,先有个大概的印象:
它是一种对称加密算法
中的分组加密算法,分组长度、秘钥长度都是128位,和国际上通用的 AES, DES
一样,SM1 算法目前还没公开,只能集成在芯片中。
和国际上通过的RSA
一样,是一种非对称加密算法
,使用公钥加密,私钥解密,在安全性和运算速度方面要优于RSA算法。
可以用来生成信息摘要,如MD5,生成的信息摘要长度为256位。
是一种对称加密算法
,可用于替代DES/AES
等国际算法, SM4算法与AES算法具有相同的密钥长度和分组长度,都是128位。
当然这些算法的源码可以在商用密码检测中心
中下载,我已经下载好了,公众号回复【SM】即可下载。
了解了SM2, SM3, SM4
这些算法之后,在我们的系统中就可以应用这些算法来对传输的报文进行加解密;一个简单的流程图如下所示:
下面将来重点介绍SM2算法
SM2
算法是国家密码管理局发布的椭圆曲线公钥密码算法,推荐使用素数域256位椭圆曲线
。
椭圆曲线方程:
曲线参数为:
如果你数学够屌,文档给你准备好了,公众号回复【SM】即可下载。
既然SM2算法那么厉害,接下来我们就使用Java来实现下吧。
首先在项目中引入对应的jar包:
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.60</version>
</dependency>
首先第一步,是要创建 SM2 的椭圆曲线参数,用到的类为 「SM2P256V1Curve」,它是代表国密SM2推荐参数定义的椭圆曲线,源码如下:
可以看到和上面所提供的推荐参数是一样的。
由于「SM2P256V1Curve」没有实现Gx 和 Gy,即基点G的x,y坐标,我们需要自行创建,参数为推荐的参数即可,然后使用x,y 坐标创建基点G,代码如下:
第一步创建 SM2 的椭圆曲线参数的代码如下:
第二步,由于SM2算法基于 ECC,所以需要根据曲线参数来生成 ECC密钥对,代码如下:
第三步,通过 ECC 密钥对获取SM2公钥和私钥,代码如下:
第四步,获取到公钥之后,就可以使用公钥对报文进行加密了,代码如下:
第五步,加密之后,需要使用私钥来进行解密,代码如下:
第六步,现在来测试一下吧。
运行结果如下:
到这里使用Java来实现SM2加解密算法已经结束了,是不是很简单,你学废了吗?
完成代码如下:
生成SM2公私钥:
private static Map<String, ECKeyParameters> getSm2PrivateKeyOrPublicKey() {
// P256V1Curve 代表国密SM2推荐参数定义的椭圆曲线:
SM2P256V1Curve sm2P256V1Curve = new SM2P256V1Curve();
BigInteger sm2N = sm2P256V1Curve.getOrder();
BigInteger sm2H = sm2P256V1Curve.getCofactor();
// 由于 gx,gy 没有提供,需要自己创建默认参数
BigInteger sm2Gx = new BigInteger("32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16);
BigInteger sm2Gy = new BigInteger("BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16);
// 根据 gx, gy 坐标创建一个 G点,即为椭圆的基点
ECPoint sm2Gpoint = sm2P256V1Curve.createPoint(sm2Gx, sm2Gy);
//设置椭圆曲线参数
ECDomainParameters ecDomainParameters = new ECDomainParameters(sm2P256V1Curve, sm2Gpoint, sm2N, sm2H);
// 生成ECC密钥对
ECKeyGenerationParameters keyGenerationParameters = new ECKeyGenerationParameters(ecDomainParameters, new SecureRandom());
ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator();
keyPairGenerator.init(keyGenerationParameters);
// ECC密钥对
AsymmetricCipherKeyPair asymmetricCipherKeyPair = keyPairGenerator.generateKeyPair();
// 通过 ECC 密钥对获取SM2公钥和私钥
// 私钥
ECPrivateKeyParameters sm2PrivateKey = (ECPrivateKeyParameters) asymmetricCipherKeyPair.getPrivate();
String x = ByteUtils.toHexString(sm2PrivateKey.getParameters().getG().getAffineXCoord().getEncoded());
String y = ByteUtils.toHexString(sm2PrivateKey.getParameters().getG().getAffineYCoord().getEncoded());
System.out.println("生成的SM2私钥为:" + x + "-" + y);
// 公钥
ECPublicKeyParameters sm2PublicKey = (ECPublicKeyParameters) asymmetricCipherKeyPair.getPublic();
String x1 = ByteUtils.toHexString(sm2PublicKey.getQ().getAffineYCoord().getEncoded());
String y1 = ByteUtils.toHexString(sm2PublicKey.getQ().getAffineXCoord().getEncoded());
System.out.println("生成的SM2公钥为:" + x1 + "-" + y1);
Map<String, ECKeyParameters> map = new HashMap<>(2);
map.put("private", sm2PrivateKey);
map.put("public", sm2PublicKey);
return map;
}
加密:
/**
* 公钥加密
*
* @param sm2PublicKey SM2 公钥
* @param pack 加密的报文
* @return 加密后的保温
* @throws Exception e
*/
private static String sm2Encrypt(ECPublicKeyParameters sm2PublicKey, String pack) throws Exception {
SM2Engine sm2PublicEngine = new SM2Engine();
ParametersWithRandom withRandom = new ParametersWithRandom(sm2PublicKey, new SecureRandom());
sm2PublicEngine.init(true, withRandom);
byte[] encryResult = sm2PublicEngine.processBlock(pack.getBytes(StandardCharsets.UTF_8), 0, pack.getBytes(StandardCharsets.UTF_8).length);
String encryResultStr = Base64.getEncoder().encodeToString(encryResult);
System.out.println("加密后的报文为:" + encryResultStr);
return encryResultStr;
}
解密:
/**
* SM2 私钥解密
*
* @param sm2PrivateKey SM2 私钥
* @param pack 解密报文
* @throws Exception e
*/
private static void sm2Decrypt(ECPrivateKeyParameters sm2PrivateKey, String pack) throws Exception {
SM2Engine sm2PrivateEngine = new SM2Engine();
sm2PrivateEngine.init(false, sm2PrivateKey);
byte[] decodePackByte = Base64.getDecoder().decode(pack);
byte[] decryptResult = sm2PrivateEngine.processBlock(decodePackByte, 0, decodePackByte.length);
String decryptResultStr = new String(decryptResult, StandardCharsets.UTF_8);
System.out.println("解密后的报文为:" + decryptResultStr);
}
测试:
static {
Security.addProvider(new BouncyCastleProvider());
}
public static void main(String[] args) throws Exception {
Map<String, ECKeyParameters> sm2Key = getSm2PrivateKeyOrPublicKey();
String pack = "公众号搜索【Java技术编程】, 公众号搜索【Java技术编程】, 公众号搜索【Java技术编程】";
System.out.println("原始报文为:" + pack);
// 对报文进行加密
String encryResult = sm2Encrypt((ECPublicKeyParameters) sm2Key.get("public"), pack);
// 对报文进行解密
sm2Decrypt((ECPrivateKeyParameters) sm2Key.get("private"), encryResult);
}
下一篇,敬请期待。
下下一篇,敬请期待。