文章目录
- 前言
- 一、SM4算法介绍
- 二、生成128位密钥工具类
- 三、SM4Util工具类
- 四、测试示例
前言
本文介绍了SM4算法的基本概念、安全性以及在Java中的应用,包括生成16字节密钥、加密、解密、签名和验签的过程,展示了如何在实际开发中使用SM4算法进行信息安全保护。
一、SM4算法介绍
SM4算法是一种分组密码算法,由中国国家密码管理局发布,主要用于商业密码应用。以下是关于SM4算法的一些关键信息:
SM4算法概述
- 全称:SM4 分组密码算法
- 用途:
适用于无线局域网的安全领域以及其他需要高强度加密的应用场景
。 - 分组长度:128位(16字节)
- 密钥长度:128位(16字节)
- 结构:采用Feistel网络结构
- 轮数:32轮迭代
- 安全性:具有较高的安全性和效率
SM4算法特点
- 加密和解密:加密和解密算法结构相同,只是使用的轮密钥顺序相反。
- 密钥扩展:采用32轮非线性迭代结构,每轮需要一个轮密钥。
- 资源利用率:算法资源利用率高,密钥扩展算法与加密算法可共用。
- 实现便利性:加密算法流程和解密算法流程一样,只是轮密钥顺序相反,因此无论是软件实现还是硬件实现都非常方便。
- 操作单位:算法中包含异或运算、数据的输入输出、线性置换等模块,这些模块都是按8位来进行运算的,现有的处理器都能处理。
SM4算法工作原理
- 初始化:
- 生成128位的密钥。
- 扩展密钥产生32个轮密钥。
- 加密过程:
- 输入128位的明文。
- 经过32轮迭代,每轮使用一个轮密钥。
- 输出128位的密文。
- 解密过程:
- 输入128位的密文。
- 经过32轮迭代,每轮使用一个轮密钥,但是轮密钥的顺序与加密过程相反。
- 输出128位的明文。
SM4算法的应用
- 无线局域网:SM4算法广泛应用于WLAN(无线局域网)的安全通信。
- 其他应用场景:由于其高效性和安全性,SM4也被用于其他需要加密的应用场景,如数据加密、文件加密等。
SM4算法的优势
- 高性能:SM4算法设计简洁高效,适合在各种计算平台上实现。
- 安全性:SM4算法经过严格的评估,被认为具有很高的安全性。
- 标准化:作为国家标准,SM4算法得到了广泛的接受和支持。
SM4算法的局限性
- 密钥管理:信息安全取决于对密钥的保护,密钥泄漏意味着任何人都能通过解密密文获得明文。
- 适用范围:加密算法与解密算法均使用相同的密钥,这意味着在某些特定的应用场景下,SM4算法的适用范围可能受到限制。
二、生成128位密钥工具类
package com.grafana.log;
import java.security.SecureRandom;
public class KeyGenerator {
/**
* 生成一个128位(16字节)的随机密钥。
*
* @return 生成的128位密钥字节数组。
*/
public static byte[] generate128BitKey() {
SecureRandom secureRandom = new SecureRandom();
byte[] key = new byte[16]; // 16 bytes for 128 bits
secureRandom.nextBytes(key);
return key;
}
}
SM4算法要求密钥是128位,这里笔者提供一个生成随机128位密钥字节数组的工具,实际开发中,可生成一次后转base64保存起来,使用时再解码转成字节数组。
三、SM4Util工具类
pom依赖如下:
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.70</version>
</dependency>
package com.grafana.log;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.Security;
import java.util.Arrays;
public class Sm4Util {
private static final String ALGORITHM = "SM4";
private static final String TRANSFORMATION_ECB = ALGORITHM + "/ECB/PKCS7Padding";
private static final String TRANSFORMATION_CBC = ALGORITHM + "/CBC/PKCS7Padding";
private static final String TRANSFORMATION_ECB_NoPADDING = ALGORITHM + "/ECB/NoPadding";
private static final String TRANSFORMATION_CBC_NoPADDING = ALGORITHM + "/CBC/NoPadding";
private static final String ALGORITHM_HMAC_SM4 = "HmacSHA256";
private static final String PROVIDER_NAME = "BC";
static {
Security.addProvider(new BouncyCastleProvider());
}
/**
* 签名
*/
public static byte[] sign(byte[] key, byte[] data) throws Exception {
Mac mac = Mac.getInstance(ALGORITHM_HMAC_SM4, PROVIDER_NAME);
SecretKeySpec secretKeySpec = new SecretKeySpec(key, ALGORITHM_HMAC_SM4);
mac.init(secretKeySpec);
return mac.doFinal(data);
}
/**
* 验证签名
*/
public static boolean verify(byte[] key, byte[] data, byte[] signature) throws Exception {
Mac mac = Mac.getInstance(ALGORITHM_HMAC_SM4, PROVIDER_NAME);
SecretKeySpec secretKeySpec = new SecretKeySpec(key, ALGORITHM_HMAC_SM4);
mac.init(secretKeySpec);
byte[] computedSignature = mac.doFinal(data);
return Arrays.equals(computedSignature, signature);
}
/**
* /CBC/PKCS7Padding 加密
*/
public static byte[] encryptByCbcPkcs7Padding(byte[] key, byte[] data) throws Exception{
Cipher cipher = Cipher.getInstance(TRANSFORMATION_CBC , "BC");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, ALGORITHM);
// CBC模式需要一个初始化向量
byte[] iv = new byte[16]; // 随机生成或指定
Arrays.fill(iv, (byte) 0x00); // 示例中填充为0
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
return cipher.doFinal(data);
}
/**
* /CBC/PKCS7Padding 解密
*/
public static byte[] decryptByCbcPkcs7Padding(byte[] key, byte[] data) throws Exception{
Cipher cipher = Cipher.getInstance(TRANSFORMATION_CBC , "BC");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, ALGORITHM);
byte[] iv = new byte[16]; // 随机生成或指定
Arrays.fill(iv, (byte) 0x00); // 示例中填充为0
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
return cipher.doFinal(data);
}
/**
* /ECB/PKCS7Padding 加密
*/
public static byte[] encryptByEcbPkcs7Padding(byte[] key, byte[] data) throws Exception{
Cipher cipher = Cipher.getInstance(TRANSFORMATION_ECB , "BC");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
return cipher.doFinal(data);
}
/**
* /ECB/PKCS7Padding 解密
*/
public static byte[] decryptByEcbPkcs7Padding(byte[] key, byte[] data) throws Exception{
Cipher cipher = Cipher.getInstance(TRANSFORMATION_ECB , "BC");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
return cipher.doFinal(data);
}
/**
* /ECB/NoPadding 加密
*/
public static byte[] encryptByEcbPkcs7PaddingNoPadding(byte[] key, byte[] data) throws Exception{
Cipher cipher = Cipher.getInstance(TRANSFORMATION_ECB_NoPADDING , "BC");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
return cipher.doFinal(data);
}
/**
* /ECB/NoPadding 解密
*/
public static byte[] decryptByEcbPkcs7PaddingNoPadding(byte[] key, byte[] data) throws Exception{
Cipher cipher = Cipher.getInstance(TRANSFORMATION_ECB_NoPADDING , "BC");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
return cipher.doFinal(data);
}
/**
* /CBC/NoPadding 加密
*/
public static byte[] encryptByCbcPkcs7PaddingNoPadding(byte[] key, byte[] data) throws Exception{
Cipher cipher = Cipher.getInstance(TRANSFORMATION_CBC_NoPADDING , "BC");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, ALGORITHM);
byte[] iv = new byte[16];
Arrays.fill(iv, (byte) 0x00);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
return cipher.doFinal(data);
}
/**
* /CBC/NoPadding 解密
*/
public static byte[] decryptByCbcPkcs7PaddingNoPadding(byte[] key, byte[] data) throws Exception{
Cipher cipher = Cipher.getInstance(TRANSFORMATION_CBC_NoPADDING , "BC");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, ALGORITHM);
byte[] iv = new byte[16];
Arrays.fill(iv, (byte) 0x00);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
return cipher.doFinal(data);
}
}
主要功能
- HMAC签名:
- sign:
使用HMAC-SHA256算法生成签名
。 - verify: 验证给定的签名是否正确。
- sign:
- 加密和解密:
- 支持两种工作模式:ECB(电子密码本)和CBC(密码块链接)。
- 支持两种填充方式:PKCS7Padding 和 NoPadding。
- 提供了加密和解密的方法组合,例如:
- /ECB/PKCS7Padding 加密和解密。
- /CBC/PKCS7Padding 加密和解密。
- /ECB/NoPadding 加密和解密。
- /CBC/NoPadding 加密和解密。
代码分析
- 常量定义:
- ALGORITHM: 定义SM4算法名称。
- TRANSFORMATION_ECB: ECB模式下的SM4加密变换。
- TRANSFORMATION_CBC: CBC模式下的SM4加密变换。
- TRANSFORMATION_ECB_NoPADDING: ECB模式下没有填充的SM4加密变换。
- TRANSFORMATION_CBC_NoPADDING: CBC模式下没有填充的SM4加密变换。
- ALGORITHM_HMAC_SM4: HMAC签名算法名称,这里实际上是HMAC-SHA256,因为Bouncy Castle不支持HmacSM4。
- PROVIDER_NAME: Bouncy Castle Provider的名称。
- 静态初始化块:
- Security.addProvider(new BouncyCastleProvider());: 添加Bouncy Castle Provider到JVM的安全提供者列表中。
- 加密和解密方法:
- 每种加密和解密方法都使用Cipher类进行操作。
- 方法参数包括密钥和数据。
- 对于CBC模式,还需要初始化向量(IV)。
- 使用SecretKeySpec和IvParameterSpec创建密钥和IV规格。
- 使用Cipher.init方法初始化加密或解密操作。
- 使用Cipher.doFinal方法执行加密或解密操作。
- HMAC签名方法:
- 使用Mac类进行HMAC签名操作。
- 方法参数包括密钥和数据。
- 使用Mac.init方法初始化签名操作。
- 使用Mac.doFinal方法生成签名。
四、测试示例
package com.grafana.log;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class Sm4UtilTest {
private static final byte[] KEY = KeyGenerator.generate128BitKey(); // 16字节128位的密钥
private static final String DATA = "This is a test message for SM4 encryption.";
//16字节或16字节的整数倍字符串,无填充模式NoPadding要求
private static final String DATA16 = "HelloWorldYesYes";
@Test
public void testEncryptDecryptEcbPkcs7Padding() throws Exception {
byte[] encrypted = Sm4Util.encryptByEcbPkcs7Padding(KEY, DATA.getBytes());
//签名
byte[] signatureData = Sm4Util.sign(KEY, encrypted);
//验证
boolean isValid = Sm4Util.verify(KEY, encrypted, signatureData);
System.out.println("签名验证结果:" + isValid);
byte[] decrypted = Sm4Util.decryptByEcbPkcs7Padding(KEY, encrypted);
System.out.println("解密后数据"+new String(decrypted));
}
@Test
public void testEncryptDecryptCbcPkcs7Padding() throws Exception {
byte[] encrypted = Sm4Util.encryptByCbcPkcs7Padding(KEY, DATA.getBytes());
//签名
byte[] signatureData = Sm4Util.sign(KEY, encrypted);
//验证
boolean isValid = Sm4Util.verify(KEY, encrypted, signatureData);
System.out.println("签名验证结果:" + isValid);
byte[] decrypted = Sm4Util.decryptByCbcPkcs7Padding(KEY, encrypted);
System.out.println("解密后数据"+new String(decrypted));
}
@Test
public void testEncryptDecryptEcbPkcs7PaddingNoPadding() throws Exception {
byte[] encrypted = Sm4Util.encryptByEcbPkcs7PaddingNoPadding(KEY, DATA16.getBytes());
byte[] signatureData = Sm4Util.sign(KEY, encrypted);
boolean isValid = Sm4Util.verify(KEY, encrypted, signatureData);
System.out.println("签名验证结果:" + isValid);
byte[] decrypted = Sm4Util.decryptByEcbPkcs7PaddingNoPadding(KEY, encrypted);
System.out.println("解密后数据"+new String(decrypted));
}
@Test
public void testEncryptDecryptCbcPkcs7PaddingNoPadding() throws Exception {
byte[] encrypted = Sm4Util.encryptByCbcPkcs7PaddingNoPadding(KEY, DATA16.getBytes());
byte[] signatureData = Sm4Util.sign(KEY, encrypted);
boolean isValid = Sm4Util.verify(KEY, encrypted, signatureData);
System.out.println("签名验证结果:" + isValid);
byte[] decrypted = Sm4Util.decryptByCbcPkcs7PaddingNoPadding(KEY, encrypted);
System.out.println("解密后数据"+new String(decrypted));
}
}
在测试代码中分别对工具类中的四种方式的加密、签名、验签、解密进行了演示,演示结果依次如下:
注意:SM4Util工具类中后两个不需要填充的,要求加密的数据块大小必须是16字节的整数倍,否则会报错
,实际开发中中前两种用的比较多。