常用的国密算法包含SM2,SM3,SM4。以下针对每个算法使用场景进行说明以比较其差异
- SM2:非对称加密算法,可以替代RSA
- 数字签名,SM2为非对称加密,加解密使用一对私钥和公钥,只有签名发行者拥有私钥,可用于加密,其他需要验证解密或验签者使用公钥进行。如果使用公钥可以成功解密,则可以确定数据、文档或其他数字资产的拥有者。
- 因性能问题,根据实际需要常用于小体积数据加密,例如对密钥或SM3生成的hash进行加密。针对SM3生成的hash值进行加密也是一种常用的签名方式,一般先对需要签名的数据、文档或数字资产使用SM3生成hash再用SM2进行签名。
注:
如果用于加密,那么加密是用公钥进行的,解密是用私钥进行的。
如果用于数字签名,那么签名是用私钥进行的,验证签名则使用公钥。
- SM3:散列哈希算法
- 数据库中用户密码的保存,获取用户输入明文密码后,进行SM3生成hash值,再与数据库中保存的已经过SM3计算后的密码值进行比对。
- 数据完整性验证,针对数据、文件或数据资产进行SM3生成hash并保存,在需要验证数据是否被修改时重新生成hash并与之前保存的hash值进行比对,一旦文件有被修改则会生成不同的hash值。例如可以针对数据库中关键数据字段进行hash,并保存。然后可以通过遍历定期验证hash是否一致,来发现被篡改的数据。
- SM4:对称加密算法,性能比SM2好
- 可以用于一般数据的加密与解密,例如可以在需要网络传输的数据发送前进行加密,对方收到数据后使用相同密钥进行解密获得明文。
基于Java的SM4(ECB模式,CBC模式)对称加解密实现
简单说明:加密算法依赖了groupId:org.bouncycastle中的bcprov-jdk15to18,Bouncy Castle (
方式一:依赖bcprov-jdk15to18(以ECB模式为例)
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15to18</artifactId>
<version>1.77</version>
</dependency>
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
public class Sm4Utils {
static {
Security.addProvider(new BouncyCastleProvider());
}
private static final String ENCODING = "UTF-8";
public static final String ALGORIGTHM_NAME = "SM4";
public static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS7Padding";
public static final int DEFAULT_KEY_SIZE = 128;
private static Cipher generateEcbCipher(String algorithmName, int mode, byte[] key) throws Exception {
Cipher cipher = Cipher.getInstance(algorithmName, "BC");
Key sm4Key = new SecretKeySpec(key, ALGORIGTHM_NAME);
cipher.init(mode, sm4Key);
return cipher;
}
public static byte[] generateKey(String keyString) throws Exception {
// Use SHA-256 to hash the string and then take first 128 bits (16 bytes)
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(keyString.getBytes(StandardCharsets.UTF_8));
byte[] key = new byte[16];
System.arraycopy(hash, 0, key, 0, 16);
return key;
}
public static String encryptEcb(String key, String paramStr, String charset) throws Exception {
String cipherText = "";
if (null != paramStr && !"".equals(paramStr)) {
byte[] keyData = generateKey(key);
charset = charset.trim();
if (charset.length() <= 0) {
charset = ENCODING;
}
byte[] srcData = paramStr.getBytes(charset);
byte[] cipherArray = encryptEcbPadding(keyData, srcData);
cipherText = ByteUtils.toHexString(cipherArray);
}
return cipherText;
}
public static byte[] encryptEcbPadding(byte[] key, byte[] data) throws Exception {
Cipher cipher = generateEcbCipher("SM4/ECB/PKCS7Padding", Cipher.ENCRYPT_MODE, key);
byte[] bs = cipher.doFinal(data);
return bs;
}
public static String decryptEcb(String key, String cipherText, String charset) throws Exception {
String decryptStr = "";
byte[] keyData = generateKey(key);
byte[] cipherData = ByteUtils.fromHexString(cipherText);
byte[] srcData = decryptEcbPadding(keyData, cipherData);
charset = charset.trim();
if (charset.length() <= 0) {
charset = ENCODING;
}
decryptStr = new String(srcData, charset);
return decryptStr;
}
public static byte[] decryptEcbPadding(byte[] key, byte[] cipherText) throws Exception {
Cipher cipher = generateEcbCipher("SM4/ECB/PKCS7Padding", Cipher.DECRYPT_MODE, key);
return cipher.doFinal(cipherText);
}
public static void main(String[] args) {
try {
String json = "311111190001010001";
String key = "test";
String cipher = encryptEcb(key, json, ENCODING);
System.out.println(cipher);
System.out.println(decryptEcb(key, cipher, ENCODING));
} catch (Exception var5) {
var5.printStackTrace();
}
}
}
方式二:依赖bcprov-jdk15to18(以CBC模式为例),代码根据GPT-4生成修改调试,可运行。
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15to18</artifactId>
<version>1.77</version>
</dependency>
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.engines.SM4Engine;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.modes.CBCModeCipher;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.security.Security;
import java.util.Arrays;
public class SM4Example {
static {
Security.addProvider(new BouncyCastleProvider());
}
public static byte[] encrypt(byte[] key, byte[] iv, byte[] data) throws Exception {
SM4Engine engine = new SM4Engine();
CBCModeCipher cbcBlockCipher = CBCBlockCipher.newInstance(engine);
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(cbcBlockCipher);
CipherParameters params = new ParametersWithIV(new KeyParameter(key), iv);
cipher.init(true, params);
byte[] temp = new byte[cipher.getOutputSize(data.length)];
int len = cipher.processBytes(data, 0, data.length, temp, 0);
len += cipher.doFinal(temp, len);
byte[] out = new byte[len];
System.arraycopy(temp, 0, out, 0, len);
return out;
}
public static byte[] decrypt(byte[] key, byte[] iv, byte[] data) throws Exception {
SM4Engine engine = new SM4Engine();
CBCModeCipher cbcBlockCipher = CBCBlockCipher.newInstance(engine);
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(cbcBlockCipher);
CipherParameters params = new ParametersWithIV(new KeyParameter(key), iv);
cipher.init(false, params);
byte[] temp = new byte[cipher.getOutputSize(data.length)];
int len = cipher.processBytes(data, 0, data.length, temp, 0);
len += cipher.doFinal(temp, len);
byte[] out = new byte[len];
System.arraycopy(temp, 0, out, 0, len);
return out;
}
public static void main(String[] args) throws Exception {
byte[] key = "0123456789abcdef".getBytes(); // 16-byte key for SM4
byte[] iv = "abcdef9876543210".getBytes(); // 16-byte IV for CBC mode
byte[] dataToEncrypt = "Hello, Bouncy Castle SM4!".getBytes();
byte[] encryptedData = encrypt(key, iv, dataToEncrypt);
System.out.println("Encrypted Data: " + java.util.Base64.getEncoder().encodeToString(encryptedData));
byte[] decryptedData = decrypt(key, iv, encryptedData);
System.out.println("Decrypted Data: " + new String(decryptedData));
}
}