概述
密钥派生函数(KDF)用于从密码、主密钥或其他密钥材料派生加密密钥。密码学安全的随机数生成器用于生成密钥、IV等随机值。
目录
PBKDF2
原理
PBKDF2 (Password-Based Key Derivation Function 2) 使用迭代哈希从密码派生密钥。通过增加迭代次数提高安全性。
算法:
ini
DK = PBKDF2(PRF, Password, Salt, c, dkLen)
技术规格
| 属性 | 值 |
|---|---|
| 迭代次数 | 建议10000+(2024年建议100000+) |
| Salt长度 | 至少64位(推荐128位) |
| 输出长度 | 可变 |
| PRF | HMAC-SHA1, HMAC-SHA256等 |
应用场景
- 密码存储:从密码派生哈希值
- 密钥派生:从主密钥派生子密钥
- 加密密钥生成:从用户密码派生加密密钥
性能影响
- 计算速度:慢(设计如此,抗暴力破解)
- 内存占用:低
- CPU使用率:高(迭代次数多)
- 可调性:通过迭代次数调整安全性与性能
Java实现示例
java
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import java.util.Base64;
public class PBKDF2Example {
/**
* PBKDF2密钥派生(用于密码哈希)
*/
public static class PasswordHash {
public String hash;
public String salt;
public int iterations;
public PasswordHash(String hash, String salt, int iterations) {
this.hash = hash;
this.salt = salt;
this.iterations = iterations;
}
}
/**
* 生成密码哈希
*/
public static PasswordHash hashPassword(String password, int iterations) throws Exception {
// 生成随机Salt
SecureRandom random = new SecureRandom();
byte[] salt = new byte[16]; // 128位Salt
random.nextBytes(salt);
// PBKDF2 with HMAC-SHA256
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iterations, 256);
byte[] hash = factory.generateSecret(spec).getEncoded();
return new PasswordHash(
Base64.getEncoder().encodeToString(hash),
Base64.getEncoder().encodeToString(salt),
iterations
);
}
/**
* 验证密码
*/
public static boolean verifyPassword(String password, PasswordHash stored) throws Exception {
byte[] salt = Base64.getDecoder().decode(stored.salt);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, stored.iterations, 256);
byte[] hash = factory.generateSecret(spec).getEncoded();
String computedHash = Base64.getEncoder().encodeToString(hash);
return computedHash.equals(stored.hash);
}
/**
* 派生加密密钥
*/
public static byte[] deriveKey(String password, byte[] salt, int iterations, int keyLength)
throws Exception {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iterations, keyLength * 8);
return factory.generateSecret(spec).getEncoded();
}
/**
* 完整示例
*/
public static void main(String[] args) throws Exception {
String password = "用户密码123";
System.out.println("=== PBKDF2密码哈希 ===");
PasswordHash hash = hashPassword(password, 100000);
System.out.println("密码: " + password);
System.out.println("Salt: " + hash.salt);
System.out.println("迭代次数: " + hash.iterations);
System.out.println("哈希: " + hash.hash);
System.out.println("\n=== 密码验证 ===");
boolean isValid = verifyPassword(password, hash);
System.out.println("验证结果: " + isValid);
boolean isInvalid = verifyPassword("错误密码", hash);
System.out.println("错误密码验证: " + isInvalid);
System.out.println("\n=== 派生加密密钥 ===");
byte[] salt = new byte[16];
SecureRandom random = new SecureRandom();
random.nextBytes(salt);
byte[] key = deriveKey(password, salt, 100000, 32); // 256位密钥
System.out.println("派生密钥长度: " + key.length + " 字节 (256位)");
System.out.println("\n=== 性能测试(不同迭代次数) ===");
int[] iterationsArray = {10000, 50000, 100000, 200000};
for (int iter : iterationsArray) {
long start = System.currentTimeMillis();
hashPassword("test", iter);
long end = System.currentTimeMillis();
System.out.println("迭代次数 " + iter + ": " + (end - start) + "ms");
}
}
}
安全建议
✅ 推荐配置:
- 迭代次数:至少100000次(2024年)
- Salt长度:至少128位
- 使用HMAC-SHA256而非SHA1
⚠️ 注意:
- 迭代次数应根据硬件性能调整(至少100ms计算时间)
- 定期增加迭代次数以适应硬件发展
bcrypt
原理
bcrypt是基于Blowfish密码的密钥派生函数,专门设计用于密码哈希。通过"成本因子"控制计算复杂度。
技术规格
| 属性 | 值 |
|---|---|
| 成本因子 | 4-31(推荐10-12) |
| Salt长度 | 128位(自动生成) |
| 输出长度 | 60字符(包含元数据) |
| 轮数 | 2^成本因子 |
应用场景
- 密码存储:广泛用于Web应用
- 数据库密码:某些数据库系统
- 系统认证:Unix/Linux系统
性能影响
- 计算速度:慢(设计如此)
- 内存占用:中等
- CPU使用率:高
- 可调性:通过成本因子调整
Java实现示例
arduino
// 需要使用第三方库,如jBCrypt或Spring Security
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import java.util.regex.Pattern;
public class BCryptExample {
private static final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(12);
/**
* 生成密码哈希
*/
public static String hashPassword(String password) {
return encoder.encode(password);
}
/**
* 验证密码
*/
public static boolean verifyPassword(String password, String hash) {
return encoder.matches(password, hash);
}
/**
* 使用自定义成本因子
*/
public static String hashPasswordWithStrength(String password, int strength) {
BCryptPasswordEncoder customEncoder = new BCryptPasswordEncoder(strength);
return customEncoder.encode(password);
}
/**
* 检查哈希是否需要升级(成本因子增加)
*/
public static boolean needsUpgrade(String hash, int minStrength) {
// bcrypt哈希格式:$2a$10$...
Pattern pattern = Pattern.compile("\$2[aby]?\$(\d+)\$");
java.util.regex.Matcher matcher = pattern.matcher(hash);
if (matcher.find()) {
int strength = Integer.parseInt(matcher.group(1));
return strength < minStrength;
}
return true;
}
public static void main(String[] args) {
String password = "用户密码";
System.out.println("=== bcrypt密码哈希 ===");
String hash = hashPassword(password);
System.out.println("密码: " + password);
System.out.println("哈希: " + hash);
System.out.println("哈希长度: " + hash.length() + " 字符");
System.out.println("\n=== 密码验证 ===");
boolean isValid = verifyPassword(password, hash);
System.out.println("验证结果: " + isValid);
System.out.println("\n=== 不同成本因子性能测试 ===");
for (int strength = 10; strength <= 14; strength++) {
long start = System.currentTimeMillis();
hashPasswordWithStrength(password, strength);
long end = System.currentTimeMillis();
System.out.println("成本因子 " + strength + ": " + (end - start) + "ms");
}
System.out.println("\n=== 检查是否需要升级 ===");
String oldHash = hashPasswordWithStrength(password, 10);
System.out.println("旧哈希需要升级: " + needsUpgrade(oldHash, 12));
}
}
安全建议
✅ 推荐配置:
- 成本因子:12-14(2024年)
- 自动升级:检测旧哈希并升级
⚠️ 注意:
- bcrypt输出包含Salt和成本因子
- 成本因子每增加1,时间翻倍
scrypt
原理
scrypt设计用于抵抗硬件攻击(GPU、ASIC),通过同时使用CPU和内存资源提高安全性。
技术规格
| 参数 | 说明 | 推荐值 |
|---|---|---|
| N | CPU/内存成本 | 16384-32768 |
| r | 块大小参数 | 8 |
| p | 并行度 | 1-4 |
应用场景
- 加密货币:某些币种使用
- 高安全应用:需要抗GPU攻击
- 密码存储:需要更高安全性
性能影响
- 计算速度:很慢(设计如此)
- 内存占用:高(N*r参数决定)
- CPU使用率:高
- 抗攻击性:强(抗GPU/ASIC)
Java实现示例
arduino
// 需要使用第三方库,如BouncyCastle
import org.bouncycastle.crypto.generators.SCrypt;
import java.security.SecureRandom;
import java.util.Base64;
public class SCryptExample {
/**
* scrypt密钥派生
*/
public static class SCryptHash {
public String hash;
public String salt;
public int N;
public int r;
public int p;
public SCryptHash(String hash, String salt, int N, int r, int p) {
this.hash = hash;
this.salt = salt;
this.N = N;
this.r = r;
this.p = p;
}
}
/**
* 生成密码哈希
*/
public static SCryptHash hashPassword(String password, int N, int r, int p) {
// 生成Salt
byte[] salt = new byte[32];
SecureRandom random = new SecureRandom();
random.nextBytes(salt);
// scrypt派生(输出256位)
byte[] hash = SCrypt.generate(
password.getBytes(),
salt,
N, // CPU/内存成本
r, // 块大小
p, // 并行度
32 // 输出长度(256位)
);
return new SCryptHash(
Base64.getEncoder().encodeToString(hash),
Base64.getEncoder().encodeToString(salt),
N, r, p
);
}
/**
* 验证密码
*/
public static boolean verifyPassword(String password, SCryptHash stored) {
byte[] salt = Base64.getDecoder().decode(stored.salt);
byte[] hash = SCrypt.generate(
password.getBytes(),
salt,
stored.N,
stored.r,
stored.p,
32
);
String computedHash = Base64.getEncoder().encodeToString(hash);
return computedHash.equals(stored.hash);
}
public static void main(String[] args) {
String password = "用户密码";
System.out.println("=== scrypt密码哈希 ===");
SCryptHash hash = hashPassword(password, 16384, 8, 1);
System.out.println("密码: " + password);
System.out.println("N: " + hash.N);
System.out.println("r: " + hash.r);
System.out.println("p: " + hash.p);
System.out.println("Salt: " + hash.salt);
System.out.println("哈希: " + hash.hash);
System.out.println("\n=== 密码验证 ===");
boolean isValid = verifyPassword(password, hash);
System.out.println("验证结果: " + isValid);
System.out.println("\n=== 性能测试 ===");
long start = System.currentTimeMillis();
hashPassword(password, 16384, 8, 1);
long end = System.currentTimeMillis();
System.out.println("scrypt (N=16384): " + (end - start) + "ms");
}
}
安全建议
✅ 推荐配置:
- N=16384, r=8, p=1(交互式登录)
- N=32768, r=8, p=1(更高安全)
- 根据可用内存调整N和r
⚠️ 注意:
- 内存需求 = 128 * N * r 字节
- 内存不足会严重影响性能
Argon2
原理
Argon2是密码哈希竞赛(PHC)的获胜者,提供三种变体:
- Argon2d:抗GPU攻击
- Argon2i:抗侧信道攻击
- Argon2id:混合模式(推荐)
技术规格
| 参数 | 说明 | 推荐值 |
|---|---|---|
| memory | 内存成本(KB) | 65536 (64MB) |
| iterations | 迭代次数 | 3 |
| parallelism | 并行度 | 4 |
| hashLength | 输出长度 | 32字节 |
应用场景
- 密码存储:现代应用首选
- 高安全场景:需要抗各类攻击
- 新项目:推荐使用Argon2
性能影响
- 计算速度:慢(设计如此)
- 内存占用:高(可配置)
- CPU使用率:高
- 抗攻击性:最强
Java实现示例
arduino
// 需要使用第三方库,如BouncyCastle
import org.bouncycastle.crypto.generators.Argon2BytesGenerator;
import org.bouncycastle.crypto.params.Argon2Parameters;
import java.security.SecureRandom;
import java.util.Base64;
public class Argon2Example {
public static class Argon2Hash {
public String hash;
public String salt;
public int memory;
public int iterations;
public int parallelism;
public Argon2Hash(String hash, String salt, int memory, int iterations, int parallelism) {
this.hash = hash;
this.salt = salt;
this.memory = memory;
this.iterations = iterations;
this.parallelism = parallelism;
}
}
/**
* Argon2id密码哈希(推荐)
*/
public static Argon2Hash hashPassword(String password, int memory, int iterations,
int parallelism) {
// 生成Salt
byte[] salt = new byte[16];
SecureRandom random = new SecureRandom();
random.nextBytes(salt);
// Argon2id参数
Argon2Parameters.Builder builder = new Argon2Parameters.Builder(Argon2Parameters.ARGON2_id)
.withSalt(salt)
.withMemoryAsKB(memory)
.withIterations(iterations)
.withParallelism(parallelism);
Argon2BytesGenerator generator = new Argon2BytesGenerator();
generator.init(builder.build());
// 生成哈希(256位)
byte[] hash = new byte[32];
generator.generateBytes(password.getBytes(), hash);
return new Argon2Hash(
Base64.getEncoder().encodeToString(hash),
Base64.getEncoder().encodeToString(salt),
memory,
iterations,
parallelism
);
}
/**
* 验证密码
*/
public static boolean verifyPassword(String password, Argon2Hash stored) {
byte[] salt = Base64.getDecoder().decode(stored.salt);
Argon2Parameters.Builder builder = new Argon2Parameters.Builder(Argon2Parameters.ARGON2_id)
.withSalt(salt)
.withMemoryAsKB(stored.memory)
.withIterations(stored.iterations)
.withParallelism(stored.parallelism);
Argon2BytesGenerator generator = new Argon2BytesGenerator();
generator.init(builder.build());
byte[] hash = new byte[32];
generator.generateBytes(password.getBytes(), hash);
String computedHash = Base64.getEncoder().encodeToString(hash);
return computedHash.equals(stored.hash);
}
public static void main(String[] args) {
String password = "用户密码";
System.out.println("=== Argon2id密码哈希 ===");
Argon2Hash hash = hashPassword(password, 65536, 3, 4);
System.out.println("密码: " + password);
System.out.println("内存: " + hash.memory + " KB");
System.out.println("迭代: " + hash.iterations);
System.out.println("并行度: " + hash.parallelism);
System.out.println("Salt: " + hash.salt);
System.out.println("哈希: " + hash.hash);
System.out.println("\n=== 密码验证 ===");
boolean isValid = verifyPassword(password, hash);
System.out.println("验证结果: " + isValid);
System.out.println("\n=== 不同配置性能测试 ===");
int[] memorySizes = {32768, 65536, 131072};
for (int mem : memorySizes) {
long start = System.currentTimeMillis();
hashPassword(password, mem, 3, 4);
long end = System.currentTimeMillis();
System.out.println("内存 " + mem + " KB: " + (end - start) + "ms");
}
}
}
安全建议
✅ 推荐配置:
- Argon2id:混合模式,推荐
- 内存:64MB(65536 KB)
- 迭代次数:3
- 并行度:4
- 输出长度:32字节
⚠️ 注意:
- 内存和并行度可根据服务器资源调整
- 交互式登录建议100-500ms计算时间
HKDF
原理
HKDF (HMAC-based Key Derivation Function) 用于从主密钥派生多个子密钥。包括提取(Extract)和扩展(Expand)两个阶段。
技术规格
| 属性 | 值 |
|---|---|
| 输入 | 主密钥 + Salt(可选) |
| 输出 | 可变长度子密钥 |
| 算法 | HMAC-SHA256/512 |
应用场景
- 密钥派生:从主密钥派生多个子密钥
- TLS:密钥派生
- 密钥分层:密钥管理系统
性能影响
- 计算速度:快
- 内存占用:低
- CPU使用率:低
Java实现示例
ini
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
public class HKDFExample {
/**
* HKDF提取阶段
*/
private static byte[] extract(byte[] salt, byte[] inputKeyMaterial) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec keySpec = new SecretKeySpec(salt, "HmacSHA256");
mac.init(keySpec);
return mac.doFinal(inputKeyMaterial);
}
/**
* HKDF扩展阶段
*/
private static byte[] expand(byte[] prk, byte[] info, int length) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec keySpec = new SecretKeySpec(prk, "HmacSHA256");
mac.init(keySpec);
byte[] output = new byte[length];
byte[] t = new byte[0];
int offset = 0;
int counter = 1;
while (offset < length) {
mac.update(t);
mac.update(info);
mac.update((byte) counter);
t = mac.doFinal();
int toCopy = Math.min(t.length, length - offset);
System.arraycopy(t, 0, output, offset, toCopy);
offset += toCopy;
counter++;
}
return output;
}
/**
* HKDF完整流程
*/
public static byte[] derive(byte[] inputKeyMaterial, byte[] salt, byte[] info, int length)
throws Exception {
// 如果没有提供Salt,使用全零
if (salt == null || salt.length == 0) {
salt = new byte[32]; // 256位全零
}
// 提取阶段
byte[] prk = extract(salt, inputKeyMaterial);
// 扩展阶段
return expand(prk, info, length);
}
/**
* 派生多个密钥
*/
public static byte[][] deriveMultipleKeys(byte[] masterKey, int keyCount, int keyLength)
throws Exception {
byte[][] keys = new byte[keyCount][];
for (int i = 0; i < keyCount; i++) {
byte[] info = ("key-" + i).getBytes();
keys[i] = derive(masterKey, null, info, keyLength);
}
return keys;
}
public static void main(String[] args) throws Exception {
// 生成主密钥
byte[] masterKey = new byte[32];
SecureRandom random = new SecureRandom();
random.nextBytes(masterKey);
System.out.println("=== HKDF密钥派生 ===");
byte[] salt = new byte[32];
random.nextBytes(salt);
byte[] info = "application-key".getBytes();
byte[] derivedKey = derive(masterKey, salt, info, 32);
System.out.println("主密钥长度: " + masterKey.length + " 字节");
System.out.println("派生密钥长度: " + derivedKey.length + " 字节");
System.out.println("\n=== 派生多个密钥 ===");
byte[][] keys = deriveMultipleKeys(masterKey, 3, 32);
System.out.println("派生了 " + keys.length + " 个密钥");
for (int i = 0; i < keys.length; i++) {
System.out.println("密钥 " + i + " 长度: " + keys[i].length + " 字节");
}
}
}
密码学随机数生成器
原理
密码学安全的随机数生成器(CSPRNG)生成不可预测的随机数,用于密钥、IV、nonce等。
Java SecureRandom
Java的SecureRandom提供密码学安全的随机数生成。
Java实现示例
arduino
import java.security.SecureRandom;
import java.util.Base64;
public class SecureRandomExample {
/**
* 生成随机字节
*/
public static byte[] generateRandomBytes(int length) {
SecureRandom random = new SecureRandom();
byte[] bytes = new byte[length];
random.nextBytes(bytes);
return bytes;
}
/**
* 生成随机密钥
*/
public static String generateRandomKey(int keyLengthBits) {
byte[] key = generateRandomBytes(keyLengthBits / 8);
return Base64.getEncoder().encodeToString(key);
}
/**
* 生成随机Salt
*/
public static String generateSalt(int saltLengthBytes) {
return Base64.getEncoder().encodeToString(generateRandomBytes(saltLengthBytes));
}
/**
* 生成随机IV
*/
public static byte[] generateIV(int ivLengthBytes) {
return generateRandomBytes(ivLengthBytes);
}
/**
* 生成随机Nonce
*/
public static byte[] generateNonce(int nonceLengthBytes) {
return generateRandomBytes(nonceLengthBytes);
}
/**
* 生成随机整数(在范围内)
*/
public static int generateRandomInt(int min, int max) {
SecureRandom random = new SecureRandom();
return random.nextInt(max - min + 1) + min;
}
/**
* 使用特定算法
*/
public static SecureRandom createSecureRandom(String algorithm) throws Exception {
return SecureRandom.getInstance(algorithm);
}
public static void main(String[] args) throws Exception {
System.out.println("=== 密码学随机数生成 ===");
System.out.println("\n随机密钥:");
System.out.println("256位密钥: " + generateRandomKey(256));
System.out.println("\n随机Salt:");
System.out.println("128位Salt: " + generateSalt(16));
System.out.println("\n随机IV:");
byte[] iv = generateIV(16);
System.out.println("IV: " + Base64.getEncoder().encodeToString(iv));
System.out.println("\n随机Nonce:");
byte[] nonce = generateNonce(12);
System.out.println("Nonce: " + Base64.getEncoder().encodeToString(nonce));
System.out.println("\n随机整数 (1-100):");
for (int i = 0; i < 5; i++) {
System.out.println(generateRandomInt(1, 100));
}
System.out.println("\n可用的SecureRandom算法:");
String[] algorithms = {"NativePRNG", "SHA1PRNG", "Windows-PRNG"};
for (String alg : algorithms) {
try {
SecureRandom sr = createSecureRandom(alg);
System.out.println(alg + ": ✓");
} catch (Exception e) {
System.out.println(alg + ": ✗");
}
}
}
}
安全建议
✅ 推荐:
- 使用
SecureRandom而非Random - 生成足够长度的随机值(密钥至少128位)
- IV和Nonce必须唯一(不重复使用)
⚠️ 避免:
- 使用
java.util.Random(不安全) - 重复使用IV/Nonce
- 可预测的随机数
密码哈希算法对比
| 算法 | 抗暴力破解 | 抗GPU攻击 | 抗侧信道 | 内存需求 | 推荐度 |
|---|---|---|---|---|---|
| Argon2id | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 高 | ⭐⭐⭐⭐⭐ |
| scrypt | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | 高 | ⭐⭐⭐⭐ |
| bcrypt | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | 低 | ⭐⭐⭐⭐ |
| PBKDF2 | ⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐ | 低 | ⭐⭐⭐ |
选择建议
新项目: Argon2id 现有项目: bcrypt或PBKDF2(逐步迁移) 高安全需求: Argon2id或scrypt 资源受限: PBKDF2
总结
密钥派生选择
密码哈希:
- 首选:Argon2id
- 备选:bcrypt
密钥派生:
- 首选:HKDF
- 备选:PBKDF2
随机数生成
必须使用 :SecureRandom
选择决策树
需要密钥派生?
├─ 密码哈希?→ Argon2id
├─ 从主密钥派生?→ HKDF
└─ 兼容性要求?→ PBKDF2
需要随机数?
└─ 使用SecureRandom