博客 / 詳情

返回

JAVA 使用國密 SM4 加解密

SM4算法

百度百科

中華人民共和國政府採用的分組密碼標準
SM4.0(原名SMS4.0)是中華人民共和國國家密碼管理局於2012年3月21日發佈的分組密碼標準,對應行業標準為GM/T 0002-2012,採用128位分組長度和128位密鑰結構,加密過程基於32輪非線性迭代,並配置固定8比特輸入8比特輸出的S盒,主要應用於商用數據加密領域

在密碼學領域,國密算法 SM4 是我國自主研發的分組對稱加密算法,憑藉 128 位分組長度、128 位密鑰長度的設計,在金融、政務、物聯網等領域廣泛應用。但分組密碼本身僅能處理固定長度(SM4 為 128 位)的明文數據,而現實中需要加密的文件、數據流、存儲塊等往往是任意長度的。為解決這一問題,“工作模式” 應運而生 —— 它相當於分組密碼的 “應用框架”,定義瞭如何將固定長度的分組加密邏輯擴展到任意長度數據,同時兼顧安全性、性能與實際場景需求。SM4 的 8 種主流工作模式:ECB、CBC、CFB、OFB、CTR、GCM、CCM 與 XTS

本文簡單介紹SM4以下常用的加解密模式

  • ECB模式(電子密碼本):工作原理:將明文分成固定長度的塊(如SM4的16字節),每個塊獨立加密,互不影響。相同明文塊會生成相同的密文塊
    • 優點:ECB模塊可以並行處理數據。
    • 缺點:同樣原文生成同樣的密文,並不能很好地保護數據。
    • 安全性:存在致命缺陷 —— 相同明文分組會生成相同密文分組。例如用 ECB 加密圖片,若圖片中有重複像素塊(如背景),密文會呈現明顯的 “塊重複”,攻擊者可通過統計分析還原明文結構,甚至篡改單個密文塊(對應明文塊也會被篡改,不影響其他塊);
    • 適用場景:僅用於加密 “短且唯一” 的數據(如密鑰封裝、固定標識),絕對禁止用於文件、數據流等大數據加密。
  • CBC模式(密碼分組鏈接模式):工作原理:通過 “鏈式關聯” 解決 ECB 的關聯性問題:
    1. 明文分組按 128 位拆分,最後一塊補全;
    2. 第一個明文分組與 “初始化向量(IV)” 進行異或運算,再用 SM4 加密得到第一個密文分組;
    3. 後續每個明文分組先與前一個密文分組異或,再加密,形成 “前密文影響後明文” 的鏈式結構。
  • CBC模式特點和應用
    • 安全性:相同明文分組因 “前密文異或” 的干擾,會生成不同密文,抵禦統計分析攻擊;但密文塊被篡改後,會影響後續所有明文塊的解密(鏈式傳播);
    • 性能:加密無法並行(需等待前一個密文塊),解密可並行(已知密文塊即可獨立解密,再與前一個密文塊異或);
    • IV要求:需隨機生成且唯一(無需保密),若 IV 重複,相同明文會生成相同密文;
    • 適用場景:文件加密、早期 SSL/TLS 協議(如 TLS 1.0)、IPsec VPN,適合對並行性要求不高的場景。
  • CFB模式:將分組密碼轉化為流密碼

其餘工作模式可參考博客:https://blog.csdn.net/openHiTLS/article/details/151288800

以下使用Bouncy Castle 開源加密庫進行加解密

<!-- Maven 方式 -->
<!--  Bouncy Castle 開源加密庫      -->
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.70</version>
</dependency>

<!-- Gradle 方式 -->
implementation 'org.bouncycastle:bcprov-jdk15on:1.70'

ECB模式

SM4Utils
 package com.isoftstone.cascade02.utils;


import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Base64;

/**
 * @Author:wk
 * @Slogan:無論風雨,和自己一決勝負吧
 * @Create:2025/12/5/18:16
 * @Description:SM4算法 ECB 模式
 * @Version:1.0
 */
public class SM4Utils {

    public final static String KEY = "4c73aae48f4d5edba3a365837904dbc8";

    private static final String ALGORITHM = "SM4";
    private static final String TRANSFORM  = "SM4/ECB/PKCS5Padding";


    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    /** 生成 128-bit(16 字節)SM4 密鑰,返回 Base64 字符串 */
    /*public static String generateKey() throws Exception {
        KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM, "BC");
        kg.init(128, new SecureRandom());
        byte[] keyBytes = kg.generateKey().getEncoded();
        return Base64.getEncoder().encodeToString(keyBytes);
    }*/

    /** 生成UUID隨機數,作為16進制密鑰 */
    public static String generateKey() throws Exception {
        return CommonUtils.getUUID();
//        byte[] keyBytes = kg.generateKey().getEncoded();
//        return Base64.getEncoder().encodeToString(keyBytes);
    }

    /** 加密:明文 + Base64 密鑰 → Base64 密文 */
    public static String encrypt(String plainText, String base64Key) {
        String encrypt = "";
        try {
            byte[] key = Hex.decodeHex(base64Key);
//        byte[] key = Base64.getDecoder().decode(base64Key);
            SecretKeySpec keySpec = new SecretKeySpec(key, ALGORITHM);
            Cipher cipher = Cipher.getInstance(TRANSFORM, "BC");
            cipher.init(Cipher.ENCRYPT_MODE, keySpec);
            byte[] encrypted = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
            encrypt = Base64.getEncoder().encodeToString(encrypted);
        } catch (DecoderException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchProviderException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        }
        return encrypt;
    }

    /** 解密:Base64 密文 + Base64 密鑰 → 明文 */
    public static String decrypt(String base64Cipher, String base64Key) {
        String decrypt = "";
        try {
            byte[] key = Hex.decodeHex(base64Key);
//        byte[] key = Base64.getDecoder().decode(base64Key);
            SecretKeySpec keySpec = new SecretKeySpec(key, ALGORITHM);
            Cipher cipher = Cipher.getInstance(TRANSFORM, "BC");
            cipher.init(Cipher.DECRYPT_MODE, keySpec);
            byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(base64Cipher));
            decrypt = new String(decrypted, StandardCharsets.UTF_8);
        } catch (DecoderException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchProviderException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        }
        return decrypt;
    }

    /* ------ 簡單測試 ------ */
    public static void main(String[] args) throws Exception {
        String plain = "你好,國密 SM4!";
        String key  = generateKey();
        String cipher = encrypt(plain, key);
        String result = decrypt(cipher, key);

        System.out.println("密鑰 :" + key);
        System.out.println("密鑰 :" + KEY);
        System.out.println("明文 :" + plain);
        System.out.println("密文 :" + cipher);
        System.out.println("解密 :" + result);
    }

}

CBC模式

SM4IVUtils
package com.isoftstone.cascade02.utils;


import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Base64;

/**
 * @Author:wk
 * @Slogan:無論風雨,和自己一決勝負吧
 * @Create:2025/12/5/18:16
 * @Description:SM4 IV 算法
 * @Version:1.0
 */
public class SM4IVUtils {

    public final static String KEY = "4c73aae48f4d5edba3a365837904dbc8";

    private static final String ALGORITHM = "SM4";
    private static final String TRANSFORM  = "SM4/CBC/PKCS5Padding";

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    /** 生成 128-bit(16 字節)SM4 密鑰,返回 Base64 字符串 */
    /*public static String generateKey() throws Exception {
        KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM, "BC");
        kg.init(128, new SecureRandom());
        byte[] keyBytes = kg.generateKey().getEncoded();
        return Base64.getEncoder().encodeToString(keyBytes);
    }*/

    /** 生成UUID隨機數,作為16進制密鑰 */
    public static String generateKey() throws Exception {
        return CommonUtils.getUUID();
    }


    /** 生成 16 字節 IV */
    public static byte[] generateIV() {
        byte[] iv = new byte[16];
        new SecureRandom().nextBytes(iv);
        return iv;
    }

    /** 加密:返回 “Base64(IV) : Base64(密文)” 的拼接字符串 */
    public static String encrypt(String plainText, String base64Key){
        String encrypt = "";
        try {
            byte[] key = Hex.decodeHex(base64Key);
//        byte[] key = Base64.getDecoder().decode(base64Key);
            SecretKeySpec keySpec = new SecretKeySpec(key, ALGORITHM);

            byte[] iv  = generateIV();
            IvParameterSpec ivSpec = new IvParameterSpec(iv);

            Cipher cipher = Cipher.getInstance(TRANSFORM, "BC");
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
            byte[] cipherBytes = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));

            // 把 IV 和密文一起發出去
            String ivBase64     = Base64.getEncoder().encodeToString(iv);
            String cipherBase64 = Base64.getEncoder().encodeToString(cipherBytes);
            encrypt = ivBase64 + ":" + cipherBase64;
        } catch (DecoderException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchProviderException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        }
        return encrypt;
    }

    /** 解密:傳入 “Base64(IV) : Base64(密文)” 的拼接字符串 */
    public static String decrypt(String pack, String base64Key) {
        String decrypt = "";
        try {
            String[] parts = pack.split(":");
            if (parts.length != 2) throw new IllegalArgumentException("數據格式錯誤");
            byte[] key = Hex.decodeHex(base64Key);
//        byte[] key = Base64.getDecoder().decode(base64Key);
            SecretKeySpec keySpec = new SecretKeySpec(key, ALGORITHM);

            byte[] iv  = Base64.getDecoder().decode(parts[0]);
            byte[] cipherBytes = Base64.getDecoder().decode(parts[1]);

            Cipher cipher = Cipher.getInstance(TRANSFORM, "BC");
            cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv));
            byte[] plainBytes = cipher.doFinal(cipherBytes);
            decrypt = new String(plainBytes, StandardCharsets.UTF_8);
        } catch (DecoderException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchProviderException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        }
        return decrypt;
    }

    /* ------ 測試 ------ */
    public static void main(String[] args) throws Exception {
        String plain = "你好,國密 SM4-CBC!";
        String key   = generateKey();   // 複用上一篇的生成方法
        String pack  = encrypt(plain, KEY);
        String ret   = decrypt(pack, KEY);

        System.out.println("密鑰 :" + KEY);
        System.out.println("明文 :" + plain);
        System.out.println("包   :" + pack);
        System.out.println("解密 :" + ret);
    }
}

 

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.