iT邦幫忙

0

應用系統建置前準備工具 - SM3Utils 雜湊工具(單向加密)

  • 分享至 

  • xImage
  •  

SM3Utils 雜湊工具類別

概述

SM3Utils 是一個提供 SM3 演算法相關功能的工具類別。此類別整合了 BouncyCastle 的實作,提供完整的 SM3 雜湊、HMAC 與驗證功能,特別適合需要使用中國國密演算法的應用場景。

專案相關程式

  • TypeConvert

第三方元件(Dependency)

  • org.bouncycastle:bcprov-jdk15on

主要功能

1. 基本雜湊

字串雜湊

// 對字串進行 SM3 雜湊
String text = "Hello, World!";
String hash = SM3Utils.encrypt(text);
System.out.println("SM3 雜湊值:" + hash);

位元組陣列雜湊

// 對位元組陣列進行 SM3 雜湊
byte[] data = "測試資料".getBytes(StandardCharsets.UTF_8);
byte[] hashBytes = SM3Utils.hash(data);
String hashHex = TypeConvert.bytesToHexString(hashBytes);

2. HMAC 雜湊

使用金鑰的雜湊

// 使用金鑰進行 HMAC-SM3 雜湊
String text = "需要加密的資料";
String key = "mySecretKey";
String hmacHash = SM3Utils.encryptPlus(text, key);

位元組陣列的 HMAC

// 對位元組陣列進行 HMAC-SM3
byte[] data = "測試資料".getBytes(StandardCharsets.UTF_8);
byte[] key = "myKey".getBytes(StandardCharsets.UTF_8);
byte[] hmacResult = SM3Utils.hmac(key, data);

3. 雜湊驗證

基本驗證

// 驗證雜湊值
String original = "原始資料";
String hash = SM3Utils.encrypt(original);
boolean isValid = SM3Utils.verify(original, hash);

HMAC 驗證

// 驗證 HMAC 雜湊值
String data = "原始資料";
String key = "mySecretKey";
String hmacHash = SM3Utils.encryptPlus(data, key);
boolean isValid = SM3Utils.verifyPlus(data, key, hmacHash);

4. 原生 JDK 實作

不使用 BouncyCastle

// 使用 JDK 內建的 SM3 實作(需要 JDK 支援)
String text = "測試文字";
String hash = SM3Utils.sm3Encrypt(text);

使用範例

系統需求

相依套件

  • org.bouncycastle:bcprov-jdk15on (用於 SM3 實作)
  • org.bouncycastle:bcpkix-jdk15on (用於安全性提供者)

運行環境

  • Java 8 或以上
  • BouncyCastle 提供者已正確安裝
  • 若使用 sm3Encrypt() 方法,需要 JDK 11+ 且支援 SM3

安全性注意事項

1. 基本安全性

  • SM3 是中國國家密碼管理局認可的雜湊演算法
  • 雜湊長度為 256 位元(32 bytes)
  • 與 SHA-256 具有相似的安全強度

2. 使用建議

  • 建議在安全要求高的場景使用 HMAC-SM3
  • 避免在日誌中記錄敏感資料的雜湊值
  • 考慮添加隨機鹽值增加安全性

注意事項

1. 一般使用

  • 所有字串操作預設使用 UTF-8 編碼
  • encrypt 方法返回 32 字元的十六進位字串
  • hash 方法返回 32 bytes 的位元組陣列

2. 安全性考量

  • 不要在程式碼中硬編碼金鑰
  • 使用安全的金鑰管理機制
  • 定期更換 HMAC 金鑰

3. 相容性

  • sm3Encrypt() 方法需要 JDK 支援
  • 建議優先使用 BouncyCastle 實作
  • 注意不同 JDK 版本的相容性

4. 效能考量

  • 考慮在高併發場景下的效能影響
  • 適當的快取雜湊結果
  • 監控雜湊運算的效能

進階用法

BouncyCastle 提供者設定

// 註冊 BouncyCastle 提供者
Security.addProvider(new BouncyCastleProvider());

// 檢查提供者是否已註冊
if (Security.getProvider("BC") != null) {
    System.out.println("BouncyCastle 提供者已註冊");
}

自定義 HMAC 實作

// 自定義 HMAC 參數
KeyParameter keyParam = new KeyParameter(key);
HMac hmac = new HMac(new SM3Digest());
hmac.init(keyParam);

// 處理資料
hmac.update(data, 0, data.length);
byte[] result = new byte[hmac.getMacSize()];
hmac.doFinal(result, 0);

效能優化範例

// 重複使用 SM3Digest 實例
SM3Digest digest = new SM3Digest();
for (byte[] data : dataList) {
    digest.reset();
    digest.update(data, 0, data.length);
    byte[] hash = new byte[digest.getDigestSize()];
    digest.doFinal(hash, 0);
    // 處理雜湊結果
}

單元測試範例

以下測試範例示範 SM3Utils 的基本雜湊與 HMAC 驗證:

1. 字串雜湊與驗證

@Test
void testEncryptAndVerify() {
    String src = "hello";
    String hash = SM3Utils.encrypt(src);
    assertNotNull(hash);
    assertTrue(SM3Utils.verify(src, hash));
}

2. HMAC 雜湊與驗證

@Test
void testEncryptPlusAndVerifyPlus() {
    String src = "data";
    String key = "myKey";
    String hmac = SM3Utils.encryptPlus(src, key);
    assertNotNull(hmac);
    assertTrue(SM3Utils.verifyPlus(src, key, hmac));
}

3. 位元組雜湊

@Test
void testHashBytes() {
    byte[] data = "abc".getBytes(StandardCharsets.UTF_8);
    byte[] hash = SM3Utils.hash(data);
    assertNotNull(hash);
    assertEquals(32, hash.length);
}

測試說明

  • 驗證雜湊與 HMAC 的正確性
  • 驗證輸出長度與內容一致性

程式碼 SM3Utils.java

package tw.lewishome.webapp.base.utility.common;

import java.nio.charset.StandardCharsets;
import java.security.Security;


import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
// import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
import java.security.MessageDigest;

/**
 * SM3Utils 提供 SM3 演算法相關的加密、驗證與 HMAC 功能工具類。
 * <p>
 * 支援使用 BouncyCastle 實作 SM3 雜湊演算法,並提供字串與 byte 陣列的加密、驗證,以及 HMAC 加密功能。
 * 亦提供不依賴 BouncyCastle 的 SM3 雜湊方法(需 JDK 支援 sm3)。
 * </p>
 * <ul>
 * <li>encrypt:將字串以 SM3 演算法加密,回傳 32 字元長度的 16 進制字串。</li>
 * <li>hash:將 byte 陣列以 SM3 演算法加密,回傳 32 bytes 的雜湊值。</li>
 * <li>encryptPlus:以指定 key 對字串進行 HMAC-SM3 加密,回傳 32 字元長度的 16 進制字串。</li>
 * <li>hmac:以指定 key 對 byte 陣列進行 HMAC-SM3 加密,回傳雜湊值。</li>
 * <li>verify:驗證原始字串與加密後字串是否一致。</li>
 * <li>verifyPlus:驗證原始字串與指定 key 加密後字串是否一致。</li>
 * <li>sm3Encrype:不依賴 BouncyCastle,直接使用 JDK 實作 SM3 雜湊(需JDK 11 以上支援)。</li>
 * </ul>
 * <p>
 * 注意事項:
 * <ul>
 * <li>本工具類預設使用 UTF-8 編碼。</li>
 * <li>若不希望依賴 BouncyCastle,可使用 sm3Encrype 方法(需 JDK 支援 sm3 演算法)。</li>
 * <li>SM3 為中國國密雜湊演算法,常用於金融、政府等領域。</li>
 * </ul>
 *
 * @author Lewis
 * @version 1.0
 */
public class SM3Utils {
    // Private constructor to prevent instantiation
    private SM3Utils() {
       throw new IllegalStateException("This is a utility class and cannot be instantiated");
    }

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

    /**
     * sm3演算法加密
     *
     * @param paramStr 待加密字串
     * @return String 加密後字串,固定長度=32的16進制字元串
     */
    public static String encrypt(String paramStr) {
        // 將返回的hash值轉換成16進制字元串
        String resultHexString = "";

        // 將字元串轉換成 byte 陣列
        byte[] srcData = paramStr.getBytes(StandardCharsets.UTF_8);
        // 調用hash()
        byte[] resultHash = hash(srcData);
        // 將hash值轉換成16進制字元串
        resultHexString = TypeConvert.bytesToHexString(resultHash);
        return resultHexString;
    }

    /**
     * byte 陣列加密 , 長度=32的 byte 陣列
     *
     * @param srcData 待加密byte 陣列
     * @return 加密後 byte 陣列,長度=32的 byte 陣列
     */
    public static byte[] hash(byte[] srcData) {
        SM3Digest digest = new SM3Digest();
        digest.update(srcData, 0, srcData.length);
        byte[] hash = new byte[digest.getDigestSize()];
        digest.doFinal(hash, 0);
        return hash;
    }

    /**
     * sm3演算法加密
     *
     * @param paramStr 待加密字串
     * @param key      指定加密的Key值
     * @return 加密後字串,固定長度=32的16進制字元串
     */
    public static String encryptPlus(String paramStr, String key) {
        // 將返回的hash值轉換成16進制字元串
        String resultHexString = "";

        // 將字元串轉換成byte數組
        byte[] srcData = paramStr.getBytes(StandardCharsets.UTF_8);
        // 調用hash()
        byte[] resultHash = hmac(srcData, key.getBytes(StandardCharsets.UTF_8));
        // 將返回的hash值轉換成16進制字元串
        
        resultHexString = TypeConvert.bytesToHexString(resultHash);
        return resultHexString;
    }

    /**
     * 通過指定加密的 Key 進行加密
     *
     * @param key     指定加密的Key值
     * @param srcData 待加密的 byte 陣列
     * @return 加密後 byte 陣列
     */
    public static byte[] hmac(byte[] key, byte[] srcData) {
        KeyParameter keyParameter = new KeyParameter(key);
        SM3Digest digest = new SM3Digest();
        HMac mac = new HMac(digest);
        mac.init(keyParameter);
        mac.update(srcData, 0, srcData.length);
        byte[] result = new byte[mac.getMacSize()];
        mac.doFinal(result, 0);
        return result;
    }

    /**
     * 判斷輸入Sring 與加密資料是否相同
     *
     * @param srcStr       待加密字串
     * @param sm3HexString 加密後字串
     * @return 驗證結果
     */
    public static boolean verify(String srcStr, String sm3HexString) {
        boolean flag = false;
        String encryptPlusString = encrypt(srcStr);
        if (encryptPlusString.equals(sm3HexString)) {
            flag = true;
        }
        return flag;
    }

    /**
     * 判斷輸入Sring 與 指定的 Key 加密的資料是否相同
     *
     * @param srcStr       待加密字串
     * @param key          指定加密的Key值
     * @param sm3HexString 加密後字串
     * @return 驗證結果
     */
    public static boolean verifyPlus(String srcStr, String key, String sm3HexString) {
        boolean flag = false;
        String encryptPlusString = encryptPlus(srcStr, key);
        if (encryptPlusString.equals(sm3HexString)) {
            flag = true;
        }
        return flag;
    }

    // 
    /**
     * sm3Encrypt.
     * 以備 不使用 bouncycastle Dependency (有弱點)
     * @param srcString a {@link java.lang.String} object
     * @return a {@link java.lang.String} object
     */
    public static String sm3Encrypt(String srcString) {
        try {
            MessageDigest digest = MessageDigest.getInstance("sm3");
            byte[] hash = digest.digest(srcString.getBytes(StandardCharsets.UTF_8));
            StringBuilder hexString = new StringBuilder();
            for (byte oneByte : hash) {
                String hex = Integer.toHexString(0xff & oneByte);
                if (hex.length() == 1) {
                    hexString.append('0');
                }
                hexString.append(hex);
            }
            return hexString.toString();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }
}

單元測試程式碼 SM3UtilsTest.java

package tw.lewishome.webapp.base.utility.common;

import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.nio.file.Path;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

/**
 * SM3Utils 的單元測試類別
 * 
 * 測試範圍包含:
 * 1. 基本 SM3 加密
 * 2. 使用 Key 的 HMAC-SM3 加密
 * 3. 驗證功能
 * 4. 不使用 BouncyCastle 的原生 SM3 實作
 */
class SM3UtilsTest {

    @TempDir
    Path tempDir;

    private static final String TEST_STRING = "test123";
    private static final String TEST_KEY = "testkey456";

    @Test
    void testBasicEncryption() {
        // 測試基本加密功能
        String result = SM3Utils.encrypt(TEST_STRING);
        assertNotNull(result);
        assertEquals(64, result.length()); // SM3 應產生 32 bytes (64 個 hex 字元)
        
        // 確認相同輸入產生相同輸出
        String result2 = SM3Utils.encrypt(TEST_STRING);
        assertEquals(result, result2);
    }

    @Test
    void testHashFunction() {
        // 測試 byte[] 加密功能
        byte[] input = TEST_STRING.getBytes(StandardCharsets.UTF_8);
        byte[] hash = SM3Utils.hash(input);
        
        assertNotNull(hash);
        assertEquals(32, hash.length); // SM3 hash 應為 32 bytes
        
        // 確認相同輸入產生相同輸出
        byte[] hash2 = SM3Utils.hash(input);
        assertArrayEquals(hash, hash2);
    }

    @Test
    void testHmacEncryption() {
        // 測試帶 Key 的加密功能
        String result = SM3Utils.encryptPlus(TEST_STRING, TEST_KEY);
        assertNotNull(result);
        assertEquals(64, result.length());
        
        // 確認相同輸入和 Key 產生相同輸出
        String result2 = SM3Utils.encryptPlus(TEST_STRING, TEST_KEY);
        assertEquals(result, result2);
        
        // 確認不同 Key 產生不同輸出
        String result3 = SM3Utils.encryptPlus(TEST_STRING, "differentkey");
        assertNotEquals(result, result3);
    }

    @Test
    void testHmacFunction() {
        // 測試 HMAC byte[] 功能
        byte[] input = TEST_STRING.getBytes(StandardCharsets.UTF_8);
        byte[] key = TEST_KEY.getBytes(StandardCharsets.UTF_8);
        
        byte[] hmac = SM3Utils.hmac(key, input);
        assertNotNull(hmac);
        assertEquals(32, hmac.length);
        
        // 確認相同輸入產生相同輸出
        byte[] hmac2 = SM3Utils.hmac(key, input);
        assertArrayEquals(hmac, hmac2);
    }

    @Test
    void testVerification() {
        // 測試驗證功能
        String encrypted = SM3Utils.encrypt(TEST_STRING);
        assertTrue(SM3Utils.verify(TEST_STRING, encrypted));
        
        // 測試錯誤的輸入
        assertFalse(SM3Utils.verify("wronginput", encrypted));
    }

    @Test
    void testHmacVerification() {
        // 測試帶 Key 的驗證功能
        String encrypted = SM3Utils.encryptPlus(TEST_STRING, TEST_KEY);
        assertTrue(SM3Utils.verifyPlus(TEST_STRING, TEST_KEY, encrypted));
        
        // 測試錯誤的輸入
        assertFalse(SM3Utils.verifyPlus(TEST_STRING, "wrongkey", encrypted));
        assertFalse(SM3Utils.verifyPlus("wronginput", TEST_KEY, encrypted));
    }

    @Test
    void testNativeImplementation() {
        // 測試原生 JDK SM3 實作
        String result = SM3Utils.sm3Encrypt(TEST_STRING);
        assertNotNull(result);
        assertEquals(64, result.length());
        
        // 確認相同輸入產生相同輸出
        String result2 = SM3Utils.sm3Encrypt(TEST_STRING);
        assertEquals(result, result2);
    }

    @Test
    void testEmptyInput() {
        // 測試空字串輸入
        String emptyResult = SM3Utils.encrypt("");
        assertNotNull(emptyResult);
        assertEquals(64, emptyResult.length());
        
        // 測試 null 輸入
        assertThrows(NullPointerException.class, () -> SM3Utils.encrypt(null));
    }

    @Test
    void testWithFileOperations() throws IOException {
        // 準備測試檔案
        File testFile = tempDir.resolve("test.txt").toFile();
        FileUtils.stringArrayListToFile(Arrays.asList(TEST_STRING), testFile.getAbsolutePath());
        
        // 讀取檔案內容並加密
        String fileContent = String.join("", FileUtils.fileToStringArrayList(testFile.getAbsolutePath()));
        String encryptedContent = SM3Utils.encrypt(fileContent);
        
        // 驗證加密結果
        assertTrue(SM3Utils.verify(TEST_STRING, encryptedContent));
        
        // 清理測試檔案
        FileUtils.deleteFile(testFile.getAbsolutePath());
    }
}


圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言