Java密码管理需用AES/GCM/NoPadding加密、PBKDF2派生密钥、SecureRandom生成nonce和salt,统一错误响应并避免时序攻击。

Java 本身不提供开箱即用的「密码管理工具」,但可以用标准库组合实现核心功能:安全存储、加密读写、主密码校验。关键不在造轮子,而在避开 Cipher 初始化向量复用、SecretKeyFactory 参数硬编码、String 存密等典型陷阱。
用 AES/GCM/NoPadding 加密密码条目,别碰 ECB 模式
ECB 模式会暴露明文结构(比如相同密码加密后总生成相同密文),GCM 提供认证加密,能防篡改。必须为每次加密生成唯一 nonce(12 字节),且不能重复使用同一 SecretKey + nonce 组合。
byte[] nonce = new byte[12];
SecureRandom.getInstanceStrong().nextBytes(nonce); // 每次加密都新生成
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new GCMParameterSpec(128, nonce));
byte[] encrypted = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
// 最终存储:nonce + encrypted(拼接或分字段存)
-
nonce必须和密文一起持久化,解密时原样传入GCMParameterSpec - 别用
new SecureRandom()—— 它可能阻塞;用SecureRandom.getInstanceStrong()或至少getInstance("SHA1PRNG") - 密钥长度必须是 128/192/256 位;推荐用 PBKDF2 从主密码派生 256 位密钥
用 PBKDF2WithHmacSHA256 衍生主密码密钥,迭代次数设 ≥ 65536
用户输的主密码太弱,直接当密钥风险极高。必须用慢哈希拉长计算时间,提高暴力破解成本。Java 8+ 原生支持,但参数容易设错。
KeySpec spec = new PBEKeySpec(masterPassword.toCharArray(), salt, 65536, 256);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
byte[] keyBytes = factory.generateSecret(spec).getEncoded();
-
salt必须随机生成(SecureRandom),且每个用户独立存储,不可复用 - 迭代次数低于 10000 易被 GPU 破解;65536 是当前 JDK 默认值,够用但不冗余
- 别用
MD5或SHA1变种 —— 它们已被证明不安全
密码条目用 java.time 存创建/修改时间,别用 System.currentTimeMillis()
时间戳本身不涉密,但它是审计和自动清理依据。用 Instant 而非 long,避免时区混淆和毫秒精度丢失;序列化时统一转 ISO-8601 字符串(如 "2024-05-22T14:30:45.123Z")。
立即学习“Java免费学习笔记(深入)”;
- 数据库或 JSON 文件中存
Instant.toString(),读取时用Instant.parse() - 别用
Date—— 它可变、线程不安全、API 设计过时 - 如果要做「30 天未使用自动删除」,用
Instant.now().minus(Duration.ofDays(30))比较,别自己算毫秒差
主密码验证失败时,别泄露「用户名不存在」或「密码错误」细节
这是最常被忽略的安全盲点。攻击者可通过响应差异判断账户是否存在(例如 HTTP 401 vs 404),或通过响应时间差异判断密码是否部分正确(时序攻击)。必须让所有失败路径耗时一致。
- 先查用户记录(无论是否存在),再统一执行密钥派生和验证逻辑
- 验证失败后,仍调用一次
SecretKeyFactory.generateSecret()(哪怕用假 salt),填平时间差 - 返回统一错误消息:「主密码不正确」,绝不提「用户未找到」「密钥解密失败」等内部信息
真正难的不是写加密逻辑,而是确保 nonce 不重、salt 不混、SecretKey 不缓存、错误响应不泄密——这些点一旦漏掉一个,整个工具就形同裸奔。










