Java Android中PBKDF2密码哈希登录校验不匹配问题排查
问题根因与修复方案
核心逻辑错误
- 盐值未持久化,每次计算随机生成新值
你实现的PBKDF2_Encrypt方法,每次调用都会新生成一个独立的16字节随机盐,计算完哈希后没有留存盐值、也没有和哈希结果一起存储。
首次设置密码流程:生成随机盐A → 用盐A计算得到哈希H1 → 仅将H1存入SharedPreferences,盐A直接丢弃
登录校验流程:重新随机生成盐B → 用盐B计算输入密码的哈希H2 → 由于两次计算使用的盐完全不同,哪怕输入密码和设置时完全一致,H2永远不可能和存储的H1相等 - 二进制内容转字符串的编码方式错误
你直接将哈希得到的二进制字节数组按UTF-8编码转为字符串,大量二进制字节不属于合法UTF-8字符序列,转换过程中会出现替换字符、字节丢失,哪怕盐值完全一致,存储的字符串内容也会出现损坏,无法正确比对。 - 冗余的存储数据清理逻辑
FirstTimeActivity的onCreate方法中,硬编码了删除isFirstTime、PasswordHash两个存储字段的逻辑,只要页面被启动,已经写入的有效密码数据就会被直接清空,相当于每次打开首次设置页就会把之前存的所有密码相关数据擦掉。
正确实现规范
- 首次设置密码阶段
生成随机盐值后,必须将盐值和计算得到的哈希结果绑定后整体存储,不能只单独存哈希值。 - 登录校验阶段
从存储中读取之前绑定好的盐值与哈希结果,使用已存储的原有盐值,对用户输入的密码重新计算PBKDF2哈希,再将新算出的哈希和原有存储的哈希做比对,不能每次校验都新生成随机盐。 - 编码处理要求
所有二进制格式的盐、哈希内容,统一通过Base64编码转为可安全存储的字符串,避免不同字符集转换带来的内容损坏。
可直接复用的修复后工具方法
// 首次设置密码时调用:返回Base64编码的「盐+哈希」组合字符串,直接存入SharedPreferences即可 public static String generateHashWithSalt(String password) throws NoSuchAlgorithmException, InvalidKeySpecException { SecureRandom random = new SecureRandom(); byte[] salt = new byte[16]; random.nextBytes(salt); KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 128); SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); byte[] hash = factory.generateSecret(spec).getEncoded(); // 拼接盐和哈希为完整字节数组,整体做Base64编码 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); outputStream.write(salt, 0, salt.length); outputStream.write(hash, 0, hash.length); return Base64.encodeToString(outputStream.toByteArray(), Base64.NO_WRAP); } // 登录校验时调用:传入存储的组合字符串、用户输入的密码,返回校验是否通过 public static boolean verifyPassword(String storedCombination, String inputPassword) throws NoSuchAlgorithmException, InvalidKeySpecException { byte[] combinationBytes = Base64.decode(storedCombination, Base64.NO_WRAP); // 从存储的组合字节中拆分出原有盐和原始哈希值 byte[] salt = Arrays.copyOfRange(combinationBytes, 0, 16); byte[] originalHash = Arrays.copyOfRange(combinationBytes, 16, combinationBytes.length); KeySpec spec = new PBEKeySpec(inputPassword.toCharArray(), salt, 65536, 128); SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); byte[] inputHash = factory.generateSecret(spec).getEncoded(); // 使用固定时间比对,避免时序泄漏风险 return MessageDigest.isEqual(originalHash, inputHash); }
额外提醒:SharedPreferences存储敏感数据的安全风险你已知晓,这里不再重复提示。
内容的提问来源于stack exchange,提问作者Cpt Price
相关产品推荐
相关产品推荐

