国密SM4算法加密解密实现以及与Spring Security集成实现
Posted ricardo.M.Yu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了国密SM4算法加密解密实现以及与Spring Security集成实现相关的知识,希望对你有一定的参考价值。
目录
简介
项目中要求密码加密算法使用国家的密码标准SM4.0,下面来介绍下。
SM4算法是我国发布的商用密码算法中的分组密码算法,是一种迭代分组密码算法,由加解密算法和密钥扩展算法组成。其分组长度和密钥长度均为128比特,加密算法和密钥扩展算法迭代轮数均为32轮。SM4加解密过程的算法相同但是轮密钥的使用顺序相反。SM4算法具有安全高效的功能特点,在设计和实现方面具有一定的优势。
项目集成
在项目中登录以及鉴权使用的是Spring Security,与框架的集成如下。
引入依赖
首先需要引入如下两个依赖
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>$hutool.version</version>
</dependency>
<!-- 加密解密包 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15to18</artifactId>
<version>$bcprov.version</version>
</dependency>
测试
工具类中可以使用如下方法自行测试加密效果
public static void main(String[] args)
try
//自定义秘钥
String key = "6gsx@654hs68456!";
String password="123456";
SymmetricCrypto crypto = SmUtil.sm4(key.getBytes());
String s2 = crypto.encryptHex(password);
String s3 = crypto.decryptStr(s2);
System.out.println(s2);
System.out.println(s3);
//用工具类生成,此处使用随机秘钥,加解密需要使用同一个示例才可得到原始密码
SM4 sm4 = SmUtil.sm4();
catch (Exception e)
throw new RuntimeException(e);
编写 PasswordEncoder
Spring Security默认支持的密码算法如下:
public static PasswordEncoder createDelegatingPasswordEncoder()
String encodingId = "bcrypt";
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put(encodingId, new BCryptPasswordEncoder());
encoders.put("ldap", new org.springframework.security.crypto.password.LdapShaPasswordEncoder());
encoders.put("MD4", new org.springframework.security.crypto.password.Md4PasswordEncoder());
encoders.put("MD5", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5"));
encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
encoders.put("SHA-1", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-1"));
encoders.put("SHA-256",
new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-256"));
encoders.put("sha256", new org.springframework.security.crypto.password.StandardPasswordEncoder());
encoders.put("argon2", new Argon2PasswordEncoder());
return new DelegatingPasswordEncoder(encodingId, encoders);
在我们集成登录时候,会将登录的密码与用户的密码作比较,因此我们需要自定义一个新的PasswordEncoder来替换掉默认的。
定义SM4PasswordEncoder
public class SM4PasswordEncoder implements PasswordEncoder
private final SymmetricCrypto sm4;
public SM4PasswordEncoder(String key)
SymmetricCrypto crypto = SmUtil.sm4(key.getBytes());
this.sm4 = crypto;
@Override
public String encode(CharSequence rawPassword)
return sm4.encryptHex(rawPassword.toString());
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword)
return sm4.encryptHex(rawPassword.toString()).equals(encodedPassword);
matches方法即会在登录时调用此方法比较。
配置文件定义
因为我们需要自定义一个加密的秘钥,在实际项目中我们根据实际情况灵活配置,所以我们定义一个配置文件 application-sm4.yml
te
system:
sm4-key: z^cnmuop2372a375
注意: key的秘钥位数需要是 16因为 128位/8 = 16
properties类
@Data
@Component
@ConfigurationProperties(prefix = "te.system")
public class SystemEnvProperties
@NotNull
@ApiModelProperty(notes = "sm4加密秘钥-需要16byte")
private String sm4Key;
注册为Bean
/**
* 密码明文加密方式配置(使用国密SM4)
* @return
*/
@Bean
public PasswordEncoder passwordEncoder(SystemEnvProperties envProperties)
return new SM4PasswordEncoder(envProperties.getSm4Key());
密码加密
在创建用户及修改密码时候,调用加密方法
@Autowired
private PasswordEncoder passwordEncoder;
userInfo.setPassword(passwordEncoder.encode(vo.getPassword()));
至此,集成完成
openssl 加解密以及国密算法
国密算法
国密即国家密码局认定的国产密码算法。主要有SM1,SM2,SM3,SM4。密钥长度和分组长度均为128位。
1、SM1 为对称加密。其加密强度与AES相当。该算法不公开,调用该算法时,需要通过加密芯片的接口进行调用。
2、SM2为非对称加密,基于ECC。该算法已公开。由于该算法基于ECC,故其签名速度与秘钥生成速度都快于RSA。ECC 256位(SM2采用的就是ECC 256位的一种)安全强度比RSA 2048位高,但运算速度快于RSA。
3、SM3 消息摘要。可以用MD5作为对比理解。该算法已公开。校验结果为256位,本身是散列算法,官方叫杂凑类算法,根据SHA256算法改进而来。
4、SM4 无线局域网标准的分组数据算法。对称加密,密钥长度和分组长度均为128位。
5、SM7适用于非接触式IC卡的对称算法,秘钥长度128bit
6、SM9是标识算法,支持加密、签名、交换。
国密芯片算法一般是对称算法,密钥不公开,当使用特定的芯片进行SM1或其他国密算法加密时,若用多个线程调用加密卡的API时,要考虑芯片对于多线程的支持情况。
生成sm2密钥对
openssl ecparam -genkey -name SM2 -out SM2PrivateKey.pem
openssl ec -in SM2PrivateKey.pem -pubout -out SM2PublicKey.pem
AES 加密
堆成算法比较成熟的是AES算法,AES加密数据块和密钥长度可以是128比特、192比特、256比特中的任意一个。实际上,国密算法一般都是根据这种算法改进产生的。
AES加密有很多轮的重复和变换。大致步骤如下:
1、密钥扩展(KeyExpansion),
2、初始轮(Initial Round),
3、重复轮(Rounds),每一轮又包括:SubBytes、ShiftRows、MixColumns、AddRoundKey,
4、最终轮(Final Round),最终轮没有MixColumns。
1、使用AES加解密数据
省略,这个比较简单
使用RSA加解密数据
RSA 主要是公钥和私钥的概念,产生一个私钥
$ openssl genrsa -out Key.pem -f4 2048
从私钥可以导出公钥
$ openssl rsa -in Key.pem -pubout -out Key_pub.pem
python 比较方便,使用python实例一下
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding
#加密函数
def encrypt(src_file_name, dst_file_name, public_key_file_name):
"""
对原始数据文件使用指定的公钥进行加密,并将加密输出到目标文件中
:param src_file_name: 原始数据文件
:param dst_file_name: 加密输出文件
:param public_key_file_name: 用于加密的公钥
:return: 加密结果的bytes数组
"""
#读取数据,需要加密的内容
data_file = open(src_file_name, 'rb')
data = data_file.read()
data_file.close()
#读取公钥文件
key_file = open(public_key_file_name, 'rb')
key_data = key_file.read()
key_file.close()
#加载公钥
public_key = serialization.load_pem_public_key(
key_data,
backend=default_backend()
)
#使用公钥对原始数据进行加密,使用PKCS#1 v1.5的填充方式
out_data = public_key.encrypt(
data,
padding.PKCS1v15()
)
#将加密结果输出到目标文件中
#write encrypted data
out_data_file = open(dst_file_name, 'wb')
out_data_file.write(out_data)
out_data_file.close()
# 返回加密结果
return out_data
# 解密函数
def decrypt(src_file_name, dst_file_name, private_key_file_name):
"""
对原始数据文件使用指定的私钥进行解密,并将结果输出到目标文件中
:param src_file_name: 原始数据文件
:param dst_file_name: 解密输出文件
:param private_key_file_name: 用于解密的私钥
:return: 解密结果的bytes数组
"""
# 读取原始数据
data_file = open(src_file_name, 'rb')
data = data_file.read()
data_file.close()
# 读取私钥数据
key_file = open(private_key_file_name, 'rb')
key_data = key_file.read()
key_file.close()
# 从私钥数据中加载私钥
private_key = serialization.load_pem_private_key(
key_data,
password=None,
backend=default_backend()
)
# 使用私钥对数据进行解密,使用PKCS#1 v1.5的填充方式
out_data = private_key.decrypt(
data,
padding.PKCS1v15()
)
# 将解密结果输出到目标文件中
out_data_file = open(dst_file_name, 'wb')
out_data_file.write(out_data)
out_data_file.close()
# 返回解密结果
return out_data
if __name__ == "__main__":
data_file_name = r'msg.bin'
encrypted_file_name = r'msg.bin.encrypted'
decrypted_file_name = r'msg.bin.decrypted'
private_key_file_name = r'Key.pem'
public_key_file_name = r'Key_pub.pem'
# 先对数据加密
data = encrypt(data_file_name, encrypted_file_name, public_key_file_name)
# 打印加密结果
print("encrypted data:")
dump_hex(data)
# 对数据进行解密
data = decrypt(encrypted_file_name, decrypted_file_name, private_key_file_name)
# 打印解密结果
print("decrypted data:")
dump_hex(data)
java AES
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
public class AESCoder
private static final String KEY_ALGORITHM = "AES";
private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";//默认的加密算法
public static byte[] initSecretKey()
//返回生成指定算法密钥生成器的 KeyGenerator 对象
KeyGenerator kg = null;
try
kg = KeyGenerator.getInstance(KEY_ALGORITHM);
catch (NoSuchAlgorithmException e)
e.printStackTrace();
return new byte[0];
//初始化此密钥生成器,使其具有确定的密钥大小
//AES 要求密钥长度为 128
kg.init(128);
//生成一个密钥
SecretKey secretKey = kg.generateKey();
return secretKey.getEncoded();
private static Key toKey(byte[] key)
//生成密钥
return new SecretKeySpec(key, KEY_ALGORITHM);
public static byte[] encrypt(byte[] data,Key key) throws Exception
return encrypt(data, key,DEFAULT_CIPHER_ALGORITHM);
public static byte[] encrypt(byte[] data,byte[] key) throws Exception
return encrypt(data, key,DEFAULT_CIPHER_ALGORITHM);
public static byte[] encrypt(byte[] data,byte[] key,String cipherAlgorithm) throws Exception
//还原密钥
Key k = toKey(key);
return encrypt(data, k, cipherAlgorithm);
public static byte[] encrypt(byte[] data,Key key,String cipherAlgorithm) throws Exception
//实例化
Cipher cipher = Cipher.getInstance(cipherAlgorithm);
//使用密钥初始化,设置为加密模式
cipher.init(Cipher.ENCRYPT_MODE, key);
//执行操作
return cipher.doFinal(data);
public static byte[] decrypt(byte[] data,byte[] key) throws Exception
return decrypt(data, key,DEFAULT_CIPHER_ALGORITHM);
public static byte[] decrypt(byte[] data,Key key) throws Exception
return decrypt(data, key,DEFAULT_CIPHER_ALGORITHM);
public static byte[] decrypt(byte[] data,byte[] key,String cipherAlgorithm) throws Exception
//还原密钥
Key k = toKey(key);
return decrypt(data, k, cipherAlgorithm);
public static byte[] decrypt(byte[] data,Key key,String cipherAlgorithm) throws Exception
//实例化
Cipher cipher = Cipher.getInstance(cipherAlgorithm);
//使用密钥初始化,设置为解密模式
cipher.init(Cipher.DECRYPT_MODE, key);
//执行操作
return cipher.doFinal(data);
private static String showByteArray(byte[] data)
if(null == data)
return null;
StringBuilder sb = new StringBuilder("");
for(byte b:data)
sb.append(b).append(",");
sb.deleteCharAt(sb.length()-1);
sb.append("");
return sb.toString();
public static void main(String[] args) throws Exception
byte[] key = initSecretKey();
System.out.println("key:"+showByteArray(key));
Key k = toKey(key); //生成秘钥
String data ="AES数据";
System.out.println("加密前数据: string:"+data);
System.out.println("加密前数据: byte[]:"+showByteArray(data.getBytes()));
System.out.println();
byte[] encryptData = encrypt(data.getBytes(), k);//数据加密
System.out.println("加密后数据: byte[]:"+showByteArray(encryptData));
// System.out.println("加密后数据: hexStr:"+Hex.encodeHexStr(encryptData));
System.out.println();
byte[] decryptData = decrypt(encryptData, k);//数据解密
System.out.println("解密后数据: byte[]:"+showByteArray(decryptData));
System.out.println("解密后数据: string:"+new String(decryptData));
国密加密过程
加解密都使用openssl 去做,openssl封装比较好,可以直接使用
unsigned char* t, *hm;
BIGNUM* rand;
EC_POINT* rG, *rK;
BIGNUM *rKx, *rKy, *rGx, *rGy;
unsigned char bK[65] = 0;
unsigned char C3[33] = 0;
rG = EC_POINT_new(this->mGroup);
rK = EC_POINT_new(this->mGroup);
rand = BN_new();
BN_rand_range(rand, this->z);
EC_POINT_mul(this->mGroup, rG, NULL,
this->mGP, rand, this->ctx);
rGx = BN_new();
rGy = BN_new();
if(!EC_POINT_get_affine_coordinates_GFp(this->mGroup,
rG, rGx, rGy, this->ctx))
return -3;
BN_bn2bin(rGx, pd);
BN_bn2bin(rGy, &pd[32]);
//[k]PB=(x2,y2)
EC_POINT_mul(this->mGroup, rK, NULL,
EC_KEY_get0_public_key(this->mKey),
rand, this->ctx);
rKx = BN_new();
rKy = BN_new();
if(!EC_POINT_get_affine_coordinates_GFp(this->mGroup,
rK, rKx, rKy, this->ctx))
return -3;
//t=KDF(x2||y2, klen)
BN_bn2bin(rKx, bK);
BN_bn2bin(rKy, &bK[32]);
t = new BYTE[elen + 1];
memset(t, 0, elen + 1);
this->mKDF(bK, 64, elen, t);
for (int i = elen; i--;)
t[i] = t[i]^pe[i];
//C3 = Hash(x2||M||y2)
hm = new unsigned char[elen + 65];
memset(hm, 0, elen + 65);
memcpy(hm, bK, 32);
memcpy(&hm[32], pe, elen);
memcpy(&hm[elen + 32], &bK[32], 32);
hash(hm, elen + 64, C3, "sha256");
//C = C1||C2||C3
memcpy(&pd[64], t, elen);
memcpy(&pd[64 + elen], C3, 32);
delete[] t;
delete[] hm;
t = NULL;
hm = NULL;
EC_POINT_free(rG);
EC_POINT_free(rK);
return 0;
国密解密过程
unsigned char* t, *c2, *hm;
unsigned char bC1x[65] = 0;
unsigned char bC1y[65] = 0;
unsigned char bK[65] = 0;
unsigned char u[33] = 0;
unsigned int mlen, hm_len;
EC_POINT *rG, *rK;
BIGNUM *C1x, *C1y, *rKx, *rKy;
//取出rG
C1x = BN_new();
C1y = BN_new();
memcpy(&bC1x[32], pe, 32);
memcpy(&bC1y[32], &pe[32], 32);
BN_bin2bn(bC1x, 64, C1x);
BN_bin2bn(bC1y, 64, C1y);
rG = EC_POINT_new(this->mGroup);
if(!EC_POINT_set_affine_coordinates_GFp(this->mGroup,
rG, C1x, C1y, this->ctx))
EC_POINT_free(rG);
return -1;
//求得rK
rK = EC_POINT_new(this->mGroup);
EC_POINT_mul(this->mGroup, rK, NULL, rG,
EC_KEY_get0_private_key(this->mKey),
this->ctx);
rKx = BN_new();
rKy = BN_new();
if(!EC_POINT_get_affine_coordinates_GFp(this->mGroup,
rK, rKx, rKy, this->ctx))
EC_POINT_free(rG);
EC_POINT_free(rK);
return -2;
//求取hv 解密
BN_bn2bin(rKx, bK);
BN_bn2bin(rKy, &bK[32]);
mlen = elen - 96;
c2 = new unsigned char[mlen + 1];
memset(c2, 0, mlen + 1);
memcpy(c2以上是关于国密SM4算法加密解密实现以及与Spring Security集成实现的主要内容,如果未能解决你的问题,请参考以下文章