在这里插入图片描述

前言:为什么Java开发者需要了解国密?

三个事实:

  1. 密评合规要求:等保三级及以上系统,密码应用必须通过商用密码应用安全性评估(密评),其中明确要求使用国密算法
  2. 信创替代加速:金融、政务、能源、交通等关键行业正在大规模推进国密改造
  3. JDK默认不支持:Java标准库 java.security 不内置国密算法,需要引入 BouncyCastle 或国产密码库

简单说:你可能不一定现在要用,但你一定会遇到。

本文用最小化代码示例,讲清楚SM2(非对称加密)、SM3(哈希)、SM4(对称加密)这三种最常用的国密算法怎么在Java里调用。


一、三种算法一句话定位

算法 类型 对标国际算法 一句话定位
SM2 非对称加密 RSA/ECC 公私钥加解密、数字签名
SM3 哈希摘要 SHA-256 数据完整性校验、密码哈希
SM4 对称加密 AES 大量数据加解密(快,密钥要保密)
         SM2(非对称)                SM4(对称)
         ┌─────────┐                ┌─────────┐
         │ 公钥加密 │                │ 同一密钥 │
         │ 私钥解密 │                │ 加解密    │
         └─────────┘                └─────────┘
              │                          │
              ├── 慢,适合加密密钥 ──→    ←── 快,适合加密数据
              │                          │
              └────── 配合使用 ──────────┘
                    (数字信封模式)

         SM3(哈希)
         ┌─────────┐
         │ 任意长度  │
         │ → 256位  │  用于:密码存储、文件校验、签名摘要
         │   不可逆  │
         └─────────┘

二、环境准备

Maven依赖(二选一):

<!-- 方式1:BouncyCastle(推荐,兼容性最好)-->
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk18on</artifactId>
    <version>1.78</version>
</dependency>

<!-- 方式2:hutool-crypto(封装更简单,适合快速开发)-->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-crypto</artifactId>
    <version>5.8.28</version>
</dependency>

本文使用 Hutool 做演示(代码量最少),文末附BouncyCastle原生写法。


三、SM4 对称加密:最常用、最容易上手

SM4是分组对称加密算法,密钥长度128位(16字节),分组长度128位。

3.1 最简单的加解密

import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.symmetric.SymmetricCrypto;

public class Sm4Demo {
    public static void main(String[] args) {
        // 1. 生成SM4密钥(或使用已有的16字节密钥)
        byte[] key = SmUtil.sm4().getSecretKey().getEncoded();
        
        // 2. 创建SM4加密器(默认ECB模式,PKCS5Padding填充)
        SymmetricCrypto sm4 = SmUtil.sm4(key);
        
        // 3. 加密
        String plainText = "这是待加密的业务数据";
        String cipherText = sm4.encryptHex(plainText);
        System.out.println("密文: " + cipherText);
        // 输出: 密文: a3f8b2c1d4e5f6a7b8c9d0e1f2a3b4...
        
        // 4. 解密
        String decrypted = sm4.decryptStr(cipherText);
        System.out.println("解密: " + decrypted);
        // 输出: 解密: 这是待加密的业务数据
    }
}

3.2 指定CBC模式(更安全,推荐生产使用)

import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.symmetric.SM4;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;

public class Sm4CbcDemo {
    public static void main(String[] args) {
        // 准备密钥和IV向量(各16字节)
        byte[] key = "0123456789abcdef".getBytes();      // 16字节密钥
        byte[] iv  = "fedcba9876543210".getBytes();      // 16字节IV
        
        // 创建SM4-CBC-PKCS5Padding实例
        SM4 sm4 = new SM4(Mode.CBC, Padding.PKCS5Padding, key, iv);
        
        String plainText = "CBC模式加密的业务数据";
        String cipherText = sm4.encryptBase64(plainText);
        System.out.println("CBC密文(Base64): " + cipherText);
        
        String decrypted = sm4.decryptStr(cipherText);
        System.out.println("解密结果: " + decrypted);
    }
}

3.3 实战:加密数据库敏感字段

@Service
public class UserPrivacyService {
    
    // 密钥从密钥管理平台获取,不要硬编码!
    @Value("${sm4.key}")
    private String sm4Key;
    
    private final SM4 sm4;
    
    @PostConstruct
    public void init() {
        byte[] keyBytes = HexUtil.decodeHex(sm4Key);
        this.sm4 = new SM4(Mode.CBC, Padding.PKCS5Padding, 
            keyBytes, "random_iv_16byte".getBytes());
    }
    
    // 手机号加密存入数据库
    public String encryptPhone(String phone) {
        return sm4.encryptBase64(phone);
    }
    
    // 手机号解密
    public String decryptPhone(String encryptedPhone) {
        return sm4.decryptStr(encryptedPhone);
    }
    
    // 使用示例
    public void saveUser(User user) {
        user.setPhone(encryptPhone(user.getPhone()));  // 加密手机号
        user.setIdCard(encryptPhone(user.getIdCard())); // 加密身份证
        // 加密后存入数据库
        userRepository.save(user);
    }
}

四、SM3 哈希算法:密码存储和完整性校验

SM3输出256位(32字节)哈希值,不可逆。

4.1 基本使用

import cn.hutool.crypto.SmUtil;

public class Sm3Demo {
    public static void main(String[] args) {
        // 计算SM3哈希
        String data = "hello 国密算法";
        String hash = SmUtil.sm3(data);
        System.out.println("SM3哈希: " + hash);
        // 输出: 64位十六进制字符串
        
        // 对文件计算SM3(文件完整性校验)
        String fileHash = SmUtil.sm3(new File("/path/to/file.pdf"));
        System.out.println("文件SM3: " + fileHash);
    }
}

4.2 实战:用户密码安全存储

@Service
public class PasswordService {
    
    // 密码加盐后SM3哈希存储
    public String hashPassword(String rawPassword, String salt) {
        // SM3(password + salt)
        String combined = rawPassword + salt;
        return SmUtil.sm3(combined);
    }
    
    // 用户注册
    public void register(String username, String rawPassword) {
        String salt = RandomUtil.randomString(16);  // 生成16位随机盐
        String hashedPassword = hashPassword(rawPassword, salt);
        
        User user = new User();
        user.setUsername(username);
        user.setPassword(hashedPassword);
        user.setSalt(salt);                          // 盐值也存起来
        userRepository.save(user);
    }
    
    // 用户登录验证
    public boolean verifyPassword(String username, String rawPassword) {
        User user = userRepository.findByUsername(username);
        String hashedInput = hashPassword(rawPassword, user.getSalt());
        return hashedInput.equals(user.getPassword());
    }
}

4.3 SM3-HMAC:带密钥的哈希(防篡改)

public class Sm3HmacDemo {
    public static void main(String[] args) {
        String data = "转账金额:5000元";
        String secretKey = "shared-secret-key";
        
        // 计算HMAC-SM3
        String hmac = SmUtil.hmacSm3(secretKey.getBytes()).digestHex(data);
        System.out.println("HMAC-SM3: " + hmac);
        
        // 应用场景:API签名验证
        // 客户端发送:data + hmac(data, secretKey)
        // 服务端用相同secretKey计算hmac,比对是否一致
        // 不一致 → 数据被篡改
    }
}

五、SM2 非对称加密:公私钥加解密和数字签名

SM2基于椭圆曲线密码(ECC),256位密钥,安全性相当于RSA-3072,但密钥更短、运算更快。

5.1 生成SM2密钥对

import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;

public class Sm2Demo {
    public static void main(String[] args) {
        // 生成密钥对
        SM2 sm2 = SmUtil.sm2();
        
        // 获取公私钥(Base64格式)
        String privateKey = sm2.getPrivateKeyBase64();
        String publicKey  = sm2.getPublicKeyBase64();
        
        System.out.println("私钥: " + privateKey);
        System.out.println("公钥: " + publicKey);
        
        // 使用自定义密钥对
        SM2 customSm2 = SmUtil.sm2(privateKey, publicKey);
    }
}

5.2 公钥加密、私钥解密

public class Sm2EncryptDemo {
    public static void main(String[] args) {
        SM2 sm2 = SmUtil.sm2();
        
        // 公钥加密
        String plainText = "用公钥加密的敏感数据";
        String cipherText = sm2.encryptBase64(plainText, KeyType.PublicKey);
        System.out.println("SM2密文: " + cipherText);
        
        // 私钥解密
        String decrypted = sm2.decryptStr(cipherText, KeyType.PrivateKey);
        System.out.println("解密: " + decrypted);
    }
}

5.3 数字签名:私钥签名、公钥验签

public class Sm2SignDemo {
    public static void main(String[] args) {
        // 模拟场景:A用私钥对合同签名,B用A的公钥验证签名
        
        SM2 sm2 = SmUtil.sm2();
        String contract = "合同内容:甲方向乙方支付100万元";
        
        // A:用私钥签名(sign)
        byte[] sign = sm2.sign(contract.getBytes());
        String signBase64 = Base64.encode(sign);
        System.out.println("签名: " + signBase64);
        
        // B:用A的公钥验证签名(verify)
        boolean verified = sm2.verify(contract.getBytes(), sign);
        System.out.println("验签结果: " + verified);  // true
        
        // 如果合同被篡改
        String tamperedContract = "合同内容:甲方向乙方支付1000万元";
        boolean tampered = sm2.verify(tamperedContract.getBytes(), sign);
        System.out.println("篡改后验签: " + tampered);  // false
    }
}

5.4 实战:SM2+SM4数字信封(最佳实践)

业界标准的"数字信封"模式:用SM4加密数据(快),用SM2加密SM4密钥(安全)。

public class DigitalEnvelopeDemo {
    
    /**
     * 发送方:SM4加密数据 + SM2加密SM4密钥
     */
    public Envelope encrypt(String plainText, String receiverPublicKey) {
        // 1. 生成随机SM4密钥(一次性)
        byte[] sm4Key = RandomUtil.randomBytes(16);
        
        // 2. 用SM4加密数据
        SM4 sm4 = new SM4(Mode.CBC, Padding.PKCS5Padding, sm4Key, sm4Key);
        String encryptedData = sm4.encryptBase64(plainText);
        
        // 3. 用接收方SM2公钥加密SM4密钥
        SM2 sm2 = SmUtil.sm2(null, receiverPublicKey);
        String encryptedKey = sm2.encryptBase64(sm4Key, KeyType.PublicKey);
        
        // 4. 组装信封
        return new Envelope(encryptedData, encryptedKey);
    }
    
    /**
     * 接收方:SM2解密SM4密钥 → SM4解密数据
     */
    public String decrypt(Envelope envelope, String receiverPrivateKey) {
        // 1. 用SM2私钥解密出SM4密钥
        SM2 sm2 = SmUtil.sm2(receiverPrivateKey, null);
        byte[] sm4Key = sm2.decrypt(envelope.getEncryptedKey(), KeyType.PrivateKey);
        
        // 2. 用SM4密钥解密数据
        SM4 sm4 = new SM4(Mode.CBC, Padding.PKCS5Padding, sm4Key, sm4Key);
        return sm4.decryptStr(envelope.getEncryptedData());
    }
    
    // 信封数据结构
    @Data
    @AllArgsConstructor
    public static class Envelope {
        private String encryptedData;    // SM4加密的数据
        private String encryptedKey;     // SM2加密的SM4密钥
    }
}

六、BouncyCastle原生写法(进阶参考)

Hutool底层用的就是BouncyCastle。如果你不想引入Hutool,可以直接用BC:

import org.bouncycastle.jce.provider.BouncyCastleProvider;

public class BcSm4RawDemo {
    static {
        Security.addProvider(new BouncyCastleProvider());
    }
    
    public static byte[] sm4Encrypt(byte[] key, byte[] data) throws Exception {
        Cipher cipher = Cipher.getInstance("SM4/ECB/PKCS7Padding", "BC");
        SecretKeySpec keySpec = new SecretKeySpec(key, "SM4");
        cipher.init(Cipher.ENCRYPT_MODE, keySpec);
        return cipher.doFinal(data);
    }
    
    public static byte[] sm4Decrypt(byte[] key, byte[] cipherData) throws Exception {
        Cipher cipher = Cipher.getInstance("SM4/ECB/PKCS7Padding", "BC");
        SecretKeySpec keySpec = new SecretKeySpec(key, "SM4");
        cipher.init(Cipher.DECRYPT_MODE, keySpec);
        return cipher.doFinal(cipherData);
    }
}

七、国密算法选型速查表

场景 算法 模式
数据库字段加密(大批量) SM4 CBC + PKCS5Padding
配置文件/小数据加密 SM2 公钥加密、私钥解密
用户密码存储 SM3 加盐后哈希
文件完整性校验 SM3 直接哈希
API签名防篡改 SM3-HMAC 带密钥哈希
电子合同/数字签名 SM2 私钥签名、公钥验签
大文件传输 SM4+SM2 数字信封模式

总结

国密三件套的使用逻辑很清晰:

  • SM4 处理数据:日常加密的主力,快且简单
  • SM2 保护密钥:在不可信环境中安全地传递SM4密钥
  • SM3 验证完整性:哈希、签名摘要、密码存储

入门只需要记住三个Hutool方法调用:SmUtil.sm4()SmUtil.sm3()SmUtil.sm2()

如果你现在的项目还没用国密,建议从SM4加密数据库敏感字段开始——成本最低、见效最快、密评检查时也能交差。


💬 话题讨论:你们项目用了国密算法吗?用SM4做数据库加密还是SM2做数字签名?欢迎评论区交流,聊聊你的实践经验。

更多推荐