前言
SM4
分组明码算法,是由国家明码局公布的国产商用明码算法。该算法的分组长度为 128 bit,密钥长度为 128 bit。具体算法形容能够查阅 GB/T 32907-2016《信息安全技术 SM4 分组明码算法》
。
本文 SM4
的 java 实现办法,在 BC 库(bouncycastle
)的根底上做了简略的封装,JS 办法在 sm-crypto
的根底上做的封装。
JAVA
加解密办法
<!-- 轻量级加密 API -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.68</version>
</dependency>
import java.security.SecureRandom;
import java.security.Security;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
/**
* 国密 SM4 分组明码算法工具类(对称加密)* <p>GB/T 32907-2016 信息安全技术 SM4 分组明码算法 </p>
*
* @author BBF
* @see <a href="http://www.gb688.cn/bzgk/gb/newGbInfo?hcno=7803DE42D3BC5E80B0C3E5D8E873D56A">GB/T
* 32907-2016</a>
*/
public class Sm4Util {
private static final String ALGORITHM_NAME = "SM4";
private static final String ALGORITHM_ECB_PKCS5PADDING = "SM4/ECB/PKCS5Padding";
/**
* SM4 算法目前只反对 128 位(即密钥 16 字节)*/
private static final int DEFAULT_KEY_SIZE = 128;
static {
// 避免内存中呈现屡次 BouncyCastleProvider 的实例
if (null == Security.getProvider(BouncyCastleProvider.PROVIDER_NAME)) {Security.addProvider(new BouncyCastleProvider());
}
}
private Sm4Util() {}
/**
* 生成密钥
* <p> 倡议应用 org.bouncycastle.util.encoders.Hex 将二进制转成 HEX 字符串 </p>
*
* @return 密钥 16 位
* @throws Exception 生成密钥异样
*/
public static byte[] generateKey() throws Exception {KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM_NAME, BouncyCastleProvider.PROVIDER_NAME);
kg.init(DEFAULT_KEY_SIZE, new SecureRandom());
return kg.generateKey().getEncoded();
}
/**
* 加密,SM4-ECB-PKCS5Padding
*
* @param data 要加密的明文
* @param key 密钥 16 字节,应用 Sm4Util.generateKey()生成
* @return 加密后的密文
* @throws Exception 加密异样
*/
public static byte[] encryptEcbPkcs5Padding(byte[] data, byte[] key) throws Exception {return sm4(data, key, ALGORITHM_ECB_PKCS5PADDING, null, Cipher.ENCRYPT_MODE);
}
/**
* 解密,SM4-ECB-PKCS5Padding
*
* @param data 要解密的密文
* @param key 密钥 16 字节,应用 Sm4Util.generateKey()生成
* @return 解密后的明文
* @throws Exception 解密异样
*/
public static byte[] decryptEcbPkcs5Padding(byte[] data, byte[] key) throws Exception {return sm4(data, key, ALGORITHM_ECB_PKCS5PADDING, null, Cipher.DECRYPT_MODE);
}
/**
* SM4 对称加解密
*
* @param input 明文(加密模式)或密文(解密模式)* @param key 密钥
* @param sm4mode sm4 加密模式
* @param iv 初始向量(ECB 模式下传 NULL)
* @param mode Cipher.ENCRYPT_MODE - 加密;Cipher.DECRYPT_MODE - 解密
* @return 密文(加密模式)或明文(解密模式)* @throws Exception 加解密异样
*/
private static byte[] sm4(byte[] input, byte[] key, String sm4mode, byte[] iv, int mode)
throws Exception {
IvParameterSpec ivParameterSpec = null;
if (null != iv) {ivParameterSpec = new IvParameterSpec(iv);
}
SecretKeySpec sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
Cipher cipher = Cipher.getInstance(sm4mode, BouncyCastleProvider.PROVIDER_NAME);
if (null == ivParameterSpec) {cipher.init(mode, sm4Key);
} else {cipher.init(mode, sm4Key, ivParameterSpec);
}
return cipher.doFinal(input);
}
}
测试用例
import java.nio.charset.StandardCharsets;
import org.bouncycastle.util.encoders.Hex;
import org.junit.Assert;
import org.junit.Test;
public class Sm4UtilTest {
@Test
public void ecbPkcs5Padding() throws Exception {
String txt = "sm4 对称加密 <pkcs5> 演示←←";
byte[] output = Sm4Util.encryptEcbPkcs5Padding(txt.getBytes(StandardCharsets.UTF_8), KEY);
String hex = Hex.toHexString(output);
LOGGER.info("SM4-ECB-PKCS5Padding,加密输入 HEX = {}", hex);
// 解密
byte[] input = Hex.decode(hex);
output = Sm4Util.decryptEcbPkcs5Padding(input, KEY);
String s = new String(output, StandardCharsets.UTF_8);
LOGGER.info("SM4-ECB-PKCS5Padding,解密输入 = {}", s);
LOGGER.info("SM4-ECB-PKCS5Padding,key = {}", Hex.toHexString(KEY));
Assert.assertEquals(txt, s);
}
}
JS
加解密办法
/**
* SM4-ECB-PKCS5Padding 对称加密
* @param text {string} 要加密的明文
* @param secretKey {string} 密钥,43 位随机大小写与数字
* @returns {string} 加密后的密文,Base64 格局
*/
function SM4_ECB_ENCRYPT(text, secretKey) {return sm4.encrypt(text, secretKey).toUpperCase();}
/**
* SM4-ECB-PKCS5Padding 对称解密
* @param textBase64 {string} 要解密的密文,Base64 格局
* @param secretKey {string} 密钥,43 位随机大小写与数字
* @returns {string} 解密后的明文
*/
function SM4_ECB_DECRYPT(textBase64, secretKey) {return sm4.decrypt(textBase64, secretKey);
}
测试用例
var message = "sm4 对称加密 <pkcs5> 演示←←";
var key = "FA171555405706F73D7B973DB89F0B47";
var ecbEncrypt = SM4_ECB_ENCRYPT(message, key);
console.log("ecb 加密", ecbEncrypt);
var ecbDecrypt = SM4_ECB_DECRYPT(ecbEncrypt, key);
console.log("ecb 后果比拟 ---", message === ecbDecrypt)
附录
- GB/T 32907-2016 http://www.gb688.cn/bzgk/gb/newGbInfo?hcno=7803DE42D3BC5E80B0C3E5D8E873D56A
- sm-crypto https://github.com/JuneAndGreen/sm-crypto
- sm4.js https://github.com/JuneAndGreen/sm-crypto/blob/master/dist/sm4.js