SM3Utils 是一個提供 SM3 演算法相關功能的工具類別。此類別整合了 BouncyCastle 的實作,提供完整的 SM3 雜湊、HMAC 與驗證功能,特別適合需要使用中國國密演算法的應用場景。
// 對字串進行 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);
// 使用金鑰進行 HMAC-SM3 雜湊
String text = "需要加密的資料";
String key = "mySecretKey";
String hmacHash = SM3Utils.encryptPlus(text, key);
// 對位元組陣列進行 HMAC-SM3
byte[] data = "測試資料".getBytes(StandardCharsets.UTF_8);
byte[] key = "myKey".getBytes(StandardCharsets.UTF_8);
byte[] hmacResult = SM3Utils.hmac(key, data);
// 驗證雜湊值
String original = "原始資料";
String hash = SM3Utils.encrypt(original);
boolean isValid = SM3Utils.verify(original, hash);
// 驗證 HMAC 雜湊值
String data = "原始資料";
String key = "mySecretKey";
String hmacHash = SM3Utils.encryptPlus(data, key);
boolean isValid = SM3Utils.verifyPlus(data, key, hmacHash);
// 使用 JDK 內建的 SM3 實作(需要 JDK 支援)
String text = "測試文字";
String hash = SM3Utils.sm3Encrypt(text);
// 註冊 BouncyCastle 提供者
Security.addProvider(new BouncyCastleProvider());
// 檢查提供者是否已註冊
if (Security.getProvider("BC") != null) {
System.out.println("BouncyCastle 提供者已註冊");
}
// 自定義 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 驗證:
@Test
void testEncryptAndVerify() {
String src = "hello";
String hash = SM3Utils.encrypt(src);
assertNotNull(hash);
assertTrue(SM3Utils.verify(src, hash));
}
@Test
void testEncryptPlusAndVerifyPlus() {
String src = "data";
String key = "myKey";
String hmac = SM3Utils.encryptPlus(src, key);
assertNotNull(hmac);
assertTrue(SM3Utils.verifyPlus(src, key, hmac));
}
@Test
void testHashBytes() {
byte[] data = "abc".getBytes(StandardCharsets.UTF_8);
byte[] hash = SM3Utils.hash(data);
assertNotNull(hash);
assertEquals(32, hash.length);
}
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;
}
}
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());
}
}