如何用Java实现与FOSUserBundle一致的SHA512密码加密
实现Java版FOSUserBundle SHA512密码加密逻辑
首先得明确FOSUserBundle的SHA512加密底层是基于PHP的crypt()函数的CRYPT_SHA512算法,格式为$6$rounds=[迭代次数]$[salt]$[哈希值],其中:
- 迭代次数:你记录的配置值(默认FOS是5000,但以你的实际记录为准)
- Salt:仅包含
[a-zA-Z0-9./]的随机字符串 - 哈希值:经过指定迭代次数SHA-512运算后的结果,用Crypt自定义的Base64表编码
你之前尝试用org.apache.commons.codec.digest.Crypt没成功,大概率是没正确构造包含算法和迭代次数的salt参数。下面是完整的Java实现方案:
依赖准备
首先确保引入Apache Commons Codec库(Maven为例):
<dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.15</version> </dependency>
完整实现代码
import org.apache.commons.codec.digest.Crypt; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class FOSPasswordUtils { /** * 验证明文密码是否匹配FOSUserBundle生成的SHA512哈希 * @param rawPassword 明文密码 * @param fosEncodedHash FOS生成的哈希字符串 * @return 是否匹配 */ public static boolean verifyFOSPassword(String rawPassword, String fosEncodedHash) { // 校验哈希格式是否符合FOS SHA512标准 if (!fosEncodedHash.startsWith("$6$")) { throw new IllegalArgumentException("输入的哈希不是FOSUserBundle SHA512格式"); } // 分割哈希字符串提取参数:$6$rounds=N$salt$hash String[] hashParts = fosEncodedHash.split("\\$"); if (hashParts.length != 5) { throw new IllegalArgumentException("无效的FOS哈希格式"); } // 构造Crypt需要的salt参数:$6$rounds=N$salt String saltParam = String.format("$6$%s$%s", hashParts[2], hashParts[3]); // 生成校验用的哈希 String generatedHash = Crypt.crypt(rawPassword, saltParam); // 用常量时间比较防止时序攻击 try { return MessageDigest.isEqual( generatedHash.getBytes(StandardCharsets.UTF_8), fosEncodedHash.getBytes(StandardCharsets.UTF_8) ); } catch (NoSuchAlgorithmException e) { // 理论上不会发生,因为isEqual是标准方法 throw new RuntimeException("哈希比较失败", e); } } /** * 生成符合FOSUserBundle格式的SHA512哈希 * @param rawPassword 明文密码 * @param rounds 迭代次数(需和FOS配置一致) * @param salt 自定义salt(必须仅包含[a-zA-Z0-9./]) * @return FOS格式的哈希字符串 */ public static String encodeFOSPassword(String rawPassword, int rounds, String salt) { // 校验salt字符合法性 if (!salt.matches("[a-zA-Z0-9./]+")) { throw new IllegalArgumentException("salt只能包含[a-zA-Z0-9./]字符"); } String saltParam = String.format("$6$rounds=%d$%s", rounds, salt); return Crypt.crypt(rawPassword, saltParam); } /** * 生成随机salt并创建FOS格式哈希 * @param rawPassword 明文密码 * @param rounds 迭代次数 * @return FOS格式的哈希字符串 */ public static String encodeFOSPassword(String rawPassword, int rounds) { String randomSalt = generateRandomFOSSalt(16); // 生成16位符合要求的salt return encodeFOSPassword(rawPassword, rounds, randomSalt); } /** * 生成符合FOS要求的随机salt * @param length salt长度(建议16位) * @return 随机salt字符串 */ private static String generateRandomFOSSalt(int length) { String allowedChars = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; StringBuilder saltBuilder = new StringBuilder(length); for (int i = 0; i < length; i++) { int randomIndex = (int) (Math.random() * allowedChars.length()); saltBuilder.append(allowedChars.charAt(randomIndex)); } return saltBuilder.toString(); } // 测试示例 public static void main(String[] args) { // 替换为你的FOS生成的实际哈希 String fosHash = "$6$rounds=5000$XyZ123Abc456Def7$abcdefghijklmnopqrstuvwxyz1234567890..."; String rawPassword = "your-user-password"; // 验证密码 boolean isMatch = verifyFOSPassword(rawPassword, fosHash); System.out.println("密码匹配结果:" + isMatch); // 生成新的FOS格式哈希 String newFosHash = encodeFOSPassword(rawPassword, 5000); System.out.println("生成的FOS兼容哈希:" + newFosHash); } }
关键注意事项
- Salt参数构造:必须明确指定
$6$rounds=[你的迭代次数]$[salt]作为Crypt的salt参数,否则Commons Codec会使用默认算法/迭代次数,导致哈希不匹配。 - 字符集与合法性:Salt只能用
[a-zA-Z0-9./],否则PHP的crypt(以及Java的Crypt实现)会拒绝处理或生成错误哈希。 - 时序攻击防护:用
MessageDigest.isEqual做哈希比较,避免通过响应时间差异破解密码。 - 迭代次数一致性:必须使用你记录的FOS配置中的迭代次数,不能用默认值,否则哈希必然不匹配。
内容的提问来源于stack exchange,提问作者Zain Elabidine
相关产品推荐
相关产品推荐

