概述
消息认证码(MAC)用于验证数据完整性和真实性,确保消息未被篡改且来自预期发送者。
目录
HMAC
原理
HMAC (Hash-based Message Authentication Code) 使用哈希函数(如SHA-256)和密钥生成MAC。
算法:
HMAC(K, m) = H((K ⊕ opad) || H((K ⊕ ipad) || m))
技术规格
| 属性 | 值 |
|---|---|
| 哈希函数 | SHA-256, SHA-384, SHA-512等 |
| 密钥长度 | 建议等于哈希输出长度 |
| 输出长度 | 等于哈希输出长度 |
| 安全级别 | 高 |
应用场景
- API认证:请求签名验证
- JWT:HMAC-SHA256签名
- OAuth:请求签名
- 消息完整性:数据传输验证
- 密码派生:密钥派生函数
- TLS:某些场景使用
性能影响
- 计算速度:快(取决于哈希函数)
- 内存占用:低
- CPU使用率:低
- 吞吐量:高
Java实现示例
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;
public class HMACExample {
/**
* 生成HMAC
*/
public static String generateHMAC(String data, String key, String algorithm) throws Exception {
Mac mac = Mac.getInstance(algorithm);
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), algorithm);
mac.init(secretKeySpec);
byte[] macBytes = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(macBytes);
}
/**
* HMAC-SHA256(最常用)
*/
public static String hmacSHA256(String data, String key) throws Exception {
return generateHMAC(data, key, "HmacSHA256");
}
/**
* HMAC-SHA512
*/
public static String hmacSHA512(String data, String key) throws Exception {
return generateHMAC(data, key, "HmacSHA512");
}
/**
* 验证HMAC
*/
public static boolean verifyHMAC(String data, String receivedMAC, String key, String algorithm)
throws Exception {
String computedMAC = generateHMAC(data, key, algorithm);
return computedMAC.equals(receivedMAC);
}
/**
* 生成随机密钥
*/
public static String generateKey(int keyLengthBytes) {
byte[] key = new byte[keyLengthBytes];
SecureRandom random = new SecureRandom();
random.nextBytes(key);
return Base64.getEncoder().encodeToString(key);
}
/**
* API请求签名示例
*/
public static class APISignature {
/**
* 签名API请求
*/
public static String signRequest(String method, String path, String body,
String timestamp, String key) throws Exception {
// 构造签名字符串
String signString = method + "\n" + path + "\n" + body + "\n" + timestamp;
return hmacSHA256(signString, key);
}
/**
* 验证API请求签名
*/
public static boolean verifyRequest(String method, String path, String body,
String timestamp, String receivedSignature,
String key) throws Exception {
String computedSignature = signRequest(method, path, body, timestamp, key);
return computedSignature.equals(receivedSignature);
}
}
/**
* 完整示例
*/
public static void main(String[] args) throws Exception {
String data = "这是要认证的消息";
String key = "secret-key-12345";
System.out.println("=== HMAC生成和验证 ===");
String hmac = hmacSHA256(data, key);
System.out.println("数据: " + data);
System.out.println("密钥: " + key);
System.out.println("HMAC-SHA256: " + hmac);
boolean isValid = verifyHMAC(data, hmac, key, "HmacSHA256");
System.out.println("验证结果: " + isValid);
// 测试数据被篡改
String tamperedData = "这是被篡改的消息";
boolean isTamperedValid = verifyHMAC(tamperedData, hmac, key, "HmacSHA256");
System.out.println("篡改后验证: " + isTamperedValid);
System.out.println("\n=== 不同哈希算法对比 ===");
String[] algorithms = {"HmacSHA256", "HmacSHA384", "HmacSHA512"};
for (String alg : algorithms) {
String mac = generateHMAC(data, key, alg);
System.out.println(alg + ": " + mac.substring(0, 30) + "...");
}
System.out.println("\n=== API请求签名示例 ===");
String apiKey = generateKey(32);
String method = "POST";
String path = "/api/v1/users";
String body = "{"name":"test"}";
String timestamp = String.valueOf(System.currentTimeMillis());
String signature = APISignature.signRequest(method, path, body, timestamp, apiKey);
System.out.println("请求签名: " + signature);
boolean isValidRequest = APISignature.verifyRequest(
method, path, body, timestamp, signature, apiKey);
System.out.println("请求验证: " + isValidRequest);
System.out.println("\n=== 性能测试 ===");
String testData = "性能测试数据" + "x".repeat(1000);
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
hmacSHA256(testData, key);
}
long end = System.currentTimeMillis();
System.out.println("HMAC-SHA256 (10000次): " + (end - start) + "ms");
}
}
安全建议
✅ 推荐:
- 使用HMAC-SHA256(性能与安全性平衡)
- 密钥长度至少等于哈希输出长度
- 使用密码学安全的随机数生成密钥
- 使用安全比较(防止时序攻击)
⚠️ 注意:
- 密钥必须保密
- 不同消息使用不同密钥或nonce
- 定期轮换密钥
CMAC
原理
CMAC (Cipher-based Message Authentication Code) 基于分组密码(如AES)生成MAC。
技术规格
| 属性 | 值 |
|---|---|
| 底层密码 | AES, DES等 |
| 密钥长度 | 128/192/256位(AES) |
| 输出长度 | 等于块大小(AES为128位) |
| 安全级别 | 高 |
应用场景
- 金融系统:某些支付协议
- 需要基于分组密码的MAC
- 与AES加密配合使用
性能影响
- 计算速度:中等(取决于底层密码)
- 内存占用:低
- CPU使用率:中等
Java实现示例
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;
public class CMACExample {
/**
* 生成AES-CMAC
*/
public static String generateCMAC(String data, byte[] key) throws Exception {
Mac mac = Mac.getInstance("AESCMAC");
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
mac.init(keySpec);
byte[] macBytes = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(macBytes);
}
/**
* 验证CMAC
*/
public static boolean verifyCMAC(String data, String receivedMAC, byte[] key) throws Exception {
String computedMAC = generateCMAC(data, key);
return computedMAC.equals(receivedMAC);
}
/**
* 生成AES密钥
*/
public static byte[] generateAESKey(int keyLengthBits) {
byte[] key = new byte[keyLengthBits / 8];
SecureRandom random = new SecureRandom();
random.nextBytes(key);
return key;
}
public static void main(String[] args) throws Exception {
System.out.println("=== AES-CMAC ===");
// 注意:AESCMAC可能需要Java 11+或BouncyCastle
try {
byte[] key = generateAESKey(256);
String data = "测试数据";
String cmac = generateCMAC(data, key);
System.out.println("数据: " + data);
System.out.println("CMAC: " + cmac);
boolean isValid = verifyCMAC(data, cmac, key);
System.out.println("验证结果: " + isValid);
} catch (Exception e) {
System.out.println("CMAC需要Java 11+或BouncyCastle库支持");
System.out.println("错误: " + e.getMessage());
}
}
}
GMAC
原理
GMAC (Galois Message Authentication Code) 是GCM模式的认证部分,基于Galois域运算。
技术规格
| 属性 | 值 |
|---|---|
| 与GCM结合 | 是 |
| 密钥长度 | 128/192/256位(AES) |
| 标签长度 | 通常128位 |
| 安全级别 | 高 |
应用场景
- AES-GCM加密:自动提供认证
- 需要认证加密的场景
- 高性能认证需求
性能影响
- 计算速度:快(硬件加速)
- 内存占用:低
- CPU使用率:低(硬件加速时)
Java实现示例
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;
public class GMACExample {
/**
* AES-GCM加密(包含GMAC认证)
*/
public static class EncryptedData {
public String encryptedData;
public String iv;
public String tag;
public EncryptedData(String encryptedData, String iv, String tag) {
this.encryptedData = encryptedData;
this.iv = iv;
this.tag = tag;
}
}
/**
* AES-GCM加密(自动生成认证标签)
*/
public static EncryptedData encryptWithAuth(String plaintext, SecretKeySpec key) throws Exception {
byte[] iv = new byte[12];
SecureRandom random = new SecureRandom();
random.nextBytes(iv);
GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key, gcmSpec);
byte[] ciphertext = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
return new EncryptedData(
Base64.getEncoder().encodeToString(ciphertext),
Base64.getEncoder().encodeToString(iv),
"" // GCM标签包含在密文中
);
}
/**
* AES-GCM解密(自动验证GMAC)
*/
public static String decryptWithAuth(EncryptedData encrypted, SecretKeySpec key) throws Exception {
byte[] iv = Base64.getDecoder().decode(encrypted.iv);
byte[] ciphertext = Base64.getDecoder().decode(encrypted.encryptedData);
GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, key, gcmSpec);
// 如果认证失败,会抛出异常
byte[] plaintext = cipher.doFinal(ciphertext);
return new String(plaintext, StandardCharsets.UTF_8);
}
public static void main(String[] args) throws Exception {
System.out.println("=== AES-GCM with GMAC ===");
byte[] keyBytes = new byte[32];
SecureRandom random = new SecureRandom();
random.nextBytes(keyBytes);
SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
String plaintext = "需要加密和认证的数据";
EncryptedData encrypted = encryptWithAuth(plaintext, key);
System.out.println("原文: " + plaintext);
System.out.println("IV: " + encrypted.iv);
System.out.println("密文: " + encrypted.encryptedData);
String decrypted = decryptWithAuth(encrypted, key);
System.out.println("解密: " + decrypted);
// 尝试篡改密文(会认证失败)
try {
EncryptedData tampered = new EncryptedData(
Base64.getEncoder().encodeToString("篡改的密文".getBytes()),
encrypted.iv,
encrypted.tag
);
decryptWithAuth(tampered, key);
System.out.println("⚠️ 篡改未被检测到!");
} catch (Exception e) {
System.out.println("✓ 篡改被检测到: " + e.getMessage());
}
}
}
Poly1305
原理
Poly1305是基于模运算的消息认证码,通常与ChaCha20结合使用(ChaCha20-Poly1305)。
技术规格
| 属性 | 值 |
|---|---|
| 输出长度 | 128位 |
| 密钥长度 | 256位 |
| 安全级别 | 高 |
| 常用组合 | ChaCha20-Poly1305 |
应用场景
- ChaCha20加密:ChaCha20-Poly1305认证加密
- TLS:某些TLS实现
- VPN:WireGuard协议
- 高性能场景:软件实现性能好
性能影响
- 计算速度:非常快(软件实现)
- 内存占用:低
- CPU使用率:低
Java实现示例
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;
public class Poly1305Example {
/**
* ChaCha20-Poly1305加密(包含Poly1305认证)
*/
public static String encryptWithAuth(String plaintext, SecretKeySpec key) throws Exception {
byte[] nonce = new byte[12];
SecureRandom random = new SecureRandom();
random.nextBytes(nonce);
IvParameterSpec ivSpec = new IvParameterSpec(nonce);
Cipher cipher = Cipher.getInstance("ChaCha20-Poly1305");
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
byte[] ciphertext = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
// 组合nonce和密文
byte[] combined = new byte[12 + ciphertext.length];
System.arraycopy(nonce, 0, combined, 0, 12);
System.arraycopy(ciphertext, 0, combined, 12, ciphertext.length);
return Base64.getEncoder().encodeToString(combined);
}
/**
* ChaCha20-Poly1305解密(自动验证Poly1305)
*/
public static String decryptWithAuth(String encryptedData, SecretKeySpec key) throws Exception {
byte[] combined = Base64.getDecoder().decode(encryptedData);
byte[] nonce = new byte[12];
System.arraycopy(combined, 0, nonce, 0, 12);
byte[] ciphertext = new byte[combined.length - 12];
System.arraycopy(combined, 12, ciphertext, 0, ciphertext.length);
IvParameterSpec ivSpec = new IvParameterSpec(nonce);
Cipher cipher = Cipher.getInstance("ChaCha20-Poly1305");
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
// 如果认证失败,会抛出异常
byte[] plaintext = cipher.doFinal(ciphertext);
return new String(plaintext, StandardCharsets.UTF_8);
}
public static void main(String[] args) throws Exception {
System.out.println("=== ChaCha20-Poly1305 ===");
// 注意:需要Java 11+
try {
byte[] keyBytes = new byte[32];
SecureRandom random = new SecureRandom();
random.nextBytes(keyBytes);
SecretKeySpec key = new SecretKeySpec(keyBytes, "ChaCha20");
String plaintext = "需要加密和认证的数据";
String encrypted = encryptWithAuth(plaintext, key);
System.out.println("原文: " + plaintext);
System.out.println("加密: " + encrypted);
String decrypted = decryptWithAuth(encrypted, key);
System.out.println("解密: " + decrypted);
// 尝试篡改
try {
String tampered = encrypted.substring(0, encrypted.length() - 5) + "XXXXX";
decryptWithAuth(tampered, key);
System.out.println("⚠️ 篡改未被检测到!");
} catch (Exception e) {
System.out.println("✓ 篡改被检测到: " + e.getMessage());
}
} catch (Exception e) {
System.out.println("需要Java 11+支持ChaCha20-Poly1305");
System.out.println("错误: " + e.getMessage());
}
}
}
性能对比
MAC算法性能对比
| 算法 | 计算速度 | 输出长度 | 密钥要求 | 推荐度 |
|---|---|---|---|---|
| HMAC-SHA256 | 快 | 256位 | 可变 | ⭐⭐⭐⭐⭐ |
| HMAC-SHA512 | 快 | 512位 | 可变 | ⭐⭐⭐⭐ |
| AES-CMAC | 中等 | 128位 | 128/192/256位 | ⭐⭐⭐ |
| GMAC | 快 | 128位 | 128/192/256位 | ⭐⭐⭐⭐ |
| Poly1305 | 很快 | 128位 | 256位 | ⭐⭐⭐⭐ |
选择建议
通用场景:
- 首选:HMAC-SHA256
与AES-GCM配合:
- GMAC(自动包含)
与ChaCha20配合:
- Poly1305(自动包含)
需要基于分组密码:
- CMAC
HMAC vs 数字签名
对比表
| 特性 | HMAC | 数字签名 |
|---|---|---|
| 密钥类型 | 对称密钥 | 非对称密钥对 |
| 密钥分发 | 需要安全通道 | 公钥可公开 |
| 计算速度 | 快 | 慢(签名) |
| 验证速度 | 快 | 快(验证) |
| 不可否认性 | 否 | 是 |
| 适用场景 | API认证、消息完整性 | 数字证书、代码签名 |
选择建议
使用HMAC:
- API认证
- 消息完整性验证
- 双方共享密钥的场景
使用数字签名:
- 需要不可否认性
- 公钥可公开的场景
- 数字证书、代码签名
安全注意事项
常见攻击
- 密钥泄露:密钥必须保密
- 重放攻击:使用时间戳或nonce
- 时序攻击:使用安全比较
- 密钥重用:不同场景使用不同密钥
最佳实践
/**
* 安全比较MAC(防止时序攻击)
*/
public static boolean secureCompare(byte[] a, byte[] b) {
if (a.length != b.length) {
return false;
}
int result = 0;
for (int i = 0; i < a.length; i++) {
result |= a[i] ^ b[i];
}
return result == 0;
}
总结
推荐选择
通用消息认证:
- 首选:HMAC-SHA256
认证加密:
- AES-GCM(包含GMAC)
- ChaCha20-Poly1305(包含Poly1305)
特殊需求:
- CMAC:需要基于分组密码的MAC
选择决策树
需要消息认证?
├─ 与加密结合?→ GCM/GMAC 或 ChaCha20-Poly1305
├─ API认证?→ HMAC-SHA256
├─ 需要分组密码MAC?→ CMAC
└─ 通用场景?→ HMAC-SHA256