1.简介

国密即国家密码局认定的国产密码算法,即商用密码。

国家密码管理局发布了一系列国产商用密码标准算法,包括SM1(SCB2)、SM2、SM3、SM4、SM7、SM9、祖冲之密码算法(ZUC)等。

SM系列密码,SM代表商密,即商业密码,是指用于商业的、不涉及国家秘密的密码技术。

2.SM4优势

  • 算法公开性:SM4 算法公开,而 SM1 和 SM7算法不公开,仅以 IP 核的形式存在于芯片中,通过芯片的接口来进行加解密。而SM4 是开源的。
  • 对称加密算法:SM4 是对称加密算法,加解密速度快,适合对大量数据进行加密,如即时通讯中的消息对话加密、文件加密等。而如SM2等国密算法是非对称加密算法,主要用于数字签名和密钥交换,其加密速度相对较慢,不太适合对长消息进行加密。
  • 分组特性:SM4 是分组对称加密算法,分组加密可以提高安全性,密文在统计特性上更加均匀,攻击者难以从密文的统计规律中获取有用信息。其次,在效率上,由于分组加密是对每个分组独立进行加密操作,因此在硬件实现上具有良好的并行处理能力。可以同时对多个分组进行加密,从而显著提高加密效率。例如,在高性能加密设备中,利用多核处理器或专用的加密芯片,可以并行处理多个分组,加快加密速度。

SM7 算法与 SM4 类似,都是对称分组加密算法。但 SM7 主要适用于非接触式 IC 卡等特定场景,如身份识别类、票务类、支付与通卡类等场景。而 SM4 的应用场景更为广泛,在安全性和性能上也针对更通用的加密需求进行了设计。

3.工作流程

3.1 密钥扩展

将 初始128位的密钥扩展成一个包含32个32位的扩展密钥数组。这个过程包括S盒变换、线性变换以及轮函数的应用。目的是生成一系列子密钥以供后续加密使用。

3.2 轮密钥

SM4算法通过32轮非线性迭代结构来保障其安全性。

每一轮都应用一个复杂的轮函数,该函数包含四个主要操作:

1.非线性变换(S盒):通过查找表的方式实现,提供良好的混淆效果。

2.线性变换: 基于矩阵运算,增加输出的复杂度和不可预测性。

3.轮密钥加:将当前轮的子密钥与状态进行异或操作,增强安全性。

4.字节重排: 调整字节顺序,进一步扰乱数据结构。

3.3 加密过程

输入的明文首先被分成四个32位的字,然后依次经过32轮轮函数的处理,最终得到对应的密文。

3.4解密过程

解密本质上是加密的逆过程。区别在于使用的轮密钥顺序相反,但轮函数本身保持不变。

4.springboot集成SM4

4.1 通过hutool实现SM4

4.1.1.在 pom.xml 文件添加国密算法依赖

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15to18</artifactId>
    <version>1.76</version>
</dependency>
 
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.25</version>
</dependency>

4.1.2.编写加密工具类

import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.symmetric.SM4;
 
public class SM4Utils {
 
    /**
     * SM4是对称加密,需要设置一个加解密秘钥
     * <p>
     * System.out.println(Arrays.toString("@ljm2025#$%^&*!+".getBytes(StandardCharsets.UTF_8)));
     * 特别注意字符串key的长度需要16位
     */
    private static final byte[] keys = new byte[]{64, 74, 104, 120, 50, 48, 50, 52, 35, 36, 37, 94, 38, 42, 33, 43};
 
    /**
     * 创建一个SM4加解密对象
     */
    private static final SM4 sm4 = SmUtil.sm4(keys);
 
    /**
     * 设置一个标识符,标识@SM4@- 开头的字符串是经过SM4加密的需要解密
     */
    public static final String SM4_PREFIX = "@SM4@-";
 
 
    /**
     * 对字符串进行加密
     *
     * @param value
     * @return
     */
    public static String encryptStr(String value) {
        // 对加密的字符串添加前缀,方便标识这是一个加密以后的字符串
        return SM4_PREFIX + sm4.encryptBase64(value);
    }
 
    /**
     * 对字符串进行解密
     *
     * @param encryptValue
     * @return
     */
    public static String decryptStr(String encryptValue) {
        // 解密时,需要去除加密标识符
        return encryptValue.startsWith(SM4_PREFIX) ? sm4.decryptStr(encryptValue.substring(SM4_PREFIX.length())) : encryptValue;
    }
}

a.首先,既然是对称加密算法,那么就应该有一个密钥,而SM4的密钥大小要求16字节(128位)。我是通过如下的方式截取前16个字节元素来生成的密钥。

    public static void main(String[] args) {
     System.out.println(Arrays.toString("@ljm2025#$%^&*!+".getBytes(StandardCharsets.UTF_8)));
    }

b.有了密钥,我们就需要创建加解密算法对象。

/**
     * 创建一个SM4加解密对象
     */
    private static final SM4 sm4 = SmUtil.sm4(keys);

通过static final修饰来使sm4对象在类加载时就进行初始化,参数就是密钥。

c.最后就是加密和解密。我通过前面加个sm4算法标识来防止数据异常导致的解密错误。

 /**
     * 设置一个标识符,标识@SM4@- 开头的字符串是经过SM4加密的需要解密
     */
    public static final String SM4_PREFIX = "@SM4@-";


    /**
     * 对字符串进行加密
     *
     * @param value
     * @return
     */
    public static String encryptStr(String value) {
        // 对加密的字符串添加前缀,方便标识这是一个加密以后的字符串
        return SM4_PREFIX + sm4.encryptBase64(value);
    }

    /**
     * 对字符串进行解密
     *
     * @param encryptValue
     * @return
     */
    public static String decryptStr(String encryptValue) {
        // 解密时,需要去除加密标识符
        return encryptValue.startsWith(SM4_PREFIX) ? sm4.decryptStr(encryptValue.substring(SM4_PREFIX.length())) : encryptValue;
    }

hutool工具包的这种方式,能够让我们简单快速的实现SM4算法。

4.1.3.模式

首先,如上的方式就是ECB模式的简单实现,这也是最简单的工作模式,但是这种模式却是具有一定的安全问题,因为它相同的明文会生成相同的密钥。

SM4的工作模式主要有ECB、CBC、CFB、OFB、CTR等。其中,CFB、OFB、CTR三种模式属于流密码。

在这里我就不赘述了,本人也没有研究太深,我下面只给大家一个简单介绍。

ECB模式将明文分组直接单独进行加密得到密文分组。 →→ 直接加密明文分组

CBC模式将明文分组先与前一个密文分组(或IVIV)异或再加密得到密文分组。 →→ 对明文分组与前一个明文分组的异或结果加密

CFB模式将前一个密文(或 IVIV)进行加密后再与明文异或得到密文。 →→ 先加密前一个密文再与明文异或

OFB模式将 IV(向量) 进行加密得到密钥流,再将该密钥流与明文异或得到密文。 →→ 先生成密钥流再与明文异或

CTR模式将计数器加密后再与明文分组异或得到密文分组。

4.2 非工具包的方式实现SM4

4.2.1 JAVA中JCA介绍

JCA(Java Cryptography Architecture) 是 Java 平台内置的加密体系结构,定义了加密服务(如密钥管理、数字签名、消息摘要)的接口与核心逻辑,但‌不直接提供具体算法实现‌,而是通过“Provider”机制动态加载第三方实现模块‌。

4.2.2 Bouncy Castle的介绍
Bouncy Castle 是一个开源的加密库,通过实现 JCA/JCE(Java Cryptography Extension)接口,提供‌扩展算法支持‌(如 SM2/SM4 等中国商用密码算法)和更丰富的安全协议实现,作为 JCA 的补充 Provider 存在‌。

4.2.3 两者的覆盖范围差异‌
JCA 默认仅支持基础算法(如 AES、RSA、SHA-256),且受限于国际出口限制,部分高强度算法可能缺失‌。
Bouncy Castle 提供 50+ 加密算法(包括国密算法)及 SSL/TLS、OpenPGP 等协议支持,弥补 JCA 的功能局限‌。

4.2.4 逻辑介绍

开发者可通过 java.security.Security.addProvider(new BouncyCastleProvider()) 将 Bouncy Castle 注册为 JCA 的 Provider,之后即可通过标准 JCA API 调用其算法(如 Cipher.getInstance("SM2", "BC")。

此时代码仍遵循 JCA 标准,仅底层实现替换为 Bouncy Castle‌。

4.2.5 引入

 <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk15to18</artifactId>
            <version>1.76</version>
        </dependency>

4.2.6 实现

public class Sm4Util{
        // SM4密钥长度为128位(16字节)
        private static final int SM4_KEY_LENGTH = 16;
        // SM4块大小为128位(16字节),IV长度必须与块大小相同
        private static final int SM4_IV_LENGTH = 16;

        static {
            // 注册Bouncy Castle安全提供者
            if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
                Security.addProvider(new BouncyCastleProvider());
            }
        }

        /**
         * 使用SM4-CBC模式加密数据,随机生成IV并将其与密文拼接
         * @param plainText 明文字符串(UTF-8编码)
         * @param key 密钥(必须为16字节)
         * @return Base64编码的密文(格式:IV[16字节] + 加密数据)
         * @throws IllegalArgumentException 密钥长度不合法
         * @throws Exception 加密过程异常
         */
        public static String encrypt(String plainText, byte[] key) throws Exception {
            // 验证密钥长度
            if (key.length != SM4_KEY_LENGTH) {
                throw new IllegalArgumentException("SM4密钥长度必须为16字节");
            }
            // 生成随机IV
            SecureRandom random = SecureRandom.getInstanceStrong();
            byte[] iv = new byte[SM4_IV_LENGTH];
            random.nextBytes(iv);

            // 创建加密器并初始化
            PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new SM4Engine()));
            cipher.init(true, new ParametersWithIV(new KeyParameter(key), iv));

            // 执行加密
            byte[] plainBytes = plainText.getBytes("UTF-8");
            byte[] encryptedBytes = processCipher(cipher, plainBytes);

            // 将IV和加密数据拼接
            ByteBuffer buffer = ByteBuffer.allocate(iv.length + encryptedBytes.length);
            buffer.put(iv);
            buffer.put(encryptedBytes);

            // 返回Base64编码结果
            return Base64.getEncoder().encodeToString(buffer.array());
        }

        /**
         * 使用SM4-CBC模式解密数据,从密文中提取IV
         * @param encryptedText Base64编码的密文(格式:IV[16字节] + 加密数据)
         * @param key 密钥(必须为16字节)
         * @return 解密后的明文字符串(UTF-8编码)
         * @throws IllegalArgumentException 密钥长度不合法或密文格式错误
         * @throws Exception 解密过程异常
         */
        public static String decrypt(String encryptedText, byte[] key) throws Exception {
            // 验证密钥长度
            if (key.length != SM4_KEY_LENGTH) {
                throw new IllegalArgumentException("SM4密钥长度必须为16字节");
            }

            // 解码Base64密文
            byte[] combined = Base64.getDecoder().decode(encryptedText);

            // 验证密文长度(至少包含IV长度)
            if (combined.length < SM4_IV_LENGTH) {
                throw new IllegalArgumentException("密文长度不足");
            }

            // 提取IV和加密数据
            byte[] iv = new byte[SM4_IV_LENGTH];
            byte[] encryptedBytes = new byte[combined.length - SM4_IV_LENGTH];

            System.arraycopy(combined, 0, iv, 0, SM4_IV_LENGTH);
            System.arraycopy(combined, SM4_IV_LENGTH, encryptedBytes, 0, encryptedBytes.length);

            // 创建解密器并初始化
            PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new SM4Engine()));
            cipher.init(false, new ParametersWithIV(new KeyParameter(key), iv));

            // 执行解密
            byte[] decryptedBytes = processCipher(cipher, encryptedBytes);
            return new String(decryptedBytes, "UTF-8");
        }

        /**
         * 处理加密/解密操作的通用方法
         */
        private static byte[] processCipher(PaddedBufferedBlockCipher cipher, byte[] input) throws Exception {
            try {
                byte[] output = new byte[cipher.getOutputSize(input.length)];
                int length = cipher.processBytes(input, 0, input.length, output, 0);
                length += cipher.doFinal(output, length);

                // 截取有效数据(去除填充)
                if (length == output.length) {
                    return output;
                } else {
                    byte[] result = new byte[length];
                    System.arraycopy(output, 0, result, 0, length);
                    return result;
                }
            } catch (Exception e) {
                // 包装异常以提供更明确的错误信息
                throw new Exception("SM4处理失败: " + e.getMessage(), e);
            }
        }
    public static void main(String[] args) throws Exception {
        String encrypt = encrypt("123456", "abcdef1234567890".getBytes());
        System.out.println(encrypt);
        String decrypt = decrypt(encrypt, "abcdef1234567890".getBytes());
        System.out.println(decrypt);
    }
}

如上的方式就是CBC工作模式的具体实现。

我们需要注意的是,向量一定要随机的,否则就和ECB模式一样相同明文生成相同密钥了。

---------------------------------------------------------------------------------------------------------------------------------

以上就是本文章的全部内容,如有写错的地方请及时联系我,我会进行修改。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐