介绍

SM4 加密模式

SM4 是中国国家密码管理局发布的对称加密算法,主要用于保护数据的机密性。在加密过程中,SM4 可以采用不同的工作模式,每种模式都有不同的用途和特性。常见的加密模式包括:

  • 电子密码本模式(ECB, Electronic Codebook):

    • 作用: 将明文分成块,每块单独加密。
    • 优点: 简单易实现。
    • 缺点: 相同的明文块会产生相同的密文块,这会暴露一些模式信息,降低安全性。
    • 用途和应用场景: 通常不推荐用于高安全性需求的场合。
  • 密文分组链接模式(CBC, Cipher Block Chaining):

    • 作用: 每个明文块在加密之前与前一个密文块进行异或操作。第一个块与一个初始化向量(IV)异或。
    • 优点: 增强了安全性,因为相同的明文块会由于前一个块的加密结果而有所不同。
    • 用途和应用场景: 适合大多数加密需求,特别是需要抵抗模式分析的场合。
  • 计数器模式(CTR, Counter Mode):

    • 作用: 将明文与加密后的计数器值进行异或。计数器值会随着每个块增加。
    • 优点: 可以并行处理,提高加密和解密速度。
    • 用途和应用场景: 适用于高性能要求的环境,例如流媒体传输和大数据处理。
  • 输出反馈模式(OFB, Output Feedback):

    • 作用: 使用加密算法的输出作为后续块的输入。
    • 优点: 可以将加密过程转换为流加密,适合数据流加密。
    • 用途和应用场景: 适用于需要加密流数据的场合,如网络传输中的数据加密。
  • 加密反馈模式(CFB, Cipher Feedback):

    • 作用: 将加密输出的一部分作为反馈用于加密明文的下一部分。
    • 优点: 类似于流加密,能够处理任意长度的数据。
    • 用途和应用场景: 适用于实时数据流加密和响应时间要求高的系统。

IV(初始化向量)

IV 是“Initialization Vector”的缩写,它是一种随机数或伪随机数,用于增强加密算法的安全性。

  • 作用:

    • 在加密过程中,用于确保即使相同的明文块和相同的密钥在不同的加密会话中也产生不同的密文。
    • 提供额外的随机性,以避免加密模式产生的模式性攻击。
  • 区别:

    • 与密钥的不同: IV 是用于加密过程中的随机化因素,而密钥是加密和解密的核心。
    • 与加密模式的不同: IV 是许多加密模式(如 CBC 和 CFB)所需要的,但不是所有模式都使用 IV。
  • 用途和应用场景:

    • 广泛应用于块加密模式(如 CBC、CFB)中。
    • IV 应该在每次加密操作中都重新生成,确保安全性。
    • 常见于安全传输协议(如 SSL/TLS)和存储加密中。

Padding(填充)

Padding 是在加密过程中对明文数据进行填充的一种方式,以确保数据的长度符合块加密算法的要求。

  • 作用:

    • 填充操作使得明文数据的长度满足加密算法要求的块大小。
    • 防止数据被截断或被篡改,提高加密的安全性。
  • PKCS5 和 PKCS7:

    • PKCS5:
      • 只对 64 位块大小的加密算法(如 DES)有效。
      • 填充内容包括填充字节的数量,填充字节的值与填充的字节数相同。
    • PKCS7:
      • 扩展了 PKCS5,可以用于任何块大小的加密算法(如 AES 的 128、192、256 位块)。
      • 填充字节的值也是填充的字节数。
    • 区别: PKCS7 比 PKCS5 更为通用,因为它支持多种块大小。
  • 具体用途和应用场景:

    • 填充在块加密模式(如 CBC、ECB)中非常重要,用于确保明文数据的完整性。
    • PKCS7 是现代应用中更常用的填充方式,广泛应用于各种加密标准和协议。

使用

官方: https://github.com/JuneAndGreen/sm-crypto

npm

安装

1
npm install --save sm-crypto

加密

1
2
3
4
5
6
7
8
const sm4 = require('sm-crypto').sm4
const msg = 'hello world! 我是 juneandgreen.' // 可以为 utf8 串或字节数组
const key = '0123456789abcdeffedcba9876543210' // 可以为 16 进制串或字节数组,要求为 128 比特

let encryptData = sm4.encrypt(msg, key) // 加密,默认输出 16 进制字符串,默认使用 pkcs#7 填充(传 pkcs#5 也会走 pkcs#7 填充)
let encryptData = sm4.encrypt(msg, key, {padding: 'none'}) // 加密,不使用 padding
let encryptData = sm4.encrypt(msg, key, {padding: 'none', output: 'array'}) // 加密,不使用 padding,输出为字节数组
let encryptData = sm4.encrypt(msg, key, {mode: 'cbc', iv: 'fedcba98765432100123456789abcdef'}) // 加密,cbc 模式

解密

1
2
3
4
5
6
7
8
const sm4 = require('sm-crypto').sm4
const encryptData = '0e395deb10f6e8a17e17823e1fd9bd98a1bff1df508b5b8a1efb79ec633d1bb129432ac1b74972dbe97bab04f024e89c' // 可以为 16 进制串或字节数组
const key = '0123456789abcdeffedcba9876543210' // 可以为 16 进制串或字节数组,要求为 128 比特

let decryptData = sm4.decrypt(encryptData, key) // 解密,默认输出 utf8 字符串,默认使用 pkcs#7 填充(传 pkcs#5 也会走 pkcs#7 填充)
let decryptData = sm4.decrypt(encryptData, key, {padding: 'none'}) // 解密,不使用 padding
let decryptData = sm4.decrypt(encryptData, key, {padding: 'none', output: 'array'}) // 解密,不使用 padding,输出为字节数组
let decryptData = sm4.decrypt(encryptData, key, {mode: 'cbc', iv: 'fedcba98765432100123456789abcdef'}) // 解密,cbc 模式

apifox

完全版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
fox.liveRequire("sm-crypto", (smCrypto) => {
const sm4 = smCrypto.sm4;

if (!sm4) {
console.error("sm-crypto 库加载失败");
return;
}

// 定义密钥和 IV
const keyHex = '854f38e8cb3f46665ad3b93ecfcc679d';
const ivHex = 'fedcba98765432100123456789abcdef';

console.log("密钥 (Hex):", keyHex);
console.log("IV (Hex):", ivHex);

// 设置加密选项
const options = {
mode: 'cbc',
iv: ivHex

};

// 获取请求参数
const requestData = "Hello!";

console.log("原始请求数据:", requestData);

// 使用 SM4 加密请求参数
try {
console.log("开始加密:", requestData);

// 直接传递字符串进行加密
const encryptedData = sm4.encrypt(requestData, keyHex, options);
console.log("encryptedData", encryptedData);
if (!encryptedData || encryptedData.length === 0) {
console.error("加密失败,数据为空");
return;
}

console.log("加密后的数据:", encryptedData);

// 使用 SM4 解密参数
const decryptedDataHex = sm4.decrypt(encryptedData, keyHex, options);
console.log("解密后的数据:", decryptedDataHex);

} catch (error) {
console.error("加密/解密过程中出现错误:", error);
}
});

前置操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
fox.liveRequire("sm-crypto", (smCrypto) => {
const sm4 = smCrypto.sm4;

if (!sm4) {
console.error("sm-crypto 库加载失败");
return;
}
// 定义密钥和 IV
const keyHex = pm.environment.get('sm4key');
const ivHex = pm.environment.get('ivHex');

// 设置加密选项
const options = {
mode: 'cbc',
iv: ivHex
};

// 获取请求参数
const requestData = pm.request.body.raw;
if (!requestData) {
console.log("原始请求数据为空,忽略...");
return;
}

// 使用 SM4 加密请求参数
try {
console.log("开始加密:", requestData);
// 直接传递字符串进行加密
const encryptedData = sm4.encrypt(requestData, keyHex, options);
if (!encryptedData || encryptedData.length === 0) {
console.error("加密失败,数据为空");
return;
}

// 构建加密后的数据包
const dataPackage = {
data: encryptedData,
};
console.log("加密后的数据包", dataPackage);
// 更新请求体中的数据
pm.request.body.update(JSON.stringify(dataPackage));


} catch (error) {
console.error("加密过程中出现错误:", error);
}
});

后置操作

1

java

1
2
3
4
5
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.31</version>
</dependency>

// 和前端sm-crypto 对接这里采用 encryptHex 方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.HexUtil;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.SM4;
import lombok.extern.slf4j.Slf4j;

import javax.crypto.SecretKey;


/**
* sm4 util
*
* @author shuzhuo
*/
@Slf4j
public class Sm4Utils {


/**
* 生成sm4密钥
*
* @return {@link String }
*/
public static String generateSm4Key() {
SecretKey secretKey = SecureUtil.generateKey("SM4", 128);
return HexUtil.encodeHexStr(secretKey.getEncoded());
}


/**
* 加密
*
* @param keyHex 密钥十六进制
* @param ivHex iv十六进制
* @param content 内容
* @return {@link String }
*/
public static String encrypt(String keyHex, String ivHex, String content) {
byte[] key = HexUtil.decodeHex(keyHex);
byte[] iv = HexUtil.decodeHex(ivHex);
SM4 sm4 = new SM4(Mode.CBC, Padding.PKCS5Padding, key, iv);
return sm4.encryptHex(content, CharsetUtil.CHARSET_UTF_8);
}

/**
* 解密
*
* @param keyHex 密钥十六进制
* @param ivHex iv十六进制
* @param content 内容
* @return {@link String }
*/
public static String decrypt(String keyHex, String ivHex, String content) {
byte[] key = HexUtil.decodeHex(keyHex);
byte[] iv = HexUtil.decodeHex(ivHex);
SM4 sm4 = new SM4(Mode.CBC, Padding.PKCS5Padding, key, iv);
return sm4.decryptStr(content, CharsetUtil.CHARSET_UTF_8);
}

public static void main(String[] args) {
String plainText = "Hello, Hutool!";
String keyHex = "854f38e8cb3f46665ad3b93ecfcc679d";
String ivHex = "fedcba98765432100123456789abcdef";
String encrypt = encrypt(keyHex, ivHex, plainText);
System.out.println(encrypt);
System.out.println(decrypt(keyHex, ivHex, encrypt));
}

}