前言

本文介绍了SM4算法的基本概念、安全性以及在Java中的应用,包括生成16字节密钥、加密、解密、签名和验签的过程,展示了如何在实际开发中使用SM4算法进行信息安全保护。


一、SM4算法介绍

SM4算法是一种分组密码算法,由中国国家密码管理局发布,主要用于商业密码应用。以下是关于SM4算法的一些关键信息:

SM4算法概述

  • 全称:SM4 分组密码算法
  • 用途:适用于无线局域网的安全领域以及其他需要高强度加密的应用场景
  • 分组长度:128位(16字节)
  • 密钥长度:128位(16字节)
  • 结构:采用Feistel网络结构
  • 轮数:32轮迭代
  • 安全性:具有较高的安全性和效率

SM4算法特点

  • 加密和解密:加密和解密算法结构相同,只是使用的轮密钥顺序相反。
  • 密钥扩展:采用32轮非线性迭代结构,每轮需要一个轮密钥。
  • 资源利用率:算法资源利用率高,密钥扩展算法与加密算法可共用。
  • 实现便利性:加密算法流程和解密算法流程一样,只是轮密钥顺序相反,因此无论是软件实现还是硬件实现都非常方便。
  • 操作单位:算法中包含异或运算、数据的输入输出、线性置换等模块,这些模块都是按8位来进行运算的,现有的处理器都能处理。

SM4算法工作原理

  1. 初始化:
    • 生成128位的密钥。
    • 扩展密钥产生32个轮密钥。
  2. 加密过程:
    • 输入128位的明文。
    • 经过32轮迭代,每轮使用一个轮密钥。
    • 输出128位的密文。
  3. 解密过程:
    • 输入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);
    }


}

主要功能

  1. HMAC签名:
    • sign: 使用HMAC-SHA256算法生成签名
    • verify: 验证给定的签名是否正确。
  2. 加密和解密:
    • 支持两种工作模式:ECB(电子密码本)和CBC(密码块链接)。
    • 支持两种填充方式:PKCS7Padding 和 NoPadding。
    • 提供了加密和解密的方法组合,例如:
      • /ECB/PKCS7Padding 加密和解密。
      • /CBC/PKCS7Padding 加密和解密。
      • /ECB/NoPadding 加密和解密。
      • /CBC/NoPadding 加密和解密。

代码分析

  1. 常量定义:
    • 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的名称。
  2. 静态初始化块:
    • Security.addProvider(new BouncyCastleProvider());: 添加Bouncy Castle Provider到JVM的安全提供者列表中。
  3. 加密和解密方法:
    • 每种加密和解密方法都使用Cipher类进行操作。
    • 方法参数包括密钥和数据。
    • 对于CBC模式,还需要初始化向量(IV)。
    • 使用SecretKeySpec和IvParameterSpec创建密钥和IV规格。
    • 使用Cipher.init方法初始化加密或解密操作。
    • 使用Cipher.doFinal方法执行加密或解密操作。
  4. 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字节的整数倍,否则会报错,实际开发中中前两种用的比较多。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐