一、HMAC-SHA256 核心讲解
HMAC-SHA256 本质不是“加密”(加密可逆,它不可逆),而是带密钥的哈希消息认证码,核心是把「密钥」和「SHA-256 哈希算法」结合,既能验证数据的完整性(数据没被篡改),又能验证来源真实性(只有持有密钥的人能生成有效认证码)。
1. 核心特性
- 输出固定 256 位(32 字节),通常转成 64 位十六进制字符串;
- 不可逆:无法从生成的 HMAC 值反推原始数据或密钥;
- 抗篡改:哪怕原始数据改 1 个字符,生成的 HMAC 值会完全不同;
- 需共享密钥:只有持有相同密钥的双方,才能验证 HMAC 的有效性。
2. 核心计算逻辑
简单来说,HMAC-SHA256 会先对密钥做预处理(补位/哈希),再和数据分两次结合 SHA-256 计算:
HMAC(K, M) = SHA256( (密钥⊕外填充) || SHA256( (密钥⊕内填充) || 消息 ) )
(⊕=异或,||=字节拼接,内填充=0x36重复64次,外填充=0x5C重复64次)
二、Java 代码示例(完整可运行)
Java 内置了 javax.crypto.Mac 类专门处理 HMAC 算法,无需引入第三方库,以下是完整示例,包含「生成 HMAC-SHA256 签名」和「验证签名」两个核心功能:
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
/**
* HMAC-SHA256 工具类(生成签名 + 验证签名)
*/
public class HmacSha256Utils {
// 算法名称(固定格式:Hmac + 哈希算法名)
private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256";
/**
* 生成 HMAC-SHA256 签名(返回十六进制字符串,更易读)
* @param data 待签名的原始数据
* @param secretKey 共享密钥(需双方保密)
* @return 64位十六进制签名串
* @throws NoSuchAlgorithmException 算法不存在(理论上不会出现)
* @throws InvalidKeyException 密钥无效(如空密钥)
*/
public static String generateHmacSha256Hex(String data, String secretKey)
throws NoSuchAlgorithmException, InvalidKeyException {
// 1. 创建 SecretKeySpec 对象(封装密钥和算法)
SecretKeySpec secretKeySpec = new SecretKeySpec(
secretKey.getBytes(StandardCharsets.UTF_8),
HMAC_SHA256_ALGORITHM
);
// 2. 获取 Mac 实例并初始化
Mac mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);
mac.init(secretKeySpec);
// 3. 计算 HMAC 字节数组
byte[] hmacBytes = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
// 4. 转成十六进制字符串(比 Base64 更直观)
return bytesToHex(hmacBytes);
}
/**
* 验证签名是否有效
* @param data 原始数据
* @param secretKey 共享密钥
* @param sign 待验证的签名(十六进制字符串)
* @return true=签名有效,false=无效(数据被篡改/密钥错误)
*/
public static boolean verifyHmacSha256(String data, String secretKey, String sign) {
try {
// 重新生成签名,和待验证签名对比
String generatedSign = generateHmacSha256Hex(data, secretKey);
return generatedSign.equalsIgnoreCase(sign);
} catch (Exception e) {
// 算法/密钥异常时,直接判定签名无效
return false;
}
}
/**
* 字节数组转十六进制字符串(工具方法)
*/
private static String bytesToHex(byte[] bytes) {
StringBuilder hexString = new StringBuilder();
for (byte b : bytes) {
// 转成两位十六进制,不足补0
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
}
// 测试主方法
public static void main(String[] args) {
// 测试数据
String originalData = "Hello, HMAC-SHA256!"; // 待签名数据
String secretKey = "my_secure_key_123456"; // 共享密钥(实际需用更复杂的密钥)
try {
// 1. 生成签名
String sign = generateHmacSha256Hex(originalData, secretKey);
System.out.println("原始数据:" + originalData);
System.out.println("HMAC-SHA256 签名(十六进制):" + sign);
// 2. 验证签名(正常情况:数据和密钥都正确)
boolean isValid = verifyHmacSha256(originalData, secretKey, sign);
System.out.println("签名验证结果(数据未篡改):" + isValid); // 输出 true
// 3. 验证篡改后的数据(模拟数据被改)
String tamperedData = "Hello, HMAC-SHA256?!";
boolean isTamperedValid = verifyHmacSha256(tamperedData, secretKey, sign);
System.out.println("签名验证结果(数据被篡改):" + isTamperedValid); // 输出 false
} catch (Exception e) {
e.printStackTrace();
}
}
}
代码关键解释
- 核心类:
SecretKeySpec:封装密钥和算法,把字符串密钥转成算法可识别的密钥对象;Mac:Java 处理 HMAC 算法的核心类,getInstance("HmacSHA256")指定算法,init()初始化密钥,doFinal()计算最终的 HMAC 字节数组。
- 字节转十六进制:
bytesToHex是工具方法,把二进制的 HMAC 结果转成易读的十六进制字符串(也可改用 Base64,只需替换该方法为Base64.getEncoder().encodeToString(hmacBytes))。 - 签名验证:核心逻辑是“用相同密钥+相同数据重新生成签名,和待验证签名对比”,只要数据/密钥有一个不对,结果就不匹配。
运行结果示例
原始数据:Hello, HMAC-SHA256!
HMAC-SHA256 签名(十六进制):f8b8b7c9e8a7d6b5c4a3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1
签名验证结果(数据未篡改):true
签名验证结果(数据被篡改):false
三、使用注意事项
- 密钥安全:密钥必须保密且足够复杂(建议至少 32 字节,可随机生成),绝对不能硬编码在代码里,生产环境建议从配置中心/密钥管理服务读取;
- 字符编码:统一使用 UTF-8 编码(避免因编码不一致导致签名不匹配);
- 异常处理:实际项目中不要直接打印异常,建议用日志框架记录,并对
InvalidKeyException做针对性处理(如密钥为空/格式错误); - 性能:HMAC-SHA256 计算效率高,适合高频场景(如 API 签名)。
总结
- HMAC-SHA256 是「密钥+SHA-256」的组合,核心用于验证数据完整性和来源真实性,而非加密;
- Java 中可通过
Mac类快速实现 HMAC-SHA256,核心步骤是“封装密钥→初始化算法→计算签名→验证签名”; - 密钥安全和编码统一是使用该算法的关键,否则会导致签名验证失败或安全漏洞。
注意:本文归作者所有,未经作者允许,不得转载