算法简介
SM2:基于椭圆曲线密码体制(ECC)的非对称加密算法,使用公钥和私钥对。公钥公开用于加密或验证签名,私钥保密用于解密或生成签名。数学原理基于椭圆曲线上的离散对数问题。主要用于数字签名、密钥交换等,能确保电子文档的真实性、完整性和不可否认性。其安全性高,同等安全强度下,密钥长度比 RSA 等非对称加密算法短,计算量小、效率高、存储成本低,但加密和解密计算过程相对复杂,处理大量数据时速度较慢。
SM3:密码哈希函数,将任意长度的数据映射为 256 位的固定长度哈希值。具有单向性,从哈希值很难反推出原始数据。主要用于数字签名中计算消息摘要以及数据完整性验证。具有高度的安全性和抗碰撞性,能有效抵御各种恶意攻击,保证数据的完整性和真实性,但原始数据丢失后无法通过哈希值恢复。
SM4:分组对称加密算法,分组长度和密钥长度均为 128 位。通过对明文分组和密钥进行多轮迭代运算实现加密,每轮运算依据特定置换和替换规则处理数据。常用于数据存储加密和数据传输加密,加密速度快,处理大量数据时效率高,128 位密钥能提供足够安全性,可抵御穷举攻击等常见攻击方式。
方案
定义两套 密钥,用于 请求 和 响应 两种交互,定义 clientPublicKey、clientPrivateKey、servicePublicKey、servicePrivateKey 四个字段,两组 公钥和私钥 。
Client 请求的时候使用服务端的公钥(servicePublicKey)进行加密,Server端使用 服务端私钥(servicePrivateKey)进行解密。响应的时候则相反。这里并不会对数据进行 加/解密 ,而是类似于 SSL 协商后续 SM4(对称加密)所需要使用的密钥。
可以在 RequestHeader 和 ResponseHeader 中添加 X-Key 用于记录 SM4 加密所需要的密钥传输。
无需纠结是服务端和客户端两个名词,只是为了区别是两套密钥。
实现
- 外部依赖
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
- Java代码一端实现
@Service
public class EncryptUtil {
/**
* 客户端公钥
*/
@Value("${client.public.key:}")
private String clientPublicKey;
/**
* 服务端私钥
*/
@Value("${service.private.key:}")
private String servicePrivateKey;
@Value("${service.salt:}")
private String salt;
/**
* SM2加密
*
* @param plaintext 明文数据
* @return 加密数据
*/
public String sm2Encrypt(String plaintext) {
return SmUtil.sm2(null, clientPublicKey).encryptHex(plaintext.getBytes(), KeyType.PublicKey);
}
/**
* SM2解密
*
* @param encrypted 加密数据
* @return 明文
*/
public String sm2Decrypt(String encrypted) {
return SmUtil.sm2(servicePrivateKey, null).decryptStr(encrypted, KeyType.PrivateKey);
}
/**
* SM4加密
*
* @param plaintext 明文
* @return 加密数据
*/
public String sm4Encrypt(String plaintext) {
return SmUtil.sm4(SM4Util.hexToBytes(RandomUtil.randomNumbers(32))).encryptHex(plaintext.getBytes());
}
/**
* SM4解密
*
* @param encrypted 加密数据
* @return 明文
*/
public String sm4Decrypt(String encrypted, String key) {
return SmUtil.sm4(SM4Util.hexToBytes(key)).decryptStr(encrypted);
}
/**
* SM3加密
*
* @param plaintext 需要HASH的明文数据
* @return Hash 数据
*/
public String sm3(String plaintext) {
return SmUtil.sm3(plaintext);
}
}