SM2和SM4加密算法详解
SM2和SM4加密算法详解
目录
- 概述
- SM2算法详解
- SM4算法详解
- Java实现工具
- 实际应用场景
- 完整代码示例
概述
SM2和SM4是中国国家密码管理局发布的商用密码算法:
算法 | 类型 | 主要用途 | 密钥特点 | 性能 |
---|---|---|---|---|
SM2 | 非对称加密 | 数字签名、身份认证、密钥交换 | 公钥+私钥对 | 较慢 |
SM4 | 对称加密 | 数据加密解密 | 单一共享密钥 | 较快 |
SM2算法详解
算法原理
SM2基于椭圆曲线离散对数问题,使用的椭圆曲线方程为:
y² = x³ + ax + b (mod p)
核心参数(256位)
- 素数p: FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF
- 参数a: FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC
- 参数b: 28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93
- 基点G: 椭圆曲线上的基础点坐标
- 阶n: FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B61C6823577B
SM2的三个核心操作
1. 密钥生成(Key Generation)
目的:创建一对密钥(公钥和私钥)
比喻:制作一把锁和对应的钥匙
- 私钥 = 钥匙(保密,只有你知道)
- 公钥 = 锁(公开,任何人都能用)
执行步骤:
// 第一步:初始化密钥生成器
ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator();// 第二步:设置生成参数(使用SM2的数学参数)
keyPairGenerator.init(new ECKeyGenerationParameters(SM2_DOMAIN, new SecureRandom()));// 第三步:生成密钥对
AsymmetricCipherKeyPair keyPair = keyPairGenerator.generateKeyPair();// 第四步:分别获取公钥和私钥
ECPrivateKeyParameters privateKey = (ECPrivateKeyParameters) keyPair.getPrivate();
ECPublicKeyParameters publicKey = (ECPublicKeyParameters) keyPair.getPublic();
数学原理:
- 随机选择私钥:d ∈ [1, n-1]
- 计算公钥:P = d × G(椭圆曲线点乘)
2. 数字签名(Digital Signature)
目的:证明文件/消息确实是你发送的,没有被篡改
比喻:在文件上盖你的私人印章
- 用你的私钥(印章)在消息上"盖章"
- 生成一个签名(印章印记)
应用场景:
- 银行转账确认
- 合同签署
- 软件更新验证
执行步骤:
// 假设要给这条消息签名
String message = "转账给张三1000元";
byte[] messageBytes = message.getBytes();// 第一步:创建签名器
SM2Signer signer = new SM2Signer();// 第二步:用私钥初始化签名器(准备盖章)
signer.init(true, privateKey); // true表示签名模式// 第三步:把消息"喂"给签名器
signer.update(messageBytes, 0, messageBytes.length);// 第四步:生成签名(盖章完成)
byte[] signature = signer.generateSignature();
数学原理:
- 计算消息摘要:e = Hash(M)
- 生成随机数k,计算:(x1, y1) = k × G
- 计算:r = (e + x1) mod n
- 计算:s = (1 + dA)^(-1) × (k - r × dA) mod n
- 签名结果:(r, s)
3. 签名验证(Signature Verification)
目的:验证签名是否真实,消息是否被篡改
比喻:检查印章是否真实
- 用发送者的公钥(公开的印章样本)
- 验证印章(签名)是否匹配
执行步骤:
// 接收方收到了:原消息 + 签名
// 现在要验证签名是否有效// 第一步:创建验证器
SM2Signer verifier = new SM2Signer();// 第二步:用公钥初始化验证器(准备验证印章)
verifier.init(false, publicKey); // false表示验证模式// 第三步:把原消息"喂"给验证器
verifier.update(messageBytes, 0, messageBytes.length);// 第四步:验证签名
boolean isValid = verifier.verifySignature(signature);if (isValid) {System.out.println("签名有效!消息确实是发送者发的,且未被篡改");
} else {System.out.println("签名无效!消息可能被篡改或者不是声称的发送者发的");
}
数学原理:
- 计算:t = (r + s) mod n
- 计算:(x1’, y1’) = s × G + t × PA
- 计算:r’ = (e + x1’) mod n
- 验证:r’ = r 是否成立
SM4算法详解
算法原理
SM4是分组密码算法,采用Feistel结构:
- 分组长度:128位(16字节)
- 密钥长度:128位(16字节)
- 轮数:32轮迭代
核心组件
- S盒(Sbox):8×8的非线性变换表
- 线性变换L:位移和异或运算
- 轮函数F:T变换 = L变换 ∘ τ变换
SM4的核心操作
1. 密钥生成
// 生成128位(16字节)的随机密钥
byte[] key = new byte[16];
SecureRandom.getInstanceStrong().nextBytes(key);
2. 加密流程
执行步骤:
// 第一步:创建SM4引擎
SM4Engine engine = new SM4Engine();
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(engine)
);// 第二步:用密钥初始化加密器
cipher.init(true, new KeyParameter(key)); // true表示加密模式// 第三步:处理明文数据
byte[] plaintext = "机密信息".getBytes();
byte[] output = new byte[cipher.getOutputSize(plaintext.length)];
int len = cipher.processBytes(plaintext, 0, plaintext.length, output, 0);// 第四步:完成加密
cipher.doFinal(output, len);
数学原理:
输入:X0, X1, X2, X3 = 明文分组(每个32位)
for i in range(32):Xi+4 = Xi ⊕ T(Xi+1 ⊕ Xi+2 ⊕ Xi+3 ⊕ rki)
输出:密文 = X35, X34, X33, X32
3. 解密流程
执行步骤:
// 第一步:创建SM4引擎
SM4Engine engine = new SM4Engine();
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(engine)
);// 第二步:用密钥初始化解密器
cipher.init(false, new KeyParameter(key)); // false表示解密模式// 第三步:处理密文数据
byte[] output = new byte[cipher.getOutputSize(ciphertext.length)];
int len = cipher.processBytes(ciphertext, 0, ciphertext.length, output, 0);// 第四步:完成解密
cipher.doFinal(output, len);
Java实现工具
1. Bouncy Castle(推荐)
Maven依赖:
<dependency><groupId>org.bouncycastle</groupId><artifactId>bcprov-jdk15on</artifactId><version>1.70</version>
</dependency>
特点:
- 功能最完整,支持SM2/SM3/SM4
- 社区活跃,更新及时
- 性能中等,稳定可靠
2. 华为鲲鹏BouncyCastle(KAE)
Maven依赖:
<dependency><groupId>com.huawei.kae</groupId><artifactId>kae-provider</artifactId><version>1.1.18</version>
</dependency>
特点:
- 硬件加速,性能最优
- 需要特定硬件支持
- 适合高性能场景
3. 使用JCA Provider方式
// 注册Provider
Security.addProvider(new BouncyCastleProvider());// 使用标准JCA接口
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC", "BC");
ECGenParameterSpec ecSpec = new ECGenParameterSpec("sm2p256v1");
keyGen.initialize(ecSpec);
KeyPair keyPair = keyGen.generateKeyPair();// SM4对称加密
Cipher cipher = Cipher.getInstance("SM4/ECB/PKCS5Padding", "BC");
SecretKeySpec keySpec = new SecretKeySpec(key, "SM4");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
byte[] encrypted = cipher.doFinal(plaintext);
实际应用场景
组合使用策略
在实际应用中,SM2和SM4经常组合使用:
- 用SM2传输SM4的密钥(密钥交换)
- 用SM4加密大量数据(性能好)
- 用SM2对数据进行签名(验证完整性)
典型应用场景
场景 | SM2的作用 | SM4的作用 |
---|---|---|
网银转账 | 数字签名确认身份 | 加密传输数据 |
电子合同 | 合同签名防篡改 | 合同内容加密 |
文件传输 | 身份验证 | 文件内容加密 |
API接口 | 请求签名验证 | 敏感数据加密 |
完整代码示例
SM2完整示例
public class SM2Example {public static void main(String[] args) {try {// === 第一阶段:密钥生成 ===System.out.println("=== SM2密钥生成 ===");AsymmetricCipherKeyPair keyPair = generateSM2KeyPair();ECPrivateKeyParameters privateKey = (ECPrivateKeyParameters) keyPair.getPrivate();ECPublicKeyParameters publicKey = (ECPublicKeyParameters) keyPair.getPublic();System.out.println("密钥生成完成");// === 第二阶段:数字签名 ===System.out.println("\n=== 数字签名 ===");String message = "重要合同内容:转账1000万元";byte[] signature = signMessage(message, privateKey);System.out.println("原始消息: " + message);System.out.println("签名完成,签名长度: " + signature.length + " 字节");// === 第三阶段:签名验证 ===System.out.println("\n=== 签名验证 ===");boolean isValid = verifySignature(message, signature, publicKey);System.out.println("签名验证结果: " + (isValid ? "有效" : "无效"));// === 第四阶段:篡改测试 ===System.out.println("\n=== 篡改测试 ===");String tamperedMessage = "重要合同内容:转账1元"; // 故意篡改boolean isTamperedValid = verifySignature(tamperedMessage, signature, publicKey);System.out.println("篡改后验证结果: " + (isTamperedValid ? "有效" : "无效"));} catch (Exception e) {e.printStackTrace();}}// 生成SM2密钥对private static AsymmetricCipherKeyPair generateSM2KeyPair() {ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator();keyPairGenerator.init(new ECKeyGenerationParameters(SM2ParameterSpec.INSTANCE, new SecureRandom()));return keyPairGenerator.generateKeyPair();}// SM2数字签名private static byte[] signMessage(String message, ECPrivateKeyParameters privateKey) {try {SM2Signer signer = new SM2Signer();signer.init(true, privateKey);byte[] messageBytes = message.getBytes("UTF-8");signer.update(messageBytes, 0, messageBytes.length);return signer.generateSignature();} catch (Exception e) {throw new RuntimeException("签名失败", e);}}// SM2签名验证private static boolean verifySignature(String message, byte[] signature, ECPublicKeyParameters publicKey) {try {SM2Signer verifier = new SM2Signer();verifier.init(false, publicKey);byte[] messageBytes = message.getBytes("UTF-8");verifier.update(messageBytes, 0, messageBytes.length);return verifier.verifySignature(signature);} catch (Exception e) {throw new RuntimeException("验证失败", e);}}
}
SM4完整示例
public class SM4Example {public static void main(String[] args) {try {// === 第一阶段:密钥生成 ===System.out.println("=== SM4密钥生成 ===");byte[] key = generateSM4Key();System.out.println("SM4密钥生成完成,密钥长度: " + key.length + " 字节");// === 第二阶段:数据加密 ===System.out.println("\n=== 数据加密 ===");String plaintext = "这是需要加密的机密信息:银行账户密码123456";byte[] encrypted = encryptSM4(plaintext.getBytes("UTF-8"), key);System.out.println("原始数据: " + plaintext);System.out.println("加密完成,密文长度: " + encrypted.length + " 字节");System.out.println("密文(Base64): " + Base64.getEncoder().encodeToString(encrypted));// === 第三阶段:数据解密 ===System.out.println("\n=== 数据解密 ===");byte[] decrypted = decryptSM4(encrypted, key);String decryptedText = new String(decrypted, "UTF-8");System.out.println("解密结果: " + decryptedText);System.out.println("解密成功: " + plaintext.equals(decryptedText));} catch (Exception e) {e.printStackTrace();}}// 生成SM4密钥private static byte[] generateSM4Key() {byte[] key = new byte[16]; // SM4使用128位(16字节)密钥new SecureRandom().nextBytes(key);return key;}// SM4加密private static byte[] encryptSM4(byte[] plaintext, byte[] key) {try {SM4Engine engine = new SM4Engine();PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(engine));// 初始化加密器cipher.init(true, new KeyParameter(key));// 执行加密byte[] output = new byte[cipher.getOutputSize(plaintext.length)];int len = cipher.processBytes(plaintext, 0, plaintext.length, output, 0);len += cipher.doFinal(output, len);// 返回实际长度的数组return Arrays.copyOf(output, len);} catch (Exception e) {throw new RuntimeException("SM4加密失败", e);}}// SM4解密private static byte[] decryptSM4(byte[] ciphertext, byte[] key) {try {SM4Engine engine = new SM4Engine();PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(engine));// 初始化解密器cipher.init(false, new KeyParameter(key));// 执行解密byte[] output = new byte[cipher.getOutputSize(ciphertext.length)];int len = cipher.processBytes(ciphertext, 0, ciphertext.length, output, 0);len += cipher.doFinal(output, len);// 返回实际长度的数组return Arrays.copyOf(output, len);} catch (Exception e) {throw new RuntimeException("SM4解密失败", e);}}
}
组合使用示例
public class CombinedSM2SM4Example {public static void main(String[] args) {try {System.out.println("=== SM2+SM4组合使用示例 ===");// 1. 生成SM2密钥对(用于密钥交换和签名)AsymmetricCipherKeyPair senderKeyPair = generateSM2KeyPair();AsymmetricCipherKeyPair receiverKeyPair = generateSM2KeyPair();// 2. 生成SM4密钥(用于数据加密)byte[] sm4Key = generateSM4Key();// 3. 准备要传输的大量数据String largeData = "这是一份包含大量机密信息的文档..." + "在实际应用中,这可能是几MB甚至几GB的数据";// === 发送方操作 ===System.out.println("\n=== 发送方操作 ===");// 4. 用接收方公钥加密SM4密钥byte[] encryptedSM4Key = sm2Encrypt(sm4Key, (ECPublicKeyParameters) receiverKeyPair.getPublic());System.out.println("SM4密钥已用SM2加密");// 5. 用SM4加密大量数据byte[] encryptedData = encryptSM4(largeData.getBytes("UTF-8"), sm4Key);System.out.println("大量数据已用SM4加密");// 6. 用发送方私钥对加密数据进行签名byte[] signature = signMessage(Base64.getEncoder().encodeToString(encryptedData),(ECPrivateKeyParameters) senderKeyPair.getPrivate());System.out.println("数据已签名");// === 模拟网络传输 ===System.out.println("\n=== 网络传输 ===");System.out.println("传输内容:加密的SM4密钥 + 加密的数据 + 数字签名");// === 接收方操作 ===System.out.println("\n=== 接收方操作 ===");// 7. 用接收方私钥解密SM4密钥byte[] decryptedSM4Key = sm2Decrypt(encryptedSM4Key,(ECPrivateKeyParameters) receiverKeyPair.getPrivate());System.out.println("SM4密钥解密成功");// 8. 验证数字签名boolean signatureValid = verifySignature(Base64.getEncoder().encodeToString(encryptedData),signature,(ECPublicKeyParameters) senderKeyPair.getPublic());System.out.println("签名验证结果: " + (signatureValid ? "有效" : "无效"));if (signatureValid) {// 9. 用解密的SM4密钥解密数据byte[] decryptedData = decryptSM4(encryptedData, decryptedSM4Key);String finalResult = new String(decryptedData, "UTF-8");System.out.println("数据解密成功!");System.out.println("原始数据: " + largeData);System.out.println("解密数据: " + finalResult);System.out.println("数据完整性: " + largeData.equals(finalResult));} else {System.out.println("签名验证失败,数据可能被篡改!");}} catch (Exception e) {e.printStackTrace();}}// SM2加密(用于加密SM4密钥)private static byte[] sm2Encrypt(byte[] data, ECPublicKeyParameters publicKey) {// 实现SM2加密逻辑// 这里简化处理,实际需要完整的SM2加密实现return data; // 简化返回}// SM2解密private static byte[] sm2Decrypt(byte[] encryptedData, ECPrivateKeyParameters privateKey) {// 实现SM2解密逻辑// 这里简化处理,实际需要完整的SM2解密实现return encryptedData; // 简化返回}// 其他方法同前面示例...
}
安全使用建议
密钥管理
- 私钥保护:私钥必须安全存储,建议使用硬件安全模块(HSM)
- 密钥轮换:定期更换密钥,特别是SM4的对称密钥
- 密钥分发:SM4密钥分发要通过安全通道(如SM2加密)
实现建议
- 使用成熟库:推荐使用Bouncy Castle等经过验证的库
- 随机数质量:确保使用密码学安全的随机数生成器
- 填充模式:SM4建议使用CBC或GCM模式,避免ECB模式
- 错误处理:不要泄露加密过程中的错误信息
性能优化
- 算法选择:大数据用SM4,签名认证用SM2
- 硬件加速:在支持的环境中使用硬件加速版本
- 缓存机制:合理缓存密钥对,避免重复生成
总结
SM2和SM4算法是国产密码算法的重要组成部分:
- SM2:非对称算法,主要用于数字签名、身份认证和密钥交换
- SM4:对称算法,主要用于数据的快速加密解密
- 组合使用:在实际应用中,两者通常配合使用,发挥各自优势
- Java实现:Bouncy Castle提供了完整可靠的实现
掌握这两个算法的原理和使用方法,对于开发安全可靠的密码系统具有重要意义。