国家商用密码
1、概述
国密即国家密码局认定的国产密码算法,即商用密码。商用密码,是指能够实现商用密码算法的加密、解密和认证等功能的技术。(包括密码算法编程技术和密码算法芯片、加密卡等的实现技术)。为了保障在金融、医疗等领域保障信息传输安全,国家商用密码管理办公室制定了一系列密码标准,包括SM1(SCB2)、SM2、SM3、SM4、SM7、SM9、祖冲之密码算法(ZUC)等。
SSF33、SM1、SM4、SM7是对称算法
SM2、SM9是非对称算法
SM3是哈希算法
1.1、SM1(不公开的分组对称加密算法,硬件使用)
SM1 算法是分组对称算法
,分组长度为128位
,密钥长度都为128比特
,算法安全保密强度及相关软硬件实现性能与 AES 相当,算法不公开,仅以 IP 核的形式存在于芯片中。
采用该算法已经研制了系列芯片、智能 IC 卡、智能密码钥匙、加密卡、加密机等安全产品,广泛应用于电子政务、电子商务及国民经济的各个应用领域(包括国家政务通、警务通等重要领域)。
SM1 为对称加密。其加密强度与AES相当。该算法不公开,调用该算法时,需要通过加密芯片的接口进行调用。
1.2、SM2(公开的非对称加密算法,替代RSA)
SM2算法是一种先进安全的公钥密码算法,在我们国家商用密码体系中被用来替换RSA算法。SM2算法就是ECC椭圆曲线密码机制,但在签名、密钥交换方面不同于ECDSA、ECDH等国际标准,而是采取了更为安全的机制。另外,SM2推荐了一条256位的曲线作为标准曲线。
SM2为非对称加密,基于ECC。该算法已公开。由于该算法基于ECC,故其签名速度与秘钥生成速度都快于RSA。ECC 256位(SM2采用的就是ECC 256位的一种)安全强度比RSA 2048位高,但运算速度快于RSA。
包括SM2-1
椭圆曲线数字签名算法,SM2-2
椭圆曲线密钥交换协议,SM2-3
椭圆曲线公钥加密算法,分别用于实现数字签名密钥协商和数据加密等功能
SM2算法与RSA算法不同的是,SM2算法是基于椭圆曲线上点群离散对数难题,相对于RSA算法,256位的SM2密码强度已经比2048位的RSA密码强度要高。
1.3、SM3(公开的哈希算法,用于消息摘要)
SM3是一种哈希算法,其算法本质是给数据加一个固定长度的指纹,这个固定长度就是256比特
。用于密码应用中的数字签名和验证、消息认证码的生成与验证以及随机数的生成,可满足多种密码应用的安全需求。
SM3 消息摘要。可以用MD5作为对比理解。该算法已公开。校验结果为256位。
适用于商用密码应用中的数字签名和验证消息认证码的生成与验证以及随机数的生成,可满足多种密码应用的安全需求。为了保证杂凑算法的安全性,其产生的杂凑值的长度不应太短。
1.4、SM4(公开的分组对称算法,替代3DES/AES)
SM4算法是一个分组算法
,用于无线局域网产品。该算法的分组长度为128比特
,密钥长度为128比特
。加密算法与密钥扩展算法都采用32轮非线性迭代构。解密算法与加密算法的结构相同,只是轮密钥的使用顺序相反,解密轮密钥是加密轮密钥的逆序。
用于实现数据的加密/解密运算,以保证数据和信息的机密性。要保证一个对称密码算法的安全性的基本条件是其具备足够的密钥长度,SM4算法与AES算法具有相同的密钥长度分组长度128比特,因此在安全性上高于3DES算法。
1.5、SM7(不公开的分组对称加密算法)
SM7 为分组加密算法,对称加密,该算法不公开,应用包括身份识别类应用(非接触式 IC 卡、门禁卡、工作证、参赛证等),票务类应用(大型赛事门票、展会门票等),支付与通卡类应用(积分消费卡、校园一卡通、企业一卡通等)。
1.6、SM9(公开的标识加密算法)
SM9 为标识加密算法(Identity-Based Cryptography),非对称加密,标识加密将用户的标识(如微信号、邮件地址、手机号码、QQ 号等)作为公钥,省略了交换数字证书和公钥过程,使得安全系统变得易于部署和管理,适用于互联网应用的各种新兴应用的安全保障,如基于云技术的密码服务、电子邮件安全、智能终端保护、物联网安全、云存储安全等等。这些安全应用可采用手机号码或邮件地址作为公钥,实现数据加密、身份认证、通话加密、通道加密等。在商用密码体系中,SM9 主要用于用户的身份认证,据新华网公开报道,SM9 的加密强度等同于 3072 位密钥的 RSA 加密算法。
1.7、ZUC 祖冲之算法(公开的流对称密码)
ZUC 为流密码算法,对称加密,该机密性算法可适用于 3GPP LTE 通信中的加密和解密,该算法包括祖冲之算法(ZUC)、机密性算法(128-EEA3)和完整性算法(128-EIA3)三个部分。已经被国际组织 3GPP 推荐为 4G 无线通信的第三套国际加密和完整性标准的候选算法。
2、对称加密(SM4)
对称加密使用的是SM4算法,由于是软件应用,硬件应用使用SM1算法。
密钥的长度必须是128位的,其他的长度会报异常。对称加密使用不同的模式,实现方式不同,本文实现了ECB和CBC两种。
2.1、依赖
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.60</version>
</dependency>
2.2、ECB加密模式
public class SM4ECBCoder {
//算法名称
public static final String ALGORITHM_NAME = "SM4";
//密钥长度
public static final int DEFAULT_KEYSIZE = 128;
//算法/填充模式/加密模式
public static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS7Padding";
/**
* 构建密钥
* @return
* @throws Exception
*/
public static byte[] generateKey() throws Exception {
Security.addProvider(new BouncyCastleProvider());
KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM_NAME, "BC");
kg.init(DEFAULT_KEYSIZE, new SecureRandom());
return kg.generateKey().getEncoded();
}
/**
* ECB模式加密
* @param key
* @param data
* @return
* @throws Exception
*/
public static byte[] encrypt(byte[] key, byte[] data) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM_NAME_ECB_PADDING, BouncyCastleProvider.PROVIDER_NAME);
Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
cipher.init(Cipher.ENCRYPT_MODE, sm4Key);
return cipher.doFinal(data);
}
/**
* 解密
* @param key
* @param cipherText
* @return
* @throws Exception
*/
public static byte[] decrypt(byte[] key, byte[] cipherText) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM_NAME_ECB_PADDING, BouncyCastleProvider.PROVIDER_NAME);
Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
cipher.init(Cipher.DECRYPT_MODE, sm4Key);
return cipher.doFinal(cipherText);
}
public static void main(String[] args) throws Exception {
String str = "hello world";
//生成密钥
byte[] key = generateKey();
System.out.println(HexUtil.encodeHexStr(key));
//加密
byte[] encrypt = encrypt(key, str.getBytes());
System.out.println(HexUtil.encodeHexStr(encrypt));
//解密
byte[] decrypt = decrypt(key, encrypt);
System.out.println(new String(decrypt));
}
}
ac47bfcf36ecd93d426fbb4cff0710d6
d57a26658aa3ff5a135f7c96f980badb
hello world
2.3、CBC加密模式
public class SM4CBCCoder {
//算法名称
public static final String ALGORITHM_NAME = "SM4";
//密钥长度
public static final int DEFAULT_KEYSIZE = 128;
//算法/填充模式/加密模式
public static final String ALGORITHM_NAME_ECB_PADDING = "SM4/CBC/PKCS7Padding";
//初始化向量,固定长度16bytes,CBC模式使用
private static final byte[] IV_PARAMETER = new byte[]{1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8};
/**
* 构建密钥
* @return
* @throws Exception
*/
public static byte[] generateKey() throws Exception {
Security.addProvider(new BouncyCastleProvider());
KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM_NAME, "BC");
kg.init(DEFAULT_KEYSIZE, new SecureRandom());
return kg.generateKey().getEncoded();
}
/**
* CBC模式加密
* @param key
* @param data
* @return
* @throws Exception
*/
public static byte[] encrypt(byte[] key, byte[] data) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM_NAME_ECB_PADDING, BouncyCastleProvider.PROVIDER_NAME);
Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
IvParameterSpec ivParameterSpec = new IvParameterSpec(IV_PARAMETER);
cipher.init(Cipher.ENCRYPT_MODE, sm4Key, ivParameterSpec);
return cipher.doFinal(data);
}
/**
* 解密
* @param key
* @param cipherText
* @return
* @throws Exception
*/
public static byte[] decrypt(byte[] key, byte[] cipherText) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM_NAME_ECB_PADDING, BouncyCastleProvider.PROVIDER_NAME);
Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
IvParameterSpec ivParameterSpec = new IvParameterSpec(IV_PARAMETER);
cipher.init(Cipher.DECRYPT_MODE, sm4Key, ivParameterSpec);
return cipher.doFinal(cipherText);
}
public static void main(String[] args) throws Exception {
String str = "hello world";
//生成密钥
byte[] key = generateKey();
System.out.println(HexUtil.encodeHexStr(key));
//iv向量
System.out.println(HexUtil.encodeHexStr(IV_PARAMETER));
//加密
byte[] encrypt = encrypt(key, str.getBytes());
System.out.println(HexUtil.encodeHexStr(encrypt));
//解密
byte[] decrypt = decrypt(key, encrypt);
System.out.println(new String(decrypt));
}
}
5aecf5dd99cb3107acd4f723f4061bc6
01020304050607080102030405060708
4cfb1c1be3e23692a79b213b48e9fe70
hello world
3、非对称加密(SM2)
3.1、算法原理
椭圆曲线定义:
SM2算法定义了两条椭圆曲线,一条基于F§上的素域曲线,一条基于F(2^m)上的拓域曲线,目前使用最多的曲线为素域曲线,本文介绍的算法基于素域曲线上的运算,素域曲线方程定义如下:
曲线参数定义:
SM2算法定义了5个默认参数,即有限域F§的规模p,椭圆曲线参数a,b,椭圆曲线的基点G(x,y),与G的阶n。算法标准给出的5个参数默认值为:
p:FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF
n:FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123
a:FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC
b:28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93
Gx:32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7
Gy:BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0
密钥结构定义:
SM2作为非对称算法拥有公钥与私钥,对于SM2中公私钥的结构如下:
- 私钥:
d
符合要求的32byte(256bit)随机数 - 公钥:
(x,y)
,实际一个坐标点,基于私钥d
计算所得
加密数据结构:
SM2加密数据使用公钥(x,y)进行加密,加密结果为c1c3c2
部分算法中定义为c1c2c3
,下面介绍密文中各个结构实际含义:
c1: 随机数K与G(x,y)的多倍点运算结果,结果也是一个点,记录为(kx,ky)
c2: 实际密文值
c3:使用SM3对于 kx||data||ky的hash值,在解密时校验解密结果是否正确
2.2、依赖
使用1.68版本,因为1.68版本添加了SM2算法的C1C3C2模式支持。
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15to18</artifactId>
<version>1.68</version>
</dependency>
2.3、实现
public class SM2Coder {
/**
* 以下为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);
/**
* 生成ECC密钥对
* @param domainParameters
* @param random
* @return
*/
public static AsymmetricCipherKeyPair generateKeyPair(ECDomainParameters domainParameters, SecureRandom random) {
ECKeyGenerationParameters keyGenerationParameters = new ECKeyGenerationParameters(domainParameters, random);
ECKeyPairGenerator kg = new ECKeyPairGenerator();
kg.init(keyGenerationParameters);
return kg.generateKeyPair();
}
public static AsymmetricCipherKeyPair generateKeyPair() {
SecureRandom random = new SecureRandom();
return generateKeyPair(DOMAIN_PARAMS, random);
}
/**
* 公钥加密,默认c1c2c3
* @param pubKey
* @param data
* @return
* @throws Exception
*/
public static byte[] encrypt(ECPublicKeyParameters pubKey, byte[] data) throws Exception {
SM2Engine engine = new SM2Engine(SM2Engine.Mode.C1C2C3);
ParametersWithRandom pwr = new ParametersWithRandom(pubKey, new SecureRandom());
engine.init(true, pwr);
return engine.processBlock(data, 0, data.length);
}
/**
* 私钥解密
* @param priKey
* @param data
* @return
* @throws Exception
*/
public static byte[] decrypt(ECPrivateKeyParameters priKey, byte[] data) throws Exception {
SM2Engine engine = new SM2Engine(SM2Engine.Mode.C1C2C3);
engine.init(false, priKey);
return engine.processBlock(data, 0, data.length);
}
/**
* 还原公钥
* @param xHex
* @param yHex
* @param curve
* @param domainParameters
* @return
*/
public static ECPublicKeyParameters createECPublicKeyParameters(String xHex, String yHex, ECCurve curve, ECDomainParameters domainParameters) {
byte[] xBytes = ByteUtils.fromHexString(xHex);
byte[] yBytes = ByteUtils.fromHexString(yHex);
return createECPublicKeyParameters(xBytes, yBytes, curve, domainParameters);
}
/**
* 还原公钥
* @param xBytes
* @param yBytes
* @param curve
* @param domainParameters
* @return
*/
public static ECPublicKeyParameters createECPublicKeyParameters(byte[] xBytes, byte[] yBytes, ECCurve curve, ECDomainParameters domainParameters) {
final byte uncompressedFlag = 0x04;
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);
}
/**
* 还原公钥
* @param hexPubKey
* @param curve
* @param domainParameters
* @return
*/
public static ECPublicKeyParameters createECPublicKeyParameters(String hexPubKey, ECCurve curve, ECDomainParameters domainParameters) {
return new ECPublicKeyParameters(curve.decodePoint(ByteUtils.fromHexString(hexPubKey)), domainParameters);
}
/**
* 还原私钥
* @param hexPriKey
* @param domainParameters
* @return
*/
public static ECPrivateKeyParameters createECPrivateKeyParameters(String hexPriKey, ECDomainParameters domainParameters) {
byte[] priKey = ByteUtils.fromHexString(hexPriKey);
return createECPrivateKeyParameters(priKey, domainParameters);
}
/**
* 还原私钥
* @param priKey
* @param domainParameters
* @return
*/
public static ECPrivateKeyParameters createECPrivateKeyParameters(byte[] priKey, ECDomainParameters domainParameters) {
return new ECPrivateKeyParameters(new BigInteger(priKey), domainParameters);
}
}
public class SM2CoderTest {
/**
* 加密/解密
* @throws Exception
*/
@Test
public void testEncryptAndDecrypt() throws Exception {
String str = "hello world";
//生成密钥对
AsymmetricCipherKeyPair keyPair = SM2Coder.generateKeyPair();
//公钥
ECPublicKeyParameters pubKey = (ECPublicKeyParameters) keyPair.getPublic();
byte[] encodedPubKey = pubKey.getQ().getEncoded(false);
System.out.println("公钥:"+ HexUtil.encodeHexStr(encodedPubKey));
//私钥
ECPrivateKeyParameters priKey = (ECPrivateKeyParameters) keyPair.getPrivate();
byte[] encodedPriKey = priKey.getD().toByteArray();
System.out.println("私钥:" + HexUtil.encodeHexStr(encodedPriKey));
//公钥加密
byte[] encrypt = SM2Coder.encrypt(pubKey, str.getBytes());
System.out.println(HexUtil.encodeHexStr(encrypt));
//私钥解密
byte[] decrypt = SM2Coder.decrypt(priKey, encrypt);
System.out.println(new String(decrypt));
}
/**
* 密钥恢复
*/
@Test
public void testSM2KeyRecovery() throws Exception {
String priHex = "00c68ea7c27b877f2149e7786e1edf6a2567ed04c27c9fa70c0a2733ba1a299324";
String encodedPubHex = "04" +
"3ea96fc0d4aeb234899965617f4727201ca6487aadf2e8f9b71d0f2858f7fd95" +
"df7f915ef43b7808cdf1bbba42f3e1d31145a8c0d15de047900815bdeaee2bf5";
String xHex = "3ea96fc0d4aeb234899965617f4727201ca6487aadf2e8f9b71d0f2858f7fd95";
String yHex = "df7f915ef43b7808cdf1bbba42f3e1d31145a8c0d15de047900815bdeaee2bf5";
String hexEncryptData = "04b2f752feb596f5c23ee1c9d1deabc8156c313a7da7dbeb07029a10e471165423e2527b2a5b27bb5072a9b5f3af0ddf3f3ea21585ee1f2ee5d2792ac29021685120745bf250d48eb22c1baa9fc3f5ce8365dac9a020db9c55ef87bb9642a53cc47f79e70f08c865a8109415";
//还原密钥
ECPrivateKeyParameters priKey = new ECPrivateKeyParameters(new BigInteger(ByteUtils.fromHexString(priHex)),
SM2Coder.DOMAIN_PARAMS);
ECPublicKeyParameters pubKey = SM2Coder.createECPublicKeyParameters(xHex, yHex, SM2Coder.CURVE,
SM2Coder.DOMAIN_PARAMS);
//私钥解密
byte[] decrypt = SM2Coder.decrypt(priKey, ByteUtils.fromHexString(hexEncryptData));
System.out.println(new String(decrypt));
}
}
4、报文摘要(SM3)
报文摘要与MD类似,只是算法不同。国密算法中SM3用作报文摘要。同样,由于报文摘要是不可逆的,所以只能是比对数字指纹。本文实现了两种,即hash和hmac。
4.1、依赖
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15to18</artifactId>
<version>1.68</version>
</dependency>
4.2、实现
public class SM3Util {
/**
* hash
* @param data
* @return
*/
public static byte[] hash(byte[] data) {
SM3Digest digest = new SM3Digest();
digest.update(data, 0, data.length);
//摘要长度32bytes
byte[] hash = new byte[digest.getDigestSize()];
digest.doFinal(hash, 0);
return hash;
}
/**
* Hmac
* @param key
* @param data
* @return
*/
public static byte[] hmac(byte[] key, byte[] data) {
KeyParameter keyParameter = new KeyParameter(key);
SM3Digest digest = new SM3Digest();
HMac mac = new HMac(digest);
mac.init(keyParameter);
mac.update(data, 0, data.length);
byte[] result = new byte[mac.getMacSize()];
mac.doFinal(result, 0);
return result;
}
public static void main(String[] args) {
String str = "hello world";
byte[] hmacKey = new byte[]{1, 2, 3, 4, 5, 6, 7, 8};
//hash
byte[] hash = hash(str.getBytes());
System.out.println(HexUtil.bytesToHexString(hash));
//hmac
byte[] hmac = hmac(hmacKey, str.getBytes());
System.out.println(HexUtil.bytesToHexString(hmac));
}
}
5、数字签名(SM2)
数字签名使用的算法是非对称加密算法,即SM2。
本文使用两种方式实现数字签名:
- 使用显式构建EC算法构造sm2椭圆曲线参数
- 直接使用默认的参数构建EC算法构造密钥对。
5.1、依赖
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15to18</artifactId>
<version>1.68</version>
</dependency>
5.2、实现一(构造sm2椭圆曲线参数)
public class SM2SignatureUtil {
/**
* 以下为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);
/**
* 使用显式构建EC算法构造sm2椭圆曲线参数
* @param domainParameters
* @param random
* @return
*/
public static AsymmetricCipherKeyPair generateKeyPair(ECDomainParameters domainParameters, SecureRandom random) {
//使用显式构建EC算法构造sm2椭圆曲线参数
ECKeyGenerationParameters keyGenerationParams = new ECKeyGenerationParameters(domainParameters, random);
ECKeyPairGenerator keyGen = new ECKeyPairGenerator();
keyGen.init(keyGenerationParams);
return keyGen.generateKeyPair();
}
/**
* 构造椭圆曲线参数
* @return
*/
public static AsymmetricCipherKeyPair generateKeyPair() {
SecureRandom random = new SecureRandom();
return generateKeyPair(DOMAIN_PARAMS, random);
}
/**
* 私钥签名
* @param privateKeyParameters
* @param withId 可以为null,若为null,则默认withId为字节数组:"1234567812345678".getBytes()
* @param data
* @return
* @throws Exception
*/
public static byte[] sign(ECPrivateKeyParameters privateKeyParameters, byte[] withId, byte[] data) throws Exception {
SM2Signer signer = new SM2Signer();
CipherParameters param = null;
ParametersWithRandom pwr = new ParametersWithRandom(privateKeyParameters, new SecureRandom());
if (withId != null) {
param = new ParametersWithID(pwr, withId);
} else {
param = pwr;
}
signer.init(true, param);
signer.update(data, 0, data.length);
return signer.generateSignature();
}
/**
* 公钥验证
* @param publicKeyParameters
* @param withId
* @param data
* @param sign
* @return
*/
public static boolean verify(ECPublicKeyParameters publicKeyParameters, byte[] withId, byte[] data, byte[] sign) {
SM2Signer signer = new SM2Signer();
CipherParameters param;
if (withId != null) {
param = new ParametersWithID(publicKeyParameters, withId);
} else {
param = publicKeyParameters;
}
signer.init(false, param);
signer.update(data, 0, data.length);
return signer.verifySignature(sign);
}
public static void main(String[] args) throws Exception {
Security.addProvider(new BouncyCastleProvider());
String str = "hello world";
//生成密钥对
AsymmetricCipherKeyPair keyPair = generateKeyPair();
//私钥签名
ECPrivateKeyParameters priKey = (ECPrivateKeyParameters) keyPair.getPrivate();
byte[] sign = sign(priKey, null, str.getBytes());
System.out.println(HexUtil.encodeHexStr(sign));
//公钥验证
ECPublicKeyParameters pubKey = (ECPublicKeyParameters) keyPair.getPublic();
boolean verify = verify(pubKey, null, str.getBytes(), sign);
System.out.println(verify);
}
}
5.3、实现二(构造密钥对)
public class SM2SignatureUtil2 {
/**
* 以下为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);
private static final String ALGO_NAME_EC = "EC";
/**
* 直接使用默认的参数构建EC算法构造密钥对
* @return
* @throws Exception
*/
public static KeyPair generateBCECKeyPair() throws Exception {
KeyPairGenerator kpg = KeyPairGenerator.getInstance(ALGO_NAME_EC, BouncyCastleProvider.PROVIDER_NAME);
SecureRandom random = new SecureRandom();
ECParameterSpec parameterSpec = new ECParameterSpec(CURVE, G_POINT, SM2_ECC_N, SM2_ECC_H);
kpg.initialize(parameterSpec, random);
return kpg.genKeyPair();
}
/**
* 私钥签名
* @param privateKeyParameters
* @param withId 可以为null,若为null,则默认withId为字节数组:"1234567812345678".getBytes()
* @param data
* @return
* @throws Exception
*/
public static byte[] sign(ECPrivateKeyParameters privateKeyParameters, byte[] withId, byte[] data) throws Exception {
SM2Signer signer = new SM2Signer();
CipherParameters param = null;
ParametersWithRandom pwr = new ParametersWithRandom(privateKeyParameters, new SecureRandom());
if (withId != null) {
param = new ParametersWithID(pwr, withId);
} else {
param = pwr;
}
signer.init(true, param);
signer.update(data, 0, data.length);
return signer.generateSignature();
}
/**
* 公钥验证
* @param publicKeyParameters
* @param withId
* @param data
* @param sign
* @return
*/
public static boolean verify(ECPublicKeyParameters publicKeyParameters, byte[] withId, byte[] data, byte[] sign) {
SM2Signer signer = new SM2Signer();
CipherParameters param;
if (withId != null) {
param = new ParametersWithID(publicKeyParameters, withId);
} else {
param = publicKeyParameters;
}
signer.init(false, param);
signer.update(data, 0, data.length);
return signer.verifySignature(sign);
}
/**
* ECC私钥转换为PKCS8
* @param priKey
* @param pubKey
* @return
*/
public static byte[] convertECPrivateKeyToPKCS8(ECPrivateKeyParameters priKey, ECPublicKeyParameters pubKey) {
ECDomainParameters domainParameters = priKey.getParameters();
ECParameterSpec spec = new ECParameterSpec(domainParameters.getCurve(), domainParameters.getG(), domainParameters.getN(), domainParameters.getH());
BCECPublicKey publicKey = null;
if (pubKey != null) {
publicKey = new BCECPublicKey(ALGO_NAME_EC, pubKey, spec, BouncyCastleProvider.CONFIGURATION);
}
BCECPrivateKey privateKey = new BCECPrivateKey(ALGO_NAME_EC, priKey, publicKey, spec, BouncyCastleProvider.CONFIGURATION);
return privateKey.getEncoded();
}
/**
* PKCS8转换为私钥对象
* @param pkcs8Key
* @return
* @throws Exception
*/
public static BCECPrivateKey convertPKCS8ToECPrivateKey(byte[] pkcs8Key) throws Exception {
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(pkcs8Key);
KeyFactory kf = KeyFactory.getInstance(ALGO_NAME_EC, BouncyCastleProvider.PROVIDER_NAME);
return (BCECPrivateKey) kf.generatePrivate(spec);
}
/**
* 转换私钥
* @param ecPriKey
* @return
*/
public static ECPrivateKeyParameters convertPrivateKey(BCECPrivateKey ecPriKey) {
ECParameterSpec parameterSpec = ecPriKey.getParameters();
ECDomainParameters domainParameters = new ECDomainParameters(parameterSpec.getCurve(), parameterSpec.getG(),
parameterSpec.getN(), parameterSpec.getH());
return new ECPrivateKeyParameters(ecPriKey.getD(), domainParameters);
}
/**
* 转换公钥
* @param ecPubKey
* @return
*/
public static ECPublicKeyParameters convertPublicKey(BCECPublicKey ecPubKey) {
ECParameterSpec parameterSpec = ecPubKey.getParameters();
ECDomainParameters domainParameters = new ECDomainParameters(parameterSpec.getCurve(), parameterSpec.getG(),
parameterSpec.getN(), parameterSpec.getH());
return new ECPublicKeyParameters(ecPubKey.getQ(), domainParameters);
}
public static void main(String[] args) throws Exception{
Security.addProvider(new BouncyCastleProvider());
String str ="hello world";
//密钥对
KeyPair keyPair = generateBCECKeyPair();
//公钥
BCECPublicKey publicKey = (BCECPublicKey) keyPair.getPublic();
//转换为公钥参数
ECPublicKeyParameters ecPublicKeyParameters = convertPublicKey(publicKey);
//私钥
BCECPrivateKey privateKey = (BCECPrivateKey) keyPair.getPrivate();
//转换为私钥参数
ECPrivateKeyParameters ecPrivateKeyParameters = convertPrivateKey(privateKey);
//签名
byte[] sign = sign(ecPrivateKeyParameters, null, str.getBytes());
System.out.println(HexUtil.encodeHexStr(sign));
//验证
boolean verify = verify(ecPublicKeyParameters, null, str.getBytes(), sign);
System.out.println(verify);
}
}
6、密钥交换(SM2)
密钥交换和一般使用国际算法中的DH类似。本实现了两种密钥交换方式:
- 经过确认的密钥交换
- 没有经过确认的密钥交换
SM2的密钥交换需要有:
- 密钥长度
- 己方固定私钥和临时私钥,
- 对方的固定公钥和临时私钥。
密钥交换即是使用己方的私钥和对方的公钥构建本地加密的密钥,然后进行加密;解密的构建密钥的过程则相反。
6.1、实现一(没有经过确认的密钥交换)
public class SM2KeyExchangeUtil {
/**
* 以下为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);
/**
* 构建密钥
* @param domainParameters
* @param random
* @return
*/
public static AsymmetricCipherKeyPair generateKeyPair(ECDomainParameters domainParameters, SecureRandom random) {
ECKeyGenerationParameters keyGenerationParams = new ECKeyGenerationParameters(domainParameters, random);
ECKeyPairGenerator keyGen = new ECKeyPairGenerator();
keyGen.init(keyGenerationParams);
return keyGen.generateKeyPair();
}
public static AsymmetricCipherKeyPair generateKeyPair() {
SecureRandom random = new SecureRandom();
return generateKeyPair(DOMAIN_PARAMS, random);
}
/**
* 生成本地密钥
* @param initiator 是否为发起方,true表发起方,false表示响应方
* @param keyBits 密钥长度
* @param selfStaticPriv 己方固定私钥
* @param selfEphemeralPriv 己方临时私钥
* @param selfId 己方id
* @param otherStaticPub 对方固定公钥
* @param otherEphemeralPub 对方临时公钥
* @param otherId 对方id
* @return byte[] 返回协商出的密钥,但是这个密钥是没有经过确认的
*/
public static byte[] calculateKey(boolean initiator, int keyBits, ECPrivateKeyParameters selfStaticPriv,
ECPrivateKeyParameters selfEphemeralPriv, byte[] selfId, ECPublicKeyParameters otherStaticPub,
ECPublicKeyParameters otherEphemeralPub, byte[] otherId) {
SM2KeyExchange exch = new SM2KeyExchange();
exch.init(new ParametersWithID(new SM2KeyExchangePrivateParameters(initiator, selfStaticPriv, selfEphemeralPriv), selfId));
return exch.calculateKey(keyBits, new ParametersWithID(new SM2KeyExchangePublicParameters(otherStaticPub, otherEphemeralPub), otherId));
}
private static final byte[] INITIATOR_ID = "ABCDEFG1234".getBytes();
private static final byte[] RESPONDER_ID = "1234567ABCD".getBytes();
private static final int KEY_BITS = 128;
public static void main(String[] args) {
// 己方固定私钥
AsymmetricCipherKeyPair initiatorStaticKp = SM2KeyExchangeUtil.generateKeyPair();
ECPrivateKeyParameters initiatorStaticPriv = (ECPrivateKeyParameters) initiatorStaticKp.getPrivate();
ECPublicKeyParameters initiatorStaticPub = (ECPublicKeyParameters) initiatorStaticKp.getPublic();
// 己方临时私钥
AsymmetricCipherKeyPair initiatorEphemeralKp = SM2KeyExchangeUtil.generateKeyPair();
ECPrivateKeyParameters initiatorEphemeralPriv = (ECPrivateKeyParameters) initiatorEphemeralKp.getPrivate();
ECPublicKeyParameters initiatorSEphemeralPub = (ECPublicKeyParameters) initiatorEphemeralKp.getPublic();
// 对方固定公钥
AsymmetricCipherKeyPair responderStaticKp = SM2KeyExchangeUtil.generateKeyPair();
ECPrivateKeyParameters responderStaticPriv = (ECPrivateKeyParameters) responderStaticKp.getPrivate();
ECPublicKeyParameters responderStaticPub = (ECPublicKeyParameters) responderStaticKp.getPublic();
// 对方临时公钥
AsymmetricCipherKeyPair responderEphemeralKp = SM2KeyExchangeUtil.generateKeyPair();
ECPrivateKeyParameters responderEphemeralPriv = (ECPrivateKeyParameters) responderEphemeralKp.getPrivate();
ECPublicKeyParameters responderSEphemeralPub = (ECPublicKeyParameters) responderEphemeralKp.getPublic();
// 实际应用中应该是通过网络交换临时公钥,具体参考DH的密钥交换示例
byte[] k1 = SM2KeyExchangeUtil.calculateKey(true, KEY_BITS, initiatorStaticPriv, initiatorEphemeralPriv,
INITIATOR_ID, responderStaticPub, responderSEphemeralPub, RESPONDER_ID);
byte[] k2 = SM2KeyExchangeUtil.calculateKey(false, KEY_BITS, responderStaticPriv, responderEphemeralPriv,
RESPONDER_ID, initiatorStaticPub, initiatorSEphemeralPub, INITIATOR_ID);
if (!Arrays.equals(k1, k2)) {
Assert.fail();
}
}
}
6.2、实现二(经过确认的密钥交换)
public class SM2KeyExchangeUtil2 {
/**
* 以下为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);
/**
* 构建密钥
* @param domainParameters
* @param random
* @return
*/
public static AsymmetricCipherKeyPair generateKeyPair(ECDomainParameters domainParameters, SecureRandom random) {
ECKeyGenerationParameters keyGenerationParams = new ECKeyGenerationParameters(domainParameters, random);
ECKeyPairGenerator keyGen = new ECKeyPairGenerator();
keyGen.init(keyGenerationParams);
return keyGen.generateKeyPair();
}
public static AsymmetricCipherKeyPair generateKeyPair() {
SecureRandom random = new SecureRandom();
return generateKeyPair(DOMAIN_PARAMS, random);
}
/**
* 生成本地密钥并确认
* @param initiator 是否为发起方,true表示发起方,false表示响应方
* @param keyBits 密钥长度
* @param confirmationTag 确认信息,如果是响应方可以为null;如果是发起方则应为响应方的s1
* @param selfStaticPriv 己方固定私钥
* @param selfEphemeralPriv 己方临时私钥
* @param selfId 己方ID
* @param otherStaticPub 对方固定公钥
* @param otherEphemeralPub 对方临时公钥
* @param otherId 对方ID
* @return ExchangeResult 经过确认的密钥封装对象
*/
public static ExchangeResult calculateKeyWithConfirmation(boolean initiator, int keyBits, byte[] confirmationTag,
ECPrivateKeyParameters selfStaticPriv, ECPrivateKeyParameters selfEphemeralPriv, byte[] selfId,
ECPublicKeyParameters otherStaticPub, ECPublicKeyParameters otherEphemeralPub, byte[] otherId) {
SM2KeyExchange exch = new SM2KeyExchange();
exch.init(new ParametersWithID(new SM2KeyExchangePrivateParameters(initiator, selfStaticPriv, selfEphemeralPriv), selfId));
byte[][] result = exch.calculateKeyWithConfirmation(
keyBits,
confirmationTag,
new ParametersWithID(new SM2KeyExchangePublicParameters(otherStaticPub, otherEphemeralPub), otherId));
//封装
ExchangeResult confirmResult = new ExchangeResult();
confirmResult.setKey(result[0]);
if (initiator) {
confirmResult.setS2(result[1]);
} else {
confirmResult.setS1(result[1]);
confirmResult.setS2(result[2]);
}
return confirmResult;
}
/**
* 确认响应
* @param s2
* @param confirmationTag
* @return
*/
public static boolean responderConfirm(byte[] s2, byte[] confirmationTag) {
return Arrays.equals(s2, confirmationTag);
}
public static class ExchangeResult {
private byte[] key;
// 发起方没有s1
private byte[] s1;
private byte[] s2;
public byte[] getKey() {
return key;
}
public void setKey(byte[] key) {
this.key = key;
}
public byte[] getS1() {
return s1;
}
public void setS1(byte[] s1) {
this.s1 = s1;
}
public byte[] getS2() {
return s2;
}
public void setS2(byte[] s2) {
this.s2 = s2;
}
}
private static final byte[] INITIATOR_ID = "ABCDEFG1234".getBytes();
private static final byte[] RESPONDER_ID = "1234567ABCD".getBytes();
private static final int KEY_BITS = 128;
public static void main(String[] args) {
// 己方固定私钥
AsymmetricCipherKeyPair initiatorStaticKp = SM2KeyExchangeUtil2.generateKeyPair();
ECPrivateKeyParameters initiatorStaticPriv = (ECPrivateKeyParameters) initiatorStaticKp.getPrivate();
ECPublicKeyParameters initiatorStaticPub = (ECPublicKeyParameters) initiatorStaticKp.getPublic();
// 己方临时私钥
AsymmetricCipherKeyPair initiatorEphemeralKp = SM2KeyExchangeUtil2.generateKeyPair();
ECPrivateKeyParameters initiatorEphemeralPriv = (ECPrivateKeyParameters) initiatorEphemeralKp.getPrivate();
ECPublicKeyParameters initiatorSEphemeralPub = (ECPublicKeyParameters) initiatorEphemeralKp.getPublic();
// 对方固定公钥
AsymmetricCipherKeyPair responderStaticKp = SM2KeyExchangeUtil2.generateKeyPair();
ECPrivateKeyParameters responderStaticPriv = (ECPrivateKeyParameters) responderStaticKp.getPrivate();
ECPublicKeyParameters responderStaticPub = (ECPublicKeyParameters) responderStaticKp.getPublic();
// 对方临时公钥
AsymmetricCipherKeyPair responderEphemeralKp = SM2KeyExchangeUtil2.generateKeyPair();
ECPrivateKeyParameters responderEphemeralPriv = (ECPrivateKeyParameters) responderEphemeralKp.getPrivate();
ECPublicKeyParameters responderSEphemeralPub = (ECPublicKeyParameters) responderEphemeralKp.getPublic();
// 第一步应该是交换临时公钥等信息
// 第二步响应方生成密钥和验证信息
SM2KeyExchangeUtil2.ExchangeResult responderResult = SM2KeyExchangeUtil2.calculateKeyWithConfirmation(false,
KEY_BITS, null, responderStaticPriv, responderEphemeralPriv, RESPONDER_ID, initiatorStaticPub,
initiatorSEphemeralPub, INITIATOR_ID);
// 第三步发起方生成密钥和验证消息,并验证响应方的验证消息
SM2KeyExchangeUtil2.ExchangeResult initiatorResult = SM2KeyExchangeUtil2.calculateKeyWithConfirmation(true,
KEY_BITS, responderResult.getS1(), initiatorStaticPriv, initiatorEphemeralPriv, INITIATOR_ID,
responderStaticPub, responderSEphemeralPub, RESPONDER_ID);
// 第四步响应方验证发起方的验证消息
if (!SM2KeyExchangeUtil2.responderConfirm(responderResult.getS2(), initiatorResult.getS2())) {
Assert.fail();
}
}
}
7、密钥编码格式
针对密钥进行不同的编码方式:
- DER
- PEM
- SEC1
7.1、依赖
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15to18</artifactId>
<version>1.68</version>
</dependency>
7.2、DER
/**
* 将ECC私钥转换为PKCS8标准的字节流
*
* @param priKey
* @param pubKey
* 可以为空,但是如果为空的话得到的结果OpenSSL可能解析不了
* @return
*/
public static byte[] convertECPrivateKeyToPKCS8(ECPrivateKeyParameters priKey, ECPublicKeyParameters pubKey) {
ECDomainParameters domainParams = priKey.getParameters();
ECParameterSpec spec = new ECParameterSpec(domainParams.getCurve(), domainParams.getG(), domainParams.getN(),
domainParams.getH());
BCECPublicKey publicKey = null;
if (pubKey != null) {
publicKey = new BCECPublicKey(ALGO_NAME_EC, pubKey, spec, BouncyCastleProvider.CONFIGURATION);
}
BCECPrivateKey privateKey = new BCECPrivateKey(ALGO_NAME_EC, priKey, publicKey, spec,
BouncyCastleProvider.CONFIGURATION);
return privateKey.getEncoded();
}
7.3、PEM
private static String convertEncodedDataToPEM(String type, byte[] encodedData) throws IOException {
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
PemWriter pWrt = new PemWriter(new OutputStreamWriter(bOut));
try {
PemObject pemObj = new PemObject(type, encodedData);
pWrt.writeObject(pemObj);
} finally {
pWrt.close();
}
return new String(bOut.toByteArray());
}
7.4、SEC1
/**
* 将ECC私钥转换为SEC1标准的字节流 openssl d2i_ECPrivateKey函数要求的DER编码的私钥也是SEC1标准的,
* 这个工具函数的主要目的就是为了能生成一个openssl可以直接“识别”的ECC私钥. 相对RSA私钥的PKCS1标准,ECC私钥的标准为SEC1
*
* @param priKey
* @param pubKey
* @return
* @throws IOException
*/
public static byte[] convertECPrivateKeyToSEC1(ECPrivateKeyParameters priKey, ECPublicKeyParameters pubKey)
throws IOException {
byte[] pkcs8Bytes = convertECPrivateKeyToPKCS8(priKey, pubKey);
PrivateKeyInfo pki = PrivateKeyInfo.getInstance(pkcs8Bytes);
ASN1Encodable encodable = pki.parsePrivateKey();
ASN1Primitive primitive = encodable.toASN1Primitive();
byte[] sec1Bytes = primitive.getEncoded();
return sec1Bytes;
}