Hutool crypto 教程

一、国密算法简介

国密算法是中国密码管理局发布的密码算法标准,主要包括:

算法 类型 用途 对应国际算法
SM2 非对称加密 签名、密钥交换、加密 RSA、ECDSA
SM3 哈希算法 摘要计算、验证 SHA-256
SM4 对称加密 数据加密 AES

二、环境准备

Maven 依赖

XML 复制代码
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.25</version>
</dependency>

<!-- Hutool 的 SM2/SM4 需要 Bouncy Castle 支持 -->
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15to18</artifactId>
    <version>1.78</version>
</dependency>

注册 Bouncy Castle 提供者

java 复制代码
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.security.Security;

// 在程序启动时注册
static {
    Security.addProvider(new BouncyCastleProvider());
}

三、SM3 哈希算法

基本使用

java 复制代码
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.digest.SM3;

public class SM3Demo {
    public static void main(String[] args) {
        String data = "Hello, 国密算法!";
        
        // 方式1:直接计算 SM3 哈希
        String hash = SmUtil.sm3(data);
        System.out.println("SM3哈希: " + hash);
        // 输出: 591e5b1b6e50f6d9b2f7d8c5a2b1e3f5a8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3
        
        // 方式2:使用 SM3 对象(支持多次更新)
        SM3 sm3 = SmUtil.sm3();
        sm3.update(data.getBytes());
        sm3.update("追加数据".getBytes());
        byte[] digest = sm3.digest();
        System.out.println("分段计算: " + cn.hutool.core.codec.HexUtil.encodeHexStr(digest));
        
        // 方式3:验证数据完整性
        String originalHash = SmUtil.sm3(originalData);
        boolean isValid = originalHash.equals(computedHash);
    }
}

带盐值的 SM3

java 复制代码
public class SM3WithSalt {
    public static void main(String[] args) {
        String password = "myPassword123";
        String salt = "randomSaltValue";
        
        // 密码 + 盐值 一起哈希
        String hashedPassword = SmUtil.sm3(password + salt);
        System.out.println("加盐哈希: " + hashedPassword);
    }
}

四、SM4 对称加密

生成密钥与基本加解密

java 复制代码
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.symmetric.SM4;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
import cn.hutool.core.codec.Base64;

public class SM4Demo {
    public static void main(String[] args) {
        String plainText = "这是一段需要加密的敏感数据";
        
        // 方式1:自动生成随机密钥
        SM4 sm4 = SmUtil.sm4();
        byte[] key = sm4.getSecretKey().getEncoded();
        System.out.println("生成的密钥(Hex): " + cn.hutool.core.codec.HexUtil.encodeHexStr(key));
        
        // 加密
        String encryptStr = sm4.encryptBase64(plainText);
        System.out.println("加密结果(Base64): " + encryptStr);
        
        // 解密
        String decryptStr = sm4.decryptStr(encryptStr);
        System.out.println("解密结果: " + decryptStr);
        
        // 方式2:使用指定密钥(16字节 = 128位)
        byte[] customKey = "1234567890123456".getBytes(); // 必须是16字节
        SM4 sm4WithKey = new SM4(customKey);
        String encrypted = sm4WithKey.encryptBase64(plainText);
        String decrypted = sm4WithKey.decryptStr(encrypted);
        System.out.println("自定义密钥解密: " + decrypted);
    }
}

多种加解密模式

java 复制代码
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.symmetric.SymmetricCrypto;

public class SM4ModesDemo {
    public static void main(String[] args) {
        byte[] key = "1234567890123456".getBytes();
        byte[] iv = "1234567890123456".getBytes(); // 初始化向量,需要16字节
        
        String data = "Hello SM4 with CBC mode!";
        
        // ECB 模式(不需要 IV)
        SymmetricCrypto sm4Ecb = new SymmetricCrypto("SM4/ECB/PKCS5Padding", key);
        String encryptedEcb = sm4Ecb.encryptBase64(data);
        String decryptedEcb = sm4Ecb.decryptStr(encryptedEcb);
        
        // CBC 模式(需要 IV)
        SymmetricCrypto sm4Cbc = new SymmetricCrypto("SM4/CBC/PKCS5Padding", key, iv);
        String encryptedCbc = sm4Cbc.encryptBase64(data);
        String decryptedCbc = sm4Cbc.decryptStr(encryptedCbc);
        
        // GCM 模式(认证加密,推荐)
        SymmetricCrypto sm4Gcm = new SymmetricCrypto("SM4/GCM/NoPadding", key);
        String encryptedGcm = sm4Gcm.encryptBase64(data);
        String decryptedGcm = sm4Gcm.decryptStr(encryptedGcm);
        
        System.out.println("ECB解密: " + decryptedEcb);
        System.out.println("CBC解密: " + decryptedCbc);
        System.out.println("GCM解密: " + decryptedGcm);
    }
}

五、SM2 非对称加密

生成密钥对与基本加解密

java 复制代码
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import cn.hutool.crypto.KeyUtil;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;

public class SM2Demo {
    public static void main(String[] args) {
        String plainText = "使用SM2加密的机密信息";
        
        // 方式1:自动生成密钥对
        SM2 sm2 = SmUtil.sm2();
        
        // 获取密钥
        String privateKeyBase64 = sm2.getPrivateKeyBase64();
        String publicKeyBase64 = sm2.getPublicKeyBase64();
        System.out.println("私钥: " + privateKeyBase64);
        System.out.println("公钥: " + publicKeyBase64);
        
        // 公钥加密,私钥解密
        String encryptStr = sm2.encryptBase64(plainText, KeyType.PublicKey);
        System.out.println("加密结果: " + encryptStr);
        
        String decryptStr = sm2.decryptStr(encryptStr, KeyType.PrivateKey);
        System.out.println("解密结果: " + decryptStr);
        
        // 方式2:使用已有密钥对
        String privateKeyHex = "你的私钥Hex字符串";
        String publicKeyHex = "你的公钥Hex字符串";
        SM2 sm2WithKey = SmUtil.sm2(privateKeyHex, publicKeyHex);
        
        String encrypted = sm2WithKey.encryptBase64(plainText, KeyType.PublicKey);
        String decrypted = sm2WithKey.decryptStr(encrypted, KeyType.PrivateKey);
    }
}

SM2 签名与验签

java 复制代码
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.SM2;
import cn.hutool.crypto.asymmetric.KeyType;

public class SM2SignDemo {
    public static void main(String[] args) {
        // 生成密钥对
        SM2 sm2 = SmUtil.sm2();
        String data = "需要签名的数据";
        
        // 签名(使用私钥)
        String sign = sm2.signBase64(data, null);
        System.out.println("签名: " + sign);
        
        // 验签(使用公钥)
        boolean verified = sm2.verify(data, sign);
        System.out.println("验签结果: " + verified);
        
        // 带用户ID的签名(SM2标准支持)
        String userId = "user123";
        String signWithId = sm2.signBase64(data, userId.getBytes());
        boolean verifiedWithId = sm2.verify(data, signWithId, userId.getBytes());
        System.out.println("带用户ID验签: " + verifiedWithId);
    }
}

密钥的存储与加载

java 复制代码
import cn.hutool.crypto.asymmetric.SM2;
import cn.hutool.crypto.KeyUtil;
import java.security.PrivateKey;
import java.security.PublicKey;

public class SM2KeyStoreDemo {
    public static void main(String[] args) {
        // 从Hex字符串加载
        String priHex = "你的私钥Hex";
        String pubHex = "你的公钥Hex";
        SM2 sm2FromHex = SmUtil.sm2(priHex, pubHex);
        
        // 从Base64字符串加载
        String priBase64 = "你的私钥Base64";
        String pubBase64 = "你的公钥Base64";
        PrivateKey privateKey = KeyUtil.generatePrivateKey("SM2", 
            cn.hutool.core.codec.Base64.decode(priBase64));
        PublicKey publicKey = KeyUtil.generatePublicKey("SM2",
            cn.hutool.core.codec.Base64.decode(pubBase64));
        SM2 sm2FromKeys = new SM2(privateKey, publicKey);
        
        // 导出密钥为PEM格式
        String privateKeyPem = KeyUtil.getPrivateKeyPem(privateKey);
        String publicKeyPem = KeyUtil.getPublicKeyPem(publicKey);
        System.out.println("私钥PEM:\n" + privateKeyPem);
        System.out.println("公钥PEM:\n" + publicKeyPem);
    }
}

六、综合示例:文件加密与签名

java 复制代码
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.SM2;
import cn.hutool.crypto.symmetric.SM4;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.codec.Base64;

import java.io.File;

public class FileEncryptWithSM2SM4 {
    
    // 使用 SM4 加密大文件,SM2 加密 SM4 密钥(混合加密)
    public static void encryptFile(File inputFile, File outputFile, SM2 sm2) {
        // 1. 生成随机的 SM4 密钥
        SM4 sm4 = SmUtil.sm4();
        byte[] sm4Key = sm4.getSecretKey().getEncoded();
        
        // 2. 使用 SM2 公钥加密 SM4 密钥
        String encryptedKey = sm2.encryptBase64(sm4Key, KeyType.PublicKey);
        
        // 3. 使用 SM4 加密文件内容
        byte[] fileBytes = FileUtil.readBytes(inputFile);
        String encryptedData = sm4.encryptBase64(fileBytes);
        
        // 4. 保存:加密后的密钥 + 加密后的数据
        String result = encryptedKey + "\n" + encryptedData;
        FileUtil.writeUtf8String(result, outputFile);
    }
    
    // 解密文件
    public static void decryptFile(File inputFile, File outputFile, SM2 sm2) {
        String content = FileUtil.readUtf8String(inputFile);
        String[] parts = content.split("\n", 2);
        
        // 1. SM2 私钥解密得到 SM4 密钥
        byte[] sm4Key = sm2.decrypt(parts[0], KeyType.PrivateKey);
        
        // 2. 使用 SM4 密钥解密数据
        SM4 sm4 = new SM4(sm4Key);
        byte[] decryptedData = sm4.decrypt(parts[1]);
        
        // 3. 写入文件
        FileUtil.writeBytes(decryptedData, outputFile);
    }
    
    public static void main(String[] args) {
        // 初始化 SM2 密钥对
        SM2 sm2 = SmUtil.sm2();
        
        // 加密文件
        File input = new File("plain.txt");
        File encrypted = new File("encrypted.dat");
        encryptFile(input, encrypted, sm2);
        
        // 解密文件
        File decrypted = new File("decrypted.txt");
        decryptFile(encrypted, decrypted, sm2);
        
        System.out.println("文件加解密完成!");
    }
}

七、注意事项

  1. 密钥长度:SM4 密钥必须为 128 位(16字节)

  2. IV 要求:CBC/GCM 等模式需要 16 字节的 IV

  3. SM2 加密限制:单次加密数据长度有限制,建议混合加密

  4. 性能考虑:大文件应使用流式处理,避免一次性读入内存

  5. BC 版本:确保 Bouncy Castle 版本与 JDK 兼容