文章目錄
- 國密算法(SM2/SM3/SM4)詳解:從性能對比到Java手機號安全處理實戰
- 一、 國密核心算法簡介
- 二、 性能深度對比
- 三、 Java實戰:手機號的安全處理
- 方案一:使用SM3哈希存儲(推薦用于驗證場景)
- 方案二:使用SM4加密存儲(用于需要原文的場景)
- Java代碼示例
- 示例1:使用SM3哈希手機號(含加鹽)
- 示例2:使用SM4加密/解密手機號
- 結論
國密算法(SM2/SM3/SM4)詳解:從性能對比到Java手機號安全處理實戰
隨著信息安全上升為國家戰略,國密(國家商用密碼)算法在金融、政務、物聯網等關鍵領域得到了廣泛應用。本文將帶您深入了解國密的核心算法(SM2/SM3/SM4),對比它們的性能差異,并提供一份在Java中安全處理手機號的完整代碼示例。
一、 國密核心算法簡介
國密算法是一套由國家密碼管理局發布的商用密碼標準,主要包含以下三個核心算法:
-
SM2:非對稱加密算法
- 類型:非對稱加密與簽名算法。
- 對標:國際上的 ECC(橢圓曲線密碼算法),功能類似 RSA。
- 用途:主要用于數字簽名、身份認證和密鑰協商。由于其計算復雜度高,不適合加密大量數據,通常用于加密對稱密鑰(如SM4的密鑰)或對數據摘要進行簽名。
-
SM3:哈希算法
- 類型:密碼雜湊算法(哈希函數)。
- 對標:國際上的 SHA-256。
- 用途:用于生成數據的唯一“數字指紋”,以保證數據完整性和身份驗證。它是一個單向函數,意味著無法從哈希值反推出原文。這是存儲密碼、手機號等敏感信息的理想選擇。
-
SM4:對稱加密算法
- 類型:對稱加密算法(分組密碼)。
- 對標:國際上的 AES。
- 用途:用于大量數據(如文件、通信報文)的加密和解密。收發雙方使用相同的密鑰進行加解密,速度快,效率高。
二、 性能深度對比
這三個算法的設計目標不同,因此性能差異巨大。簡單來說,它們的計算速度排行如下:
SM3 (最快) > SM4 (很快) > SM2 (相對最慢)
算法 | 類型 | 計算復雜度 | 相對速度 | 形象比喻 |
---|---|---|---|---|
SM3 | 哈希算法 | 最低(位運算、邏輯運算) | 最快 | 指紋識別器:瞬間完成,給出唯一結果。 |
SM4 | 對稱加密 | 中等(多輪迭代、查表) | 很快 | 保險箱:用同一把鑰匙開關,操作迅速。 |
SM2 | 非對稱加密 | 最高(橢圓曲線點乘運算) | 最慢 | 銀行保險柜:開戶和存取手續嚴謹復雜,但安全性最高。 |
- 為什么SM3最快? 它的計算是純粹的位運算,CPU執行效率極高,專為速度而生。
- 為什么SM4居中? 它需要對數據分組并進行多輪復雜的迭代變換,比SM3慢,但遠快于SM2,適合處理大數據。
- 為什么SM2最慢? 它基于復雜的橢圓曲線數學難題,計算量巨大,但提供了非對稱加密獨有的安全性(如密鑰交換和數字簽名)。
三、 Java實戰:手機號的安全處理
對于“加密”手機號,我們有兩種主流且安全的方案,適用于不同業務場景。
方案一:使用SM3哈希存儲(推薦用于驗證場景)
當你的業務只需要驗證用戶輸入的手機號是否正確,而不需要知道手機號原文時(例如用戶注冊、登錄),這是最安全、最推薦的做法。
- 優點:不可逆。即使數據庫泄露,攻擊者也無法獲得原始手機號。
- 實踐:結合**加鹽(Salt)**可以抵御彩虹表攻擊,安全性更高。
方案二:使用SM4加密存儲(用于需要原文的場景)
當你的業務必須使用手機號原文時(例如發送短信驗證碼、進行外呼),你需要使用可逆的對稱加密。
- 優點:可以解密還原出原始手機號。
- 挑戰:密鑰的保護至關重要。密鑰一旦泄露,所有數據都將暴露。
Java代碼示例
首先,請確保你的項目中已添加 Bouncy Castle 依賴。
Maven pom.xml
:
<dependency><groupId>org.bouncycastle</groupId><artifactId>bcprov-jdk18on</artifactId><version>1.78.1</version>
</dependency>
示例1:使用SM3哈希手機號(含加鹽)
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Hex;import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.Security;/*** 國密 SM3 哈希工具類(推薦用于密碼、手機號等敏感信息的存儲)*/
public class Sm3Demo {static {// 注冊 Bouncy Castle 提供者if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {Security.addProvider(new BouncyCastleProvider());}}/*** 生成一個安全的隨機鹽* @param aLength 鹽的字節長度* @return 十六進制表示的鹽字符串*/public static String generateSalt(int length) {SecureRandom random = new SecureRandom();byte[] salt = new byte[length];random.nextBytes(salt);return Hex.toHexString(salt);}/*** 對輸入字符串進行加鹽 SM3 哈希** @param input 待哈希的原文* @param salt 鹽值(十六進制字符串)* @return 64位的十六進制哈希值*/public static String hashWithSalt(String input, String salt) {if (input == null || salt == null) {return null;}// 鹽值在前或在后,或混合都可以,但要保持一致String dataToHash = salt + input;try {MessageDigest digest = MessageDigest.getInstance("SM3", BouncyCastleProvider.PROVIDER_NAME);byte[] hashBytes = digest.digest(dataToHash.getBytes(StandardCharsets.UTF_8));return Hex.toHexString(hashBytes);} catch (NoSuchAlgorithmException e) {throw new RuntimeException("SM3 algorithm not found", e);}}public static void main(String[] args) {String phoneNumber = "15888888888";// 1. 為該手機號生成一個唯一的鹽// 在實際應用中,這個鹽需要與哈希值一起存儲在數據庫的用戶記錄中String salt = generateSalt(16); // 生成16字節(32個十六進制字符)的鹽System.out.println("原始手機號: " + phoneNumber);System.out.println("生成的鹽 (Salt): " + salt);// 2. 計算加鹽后的哈希值String hashedPhoneNumber = hashWithSalt(phoneNumber, salt);System.out.println("SM3 加鹽哈希值: " + hashedPhoneNumber);System.out.println("哈希值長度: " + hashedPhoneNumber.length());// 3. 驗證過程 (模擬用戶登錄)System.out.println("\n--- 用戶驗證 ---");String userInputPhone = "15888888888";// 從數據庫中取出該用戶存儲的 salt 和 hashedPhoneNumberString storedSalt = salt;String storedHash = hashedPhoneNumber;// 使用相同的鹽對用戶輸入進行哈希String verificationHash = hashWithSalt(userInputPhone, storedSalt);System.out.println("驗證哈希: " + verificationHash);System.out.println("驗證是否通過: " + storedHash.equals(verificationHash));// 錯誤輸入String wrongInputPhone = "15800000000";String wrongVerificationHash = hashWithSalt(wrongInputPhone, storedSalt);System.out.println("錯誤輸入驗證是否通過: " + storedHash.equals(wrongVerificationHash));}
}
示例2:使用SM4加密/解密手機號
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Base64;import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.security.Security;/*** 國密 SM4 對稱加密工具類(用于需要還原原文的場景)*/
public class Sm4Demo {static {if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {Security.addProvider(new BouncyCastleProvider());}}private static final String ALGORITHM_NAME = "SM4";// SM4/CBC/PKCS5Padding 是一種常用的加密模式// CBC 模式需要一個初始化向量 (IV)private static final String PADDING_MODE = "SM4/CBC/PKCS5Padding";/*** 生成一個 SM4 密鑰 (128位/16字節)* @return Base64 編碼的密鑰字符串*/public static String generateKey() throws Exception {KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM_NAME, BouncyCastleProvider.PROVIDER_NAME);kg.init(128, new SecureRandom());return Base64.toBase64String(kg.generateKey().getEncoded());}/*** 生成一個初始化向量 (IV) (128位/16字節)* @return Base64 編碼的 IV 字符串*/public static String generateIv() {byte[] iv = new byte[16];new SecureRandom().nextBytes(iv);return Base64.toBase64String(iv);}/*** SM4 加密* @param plainText 明文* @param key Base64 編碼的密鑰* @param iv Base64 編碼的 IV* @return Base64 編碼的密文*/public static String encrypt(String plainText, String key, String iv) throws Exception {SecretKeySpec keySpec = new SecretKeySpec(Base64.decode(key), ALGORITHM_NAME);IvParameterSpec ivSpec = new IvParameterSpec(Base64.decode(iv));Cipher cipher = Cipher.getInstance(PADDING_MODE, BouncyCastleProvider.PROVIDER_NAME);cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);byte[] encryptedBytes = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));return Base64.toBase64String(encryptedBytes);}/*** SM4 解密* @param cipherText Base64 編碼的密文* @param key Base64 編碼的密鑰* @param iv Base64 編碼的 IV* @return 明文*/public static String decrypt(String cipherText, String key, String iv) throws Exception {SecretKeySpec keySpec = new SecretKeySpec(Base64.decode(key), ALGORITHM_NAME);IvParameterSpec ivSpec = new IvParameterSpec(Base64.decode(iv));Cipher cipher = Cipher.getInstance(PADDING_MODE, BouncyCastleProvider.PROVIDER_NAME);cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);byte[] decryptedBytes = cipher.doFinal(Base64.decode(cipherText));return new String(decryptedBytes, StandardCharsets.UTF_8);}public static void main(String[] args) throws Exception {String phoneNumber = "15888888888";// 在真實應用中,密鑰和 IV 必須被安全地存儲和管理,例如使用硬件加密機(HSM)或配置中心String sm4Key = generateKey();String sm4Iv = generateIv();System.out.println("原始手機號: " + phoneNumber);System.out.println("SM4 密鑰 (Base64): " + sm4Key);System.out.println("SM4 IV (Base64): " + sm4Iv);// 加密String encryptedPhone = encrypt(phoneNumber, sm4Key, sm4Iv);System.out.println("SM4 加密后 (Base64): " + encryptedPhone);// 解密String decryptedPhone = decrypt(encryptedPhone, sm4Key, sm4Iv);System.out.println("SM4 解密后: " + decryptedPhone);System.out.println("解密是否成功: " + phoneNumber.equals(decryptedPhone));}
}
結論
- 選擇合適的工具:沒有“最好”的算法,只有“最合適”的場景。請根據你的業務需求選擇正確的國密算法。
- SM3哈希優先:當處理用戶密碼、手機號等用于驗證的敏感信息時,優先采用SM3加鹽哈希,這是最安全的存儲方式。
- 謹慎使用SM4:只有在業務流程中確實需要恢復原文時,才使用SM4加密。同時,必須投入資源確保密鑰的安全,因為密鑰就是一切。
- 協同工作:在復雜的系統中,SM2、SM3、SM4往往協同工作,共同構建起一個完整的、高強度的安全體系。