vlambda博客
学习文章列表

国密SM4分组密码算法(对称加密)的JS和JAVA类库

作者:bluesbruce
来源:SegmentFault 思否社区




前言


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>演示←←";
    String key = "FA171555405706F73D7B973DB89F0B47";
    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