一、SM4 算法背景
-
国密算法
SM4 是中国国家密码管理局(GM/T)于 2012 年发布的商用密码算法,属于 国密标准(GM/T 0002-2012),与 SM2(非对称加密)、SM3(哈希算法)共同构成中国自主密码体系。-
类型:对称分组加密算法。
-
设计目标:替代国际算法(如 AES),满足金融、政务等领域的高安全性需求。
-
二、SM4 核心特性
特性 | 说明 |
---|---|
分组长度 | 128 位(16 字节) |
密钥长度 | 128 位(16 字节) |
加密模式 | 支持 ECB、CBC、CTR、OFB 等模式(需自行实现或依赖密码库) |
轮数 | 32 轮非线性迭代 |
安全性 | 抗差分攻击、线性攻击,符合金融级安全标准 |
三、SM4 算法流程
-
密钥扩展
-
将 128 位初始密钥扩展为 32 个 32 位的轮密钥(
rk[0]
到rk[31]
)。 -
轮密钥生成过程包含非线性变换和循环移位。
-
-
加密过程
-
输入 128 位明文分组,通过 32 轮迭代操作,每轮包含:
-
非线性变换(S盒):4 个并行的 8 位 S 盒替换。
-
线性变换:循环移位和异或操作。
-
-
最终输出 128 位密文。
-
-
解密过程
-
解密与加密流程相同,但轮密钥需按逆序(
rk[31]
到rk[0]
)使用。
-
四、SM4 vs AES
特性 | SM4 | AES-128 |
---|---|---|
标准 | 中国国密标准(GM/T) | 国际标准(NIST) |
分组长度 | 128 位 | 128 位 |
密钥长度 | 128 位 | 128/192/256 位 |
轮数 | 32 轮 | 10/12/14 轮(依密钥长度) |
应用场景 | 中国政务、金融、物联网 | 国际通用领域 |
五、SM4 应用场景
-
数据加密
-
数据库字段、文件、通信内容的加密保护。
-
示例:敏感信息(如身份证号)存储时使用 SM4 加密。
-
-
金融支付
-
POS 终端、移动支付中的数据加密传输。
-
示例:银联部分终端采用 SM4 加密交易信息。
-
-
物联网安全
-
设备间的安全通信协议(如 TLS 中集成 SM4)。
-
-
国密合规场景
-
满足中国密码法规要求的系统(如政务云、电子发票)。
-
代码示例:
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Base64;
/**
* <p>SM4 对称加密工具类(支持 ECB/CBC 模式)</p>
*
* @CreateTime: 2025-03-10 11:50 <br>
*/
public class SM4Utils {
static {
// 添加 BouncyCastle 支持
Security.addProvider(new BouncyCastleProvider());
}
private static final String ALGORITHM = "SM4";
/**
* SM4 密钥长度固定为 128 位
*/
private static final int KEY_SIZE = 128;
/**
* CBC 模式初始化向量长度 16 字节
*/
private static final int IV_SIZE = 16;
// -------------------- 密钥生成 ----------------------------------------
/**
* 生成随机密钥(Base64 编码)
*/
public static String generateKeyBase64() {
return Base64.getEncoder().encodeToString(generateKeyBytes());
}
/**
* 生成随机密钥(字节数组)
*/
public static byte[] generateKeyBytes() {
SecureRandom random = new SecureRandom();
byte[] key = new byte[KEY_SIZE / 8];
random.nextBytes(key);
return key;
}
/**
* 生成 CBC 模式所需的初始化向量(Base64 编码)
*/
public static String generateIvBase64() {
return Base64.getEncoder().encodeToString(generateIvBytes());
}
/**
* 生成 CBC 模式所需的初始化向量(字节数组)
*/
public static byte[] generateIvBytes() {
SecureRandom random = new SecureRandom();
byte[] iv = new byte[IV_SIZE];
random.nextBytes(iv);
return iv;
}
// -------------------- 加密方法 ----------------------------------------
/**
* ECB 模式加密(结果返回 Base64 字符串)
*
* @param plaintext 明文
* @param base64Key Base64 编码的密钥
*/
public static String encryptEcbBase64(String plaintext, String base64Key) {
byte[] key = Base64.getDecoder().decode(base64Key);
byte[] ciphertext = encryptEcb(plaintext.getBytes(StandardCharsets.UTF_8), key);
return Base64.getEncoder().encodeToString(ciphertext);
}
/**
* CBC 模式加密(结果返回 Base64 字符串,包含 IV)
*
* @param plaintext 明文
* @param base64Key Base64 编码的密钥
* @param base64Iv Base64 编码的初始化向量(若为 null 则自动生成)
*/
public static String encryptCbcBase64(String plaintext, String base64Key, String base64Iv) {
byte[] key = Base64.getDecoder().decode(base64Key);
byte[] iv = (base64Iv == null) ? generateIvBytes() : Base64.getDecoder().decode(base64Iv);
byte[] ciphertext = encryptCbc(plaintext.getBytes(StandardCharsets.UTF_8), key, iv);
// 将 IV 附加到密文前(IV 不加密,与密文拼接后返回)
byte[] combined = new byte[iv.length + ciphertext.length];
System.arraycopy(iv, 0, combined, 0, iv.length);
System.arraycopy(ciphertext, 0, combined, iv.length, ciphertext.length);
return Base64.getEncoder().encodeToString(combined);
}
/**
* ECB 模式加密(字节数组)
*/
public static byte[] encryptEcb(byte[] plaintext, byte[] key) {
return process(Cipher.ENCRYPT_MODE, "SM4/ECB/PKCS5Padding", key, null, plaintext);
}
/**
* CBC 模式加密(字节数组)
*/
public static byte[] encryptCbc(byte[] plaintext, byte[] key, byte[] iv) {
return process(Cipher.ENCRYPT_MODE, "SM4/CBC/PKCS5Padding", key, iv, plaintext);
}
// -------------------- 解密方法 ----------------------------------------
/**
* ECB 模式解密(Base64 输入)
*
* @param ciphertextBase64 Base64 编码的密文
* @param base64Key Base64 编码的密钥
*/
public static String decryptEcbBase64(String ciphertextBase64, String base64Key) {
byte[] key = Base64.getDecoder().decode(base64Key);
byte[] ciphertext = Base64.getDecoder().decode(ciphertextBase64);
byte[] plaintext = decryptEcb(ciphertext, key);
return new String(plaintext, StandardCharsets.UTF_8);
}
/**
* CBC 模式解密(Base64 输入,包含 IV)
*
* @param ciphertextBase64 Base64 编码的密文(前16字节为 IV)
* @param base64Key Base64 编码的密钥
*/
public static String decryptCbcBase64(String ciphertextBase64, String base64Key) {
byte[] key = Base64.getDecoder().decode(base64Key);
byte[] combined = Base64.getDecoder().decode(ciphertextBase64);
// 分离 IV 和密文
byte[] iv = new byte[IV_SIZE];
byte[] ciphertext = new byte[combined.length - IV_SIZE];
System.arraycopy(combined, 0, iv, 0, IV_SIZE);
System.arraycopy(combined, IV_SIZE, ciphertext, 0, ciphertext.length);
byte[] plaintext = decryptCbc(ciphertext, key, iv);
return new String(plaintext, StandardCharsets.UTF_8);
}
/**
* ECB 模式解密(字节数组)
*/
public static byte[] decryptEcb(byte[] ciphertext, byte[] key) {
return process(Cipher.DECRYPT_MODE, "SM4/ECB/PKCS5Padding", key, null, ciphertext);
}
/**
* CBC 模式解密(字节数组)
*/
public static byte[] decryptCbc(byte[] ciphertext, byte[] key, byte[] iv) {
return process(Cipher.DECRYPT_MODE, "SM4/CBC/PKCS5Padding", key, iv, ciphertext);
}
// -------------------- 核心加解密逻辑 --------------------
private static byte[] process(int mode, String transformation, byte[] key, byte[] iv, byte[] data) {
try {
SecretKeySpec keySpec = new SecretKeySpec(key, ALGORITHM);
Cipher cipher = Cipher.getInstance(transformation, BouncyCastleProvider.PROVIDER_NAME);
if (iv == null) {
cipher.init(mode, keySpec);
} else {
cipher.init(mode, keySpec, new IvParameterSpec(iv));
}
return cipher.doFinal(data);
} catch (Exception e) {
throw new RuntimeException("SM4 加解密失败: " + e.getMessage(), e);
}
}
// -------------------- 测试示例 --------------------
public static void main(String[] args) {
// 1. 生成密钥和 IV
String key = SM4Utils.generateKeyBase64();
String iv = SM4Utils.generateIvBase64();
String plaintext = "Hello, SM4测试!";
// 2. ECB 模式加密/解密
String ciphertextEcb = SM4Utils.encryptEcbBase64(plaintext, key);
String decryptedEcb = SM4Utils.decryptEcbBase64(ciphertextEcb, key);
System.out.println("ECB 模式加密结果: " + ciphertextEcb);
System.out.println("ECB 模式解密结果: " + decryptedEcb);
// 3. CBC 模式加密/解密(自动生成 IV)
String ciphertextCbc = SM4Utils.encryptCbcBase64(plaintext, key, null);
String decryptedCbc = SM4Utils.decryptCbcBase64(ciphertextCbc, key);
System.out.println("CBC 模式加密结果: " + ciphertextCbc);
System.out.println("CBC 模式解密结果: " + decryptedCbc);
}
}