SM4概念介绍
SM4是一种对称加密算法,也被称为国密算法或商密算法。它是中国国家密码管理局(State Cryptography Administration,SCA)发布的一套国家密码标准中的一部分,用于数据加密和保护敏感信息。SM4算法的设计旨在提供高度的安全性和性能,适用于各种应用场景,包括电子商务、金融交易、通信系统等。
SM4算法的一些重要特点和特性:
- 分组大小和密钥长度: SM4算法采用128位的分组大小(16字节)和128位的密钥长度。这种分组大小和密钥长度使得SM4适用于大多数安全需求。
- 分组密码: SM4是一种分组密码,意味着它将输入数据分成固定大小的块(分组),然后对每个分组应用加密算法。这种模式允许SM4对长数据进行安全且高效的加密。
- Feistel结构: SM4采用Feistel结构,这是一种常见的对称加密算法结构。在Feistel结构中,输入分组会被分成两部分,然后通过多轮的加密运算和密钥混合来生成输出。
- S盒和轮密钥: SM4使用了S盒(Substitution Box)和轮密钥(Round Key)。S盒执行字节替换,轮密钥用于每轮加密时的密钥混合。
- 高度的混淆和扩散: SM4注重混淆(confusion)和扩散(diffusion)的设计原则,这有助于提高算法的安全性和抵抗各种攻击。
- 多轮结构: SM4算法进行多轮的加密运算,每轮包括S盒替换、线性变换、轮密钥混合等步骤。多轮设计增强了算法的复杂性和安全性。
- 密钥扩展: SM4使用密钥扩展算法来生成每轮加密所需的轮密钥。密钥扩展确保了每轮的密钥都是独立的,增加了攻击者破解密钥的难度。
SM4加密模式
不同的加密模式适用于不同的场景和需求,以下是一些常见的加密模式及其适用情况:
- ECB(Electronic Codebook)模式:
-
- 每个数据块独立加密,不考虑前后数据的关系。
- 适用于小数据块的加密,不适合加密大量数据。
- 不适用于保护数据完整性,因为相同的明文块会生成相同的密文块。
- CBC(Cipher Block Chaining)模式:
-
- 需要提供一个初始向量(IV)来增加数据的随机性。
- 适用于需要保护数据完整性且数据块较大的情况。
- 可以防止明文块的重复,但是对初始向量的管理要求较高。
- CFB(Cipher Feedback)模式:
-
- 允许部分解密,适用于需要在加密和解密之间进行频繁切换的场景。
- 可以加密小块数据。
- 对于大数据流的加密,性能可能较差。
- OFB(Output Feedback)模式:
-
- 类似于 CFB,但更适用于流加密,因为密钥流可以预先生成。
- 可以加密小块数据。
- 与 CFB 类似,性能可能较差。
- CTR(Counter)模式:
-
- 允许并行加密和解密,适用于大数据流的加密。
- 密钥流的生成与数据无关,可以预先生成。
- 不需要初始向量,但需要一个唯一的计数器。
加密模式的选择取决于你的应用需求和安全性要求。例如,对于数据块较大的情况,CBC 模式可能更适合,而对于大数据流的加密,CTR 模式可能更有效。在实际应用中,还需要考虑密钥管理、数据填充、初始向量的安全性、异常处理等因素。选择适合的加密模式是确保数据安全性的重要一步。
SM4加密算法使用流程
- 选择加密模式: 首先,你需要确定要使用的加密模式,如 ECB、CBC、CFB、OFB、CTR 等。不同的加密模式适用于不同的场景和需求。
- 生成密钥: 生成一个合适长度的密钥,SM4 使用的密钥长度为 128 位(16 字节)。
- 选择初始向量(IV): 对于需要使用 CBC、CFB、OFB、CTR 等模式的加密,你需要选择一个合适的初始向量(IV)。
- 数据填充: 对于分组密码算法,要求数据长度必须是分组大小的整数倍。如果原始数据不是分组大小的整数倍,需要进行数据填充,常用的填充方式有 PKCS#7 和 Zero Padding。
- 加密: 使用选择的加密模式,将填充后的数据和生成的密钥传入 SM4 加密算法中进行加密操作。
- 解密: 如果需要解密,将加密后的数据和相应的密钥传入 SM4 解密算法中进行解密操作。
- 处理解密结果: 解密得到的数据可能包含填充字节,需要根据填充方式进行相应的处理,以获取原始数据。
在实际应用中,根据数据保护的需求和安全性要求,选择合适的加密模式,并遵循密码学的最佳实践来确保数据的安全性和完整性。同时,还需要考虑异常处理、密钥管理以及与其他安全机制的配合。
SM4加密工具类
使用bc库进行SM4加密,填充模式有以下几种
- NoPadding: 该填充方式不对数据进行填充,要求明文数据的长度必须是块大小的倍数(16字节),否则会抛出异常。
- PKCS5Padding: 基于PKCS#5标准的填充方式,适用于块密码加密,会根据明文长度在明文末尾添加相应数量的填充字节,使得明文长度成为块大小的倍数。
- ISO10126Padding: 该填充方式将明文末尾填充随机字节,其中最后一个字节指示填充的字节数。
- X923Padding: 与ISO10126Padding类似,但最后一个字节是填充的字节数,其他字节填充0。
ECB和CBC使用NoPadding填充模式时,需要加密字段为16位整数倍,才能正常进行加密,否则会提示:data not block size aligned 异常
其他模式没有这个限制,可以正常进行加密解密。
完整SM4工具类
package com.example.testvpndemo.util;
import android.util.Base64;
import android.util.Log;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Hex;
import java.security.AlgorithmParameters;
import java.security.Key;
import java.security.SecureRandom;
import java.security.Security;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/**
* @Description SM4 工具类
* @Time 2023/8/15 9:11
* @Author gz
**/
public class SM4Util {
private static String TAG = SM4Util.class.getSimpleName();
public static final int DEFAULT_KEY_SIZE = 128;
public static final String ALGORITHM_NAME = "SM4";
static {
double version = Security.getProvider(BouncyCastleProvider.PROVIDER_NAME).getVersion();
Log.i(TAG, "generateKey1: " + version);
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
Log.i("sys", "运行环境没有BouncyCastleProvider");
Security.addProvider(new BouncyCastleProvider());
}
version = Security.getProvider(BouncyCastleProvider.PROVIDER_NAME).getVersion();
Log.i(TAG, "generateKey2: " + version);
}
/**
* 生成密钥
*
* @return
* @explain
*/
public static String generateKey() {
// 创建一个安全的随机数生成器
SecureRandom secureRandom = new SecureRandom();
// 生成一个 128 位(16 字节)的随机密钥
byte[] keyBytes = new byte[16];
secureRandom.nextBytes(keyBytes);
return Hex.toHexString(keyBytes);
}
/**
* 生成初始化向量iv
*/
public static AlgorithmParameters generateIV() throws Exception {
SecureRandom secureRandom = new SecureRandom();
byte[] iv = new byte[16];
//随机生成bytes数组
secureRandom.nextBytes(iv);
AlgorithmParameters params = AlgorithmParameters.getInstance(ALGORITHM_NAME);
IvParameterSpec spec = new IvParameterSpec(iv);
params.init(spec);
Log.i(TAG, "generateKey: iv:" + iv);
return params;
}
/**
* @param algorithmName 算法名称
* @param key 密钥
* @param iv 初始向量
* @param data 加密/解密数据 Hex.decode()
* @return 加密后数据
* @throws Exception
* @description 加密
*/
public static byte[] encrypt(String algorithmName, byte[] key, byte[] iv, byte[] data) throws Exception {
return sm4Core(algorithmName, Cipher.ENCRYPT_MODE, key, iv, data);
}
/**
* @param algorithmName 算法名称
* @param key 密钥
* @param iv 初始向量
* @param data 加密数据
* @return 解密结果
* @throws Exception
* @description 解密
*/
public static byte[] decrypt(String algorithmName, byte[] key, byte[] iv, byte[] data) throws Exception {
return sm4Core(algorithmName, Cipher.DECRYPT_MODE, key, iv, data);
}
/**
* 加密/解密
*
* @param algorithmName 算法名称
* @param type 加密/解密
* @param key 密钥
* @param iv 初始向量
* @param data 加密/解密数据
* @return 结果
* @throws Exception
*/
private static byte[] sm4Core(String algorithmName, int type, byte[] key, byte[] iv, byte[] data)
throws Exception {
//指定加密算法
Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);// 初始化加密器,设置加密模式和初始向量
//ecb不需要设置初始向量
if (algorithmName.contains("/ECB/")) {
cipher.init(type, sm4Key);
} else {
// 创建初始向量参数
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
//// 初始化加密器,设置加密模式和初始向量
cipher.init(type, sm4Key, ivParameterSpec);
}
return cipher.doFinal(data);
}
}
密钥生成
try {
createKey = SM4Util.generateKey();
algorithmParameters = SM4Util.generateIV();
IvParameterSpec spec = SM4Util.generateIV().getParameterSpec(IvParameterSpec.class);
ivBytes = spec.getIV();
String generateIV = Hex.toHexString(ivBytes);
viewModel.sm4CreateKey.set("密钥:\n" + createKey + "\n向量IV:\n" + generateIV);
} catch (Exception e) {
e.printStackTrace();
}
加密解密调用
ECB加密调用
try {
ECBEncodeByte = SM4Util.encrypt("SM4/ECB/" + chooseMode, Hex.decode(createKey), ivBytes, paramStr);
cipher = Hex.toHexString(ECBEncodeByte);
System.out.println(cipher);
} catch (Exception e) {
cipher = "加密失败:" + e.getMessage();
e.printStackTrace();
}
ECB解密调用
try {
byte[] bytes;
bytes = SM4Util.decrypt("SM4/ECB/" + chooseMode, Hex.decode(createKey), ivBytes, tempBytes);
result = new String(bytes);
} catch (Exception e) {
result = "解密失败" + e.getMessage();
e.printStackTrace();
}