You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java中Base64结合哈希加盐实现密码加解密登录问题求助

问题分析与解决方案

看起来你在给密码添加盐值的过程中踩了几个关键的坑,导致存储的密码无法用于登录验证,下面逐一拆解问题并给出修复方案:

1. 加密目标完全错误:你没加密用户的密码

在encryDecryPasswordWithSalt方法里,你硬编码加密了固定字符串"M0993000353",而不是用户实际输入的密码secretKey(也就是systemUser.getPassword())。这直接导致数据库里存的根本不是用户密码的加密结果,登录验证自然匹配不上。

2. IV拼接逻辑错误

你的addIVToCipher方法中,第二个System.arraycopy的目标位置写错了:应该把密文放在IV之后(从ivCode.length的位置开始),而不是encrypted.length的位置。这个错误会导致IV和密文的拼接完全混乱,解密时根本无法正确提取IV。

3. 解密时IV处理逻辑错误

解密时必须使用加密时生成的同一个IV,而不是重新生成新的IV。你当前的代码在initCipher里每次都会生成新的IV,测试时能解密成功只是因为同一次请求中IV是同一个成员变量,但实际登录是不同请求,IV会重新生成,必然解密失败。

4. 密码存储的设计误区:用了可逆加密而非哈希加盐

密码的安全存储应该用不可逆的哈希加盐,而不是可逆加密。因为密码不需要被解密,登录时只需要将用户输入的密码加盐后哈希,再和数据库中存储的哈希值比对即可。你之前的SHA-256方案是正确的哈希思路,但改成PBE可逆加密完全不符合密码存储的安全规范。


推荐方案:哈希加盐的密码存储(符合安全规范)

如果你的需求是安全存储密码,强烈推荐使用PBKDF2+HMAC+SHA-256的哈希加盐方案,示例代码如下:

public class SystemUserDao { 
    @PersistenceContext 
    private EntityManager manager; 

    // 迭代次数:数值越高,破解难度越大,建议至少10000次
    private static final int ITERATIONS = 65536;
    // 哈希长度
    private static final int KEY_LENGTH = 256;
    // 盐的长度:建议至少16字节
    private static final int SALT_LENGTH = 16;

    public void save(SystemUser systemUser) { 
        hashPasswordWithSalt(systemUser); 
        manager.persist(systemUser); 
    } 

    // 哈希用户密码,将盐和哈希值拼接后存储
    private void hashPasswordWithSalt(SystemUser systemUser) { 
        String plainPassword = systemUser.getPassword();
        // 生成随机盐(每个用户独立盐)
        byte[] salt = generateRandomSalt();
        try {
            SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
            KeySpec spec = new PBEKeySpec(plainPassword.toCharArray(), salt, ITERATIONS, KEY_LENGTH);
            byte[] hash = factory.generateSecret(spec).getEncoded();
            
            // 拼接盐和哈希值,用Base64编码后存储
            byte[] saltAndHash = new byte[salt.length + hash.length];
            System.arraycopy(salt, 0, saltAndHash, 0, salt.length);
            System.arraycopy(hash, 0, saltAndHash, salt.length, hash.length);
            String encoded = Base64.encodeBase64String(saltAndHash);
            systemUser.setPassword(encoded);
        } catch (Exception e) { 
            throw new RuntimeException(e); 
        } 
    } 

    // 登录时验证密码的方法
    public boolean verifyPassword(String inputPassword, String storedPassword) {
        try {
            byte[] saltAndHash = Base64.decodeBase64(storedPassword);
            // 提取存储的盐
            byte[] salt = Arrays.copyOfRange(saltAndHash, 0, SALT_LENGTH);
            // 提取存储的哈希值
            byte[] storedHash = Arrays.copyOfRange(saltAndHash, SALT_LENGTH, saltAndHash.length);
            
            // 对输入密码做相同的哈希处理
            SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
            KeySpec spec = new PBEKeySpec(inputPassword.toCharArray(), salt, ITERATIONS, KEY_LENGTH);
            byte[] inputHash = factory.generateSecret(spec).getEncoded();
            
            // 用Arrays.equals避免时序攻击,安全比对哈希值
            return Arrays.equals(inputHash, storedHash);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    // 生成随机盐
    private byte[] generateRandomSalt() {
        SecureRandom random = new SecureRandom();
        byte[] salt = new byte[SALT_LENGTH];
        random.nextBytes(salt);
        return salt;
    }
}

如果你必须使用可逆加密(不推荐用于密码存储)

如果因为特殊需求必须用可逆加密,修复后的代码如下:

public class SystemUserDao { 
    @PersistenceContext 
    private EntityManager manager; 

    private static final String factoryInstance = "PBKDF2WithHmacSHA256";
    private static final String secretKeyType = "AES";
    private static final String cipherInstance = "AES/CBC/PKCS5Padding";

    public void save(SystemUser systemUser) { 
        encryptPasswordWithSalt(systemUser); 
        manager.persist(systemUser); 
    } 

    private void encryptPasswordWithSalt(SystemUser systemUser) { 
        String plainPassword = systemUser.getPassword();
        // 建议每个用户用独立随机盐,并把盐存在数据库
        String salt = "your-fixed-salt-or-generate-random"; 
        try { 
            String cipherText = encrypt(plainPassword, salt, plainPassword); 
            systemUser.setPassword(cipherText); 
        } catch (Exception e) { 
            throw new RuntimeException(e); 
        } 
    } 

    public static String encrypt(String secretKey, String salt, String value) throws Exception { 
        // 生成随机IV
        byte[] iv = new byte[16];
        SecureRandom random = new SecureRandom();
        random.nextBytes(iv);
        
        Cipher cipher = initCipher(secretKey, salt, Cipher.ENCRYPT_MODE, iv); 
        byte[] encrypted = cipher.doFinal(value.getBytes(StandardCharsets.UTF_8)); 
        
        // 正确拼接IV和密文
        byte[] cipherWithIv = new byte[iv.length + encrypted.length]; 
        System.arraycopy(iv, 0, cipherWithIv, 0, iv.length); 
        System.arraycopy(encrypted, 0, cipherWithIv, iv.length, encrypted.length); 
        return Base64.encodeBase64String(cipherWithIv); 
    } 

    public static String decrypt(String secretKey, String salt, String encrypted) throws Exception { 
        byte[] cipherWithIv = Base64.decodeBase64(encrypted); 
        // 提取加密时用的IV
        byte[] iv = Arrays.copyOfRange(cipherWithIv, 0, 16); 
        // 提取密文
        byte[] cipherBytes = Arrays.copyOfRange(cipherWithIv, 16, cipherWithIv.length); 
        
        Cipher cipher = initCipher(secretKey, salt, Cipher.DECRYPT_MODE, iv); 
        byte[] original = cipher.doFinal(cipherBytes); 
        return new String(original, StandardCharsets.UTF_8); 
    } 

    private static Cipher initCipher(String secretKey, String salt, int mode, byte[] iv) throws Exception { 
        SecretKeyFactory factory = SecretKeyFactory.getInstance(factoryInstance); 
        KeySpec spec = new PBEKeySpec(secretKey.toCharArray(), salt.getBytes(StandardCharsets.UTF_8), 65536, 256); 
        SecretKey tmp = factory.generateSecret(spec); 
        SecretKeySpec skeySpec = new SecretKeySpec(tmp.getEncoded(), secretKeyType); 
        Cipher cipher = Cipher.getInstance(cipherInstance); 
        cipher.init(mode, skeySpec, new IvParameterSpec(iv)); 
        return cipher; 
    } 
}

关键注意事项:

  • 用固定盐会降低安全性,推荐每个用户使用独立的随机盐,并将盐和加密/哈希结果一起存在数据库中。
  • 密码存储优先选择不可逆哈希加盐方案,可逆加密仅适用于必须恢复原始内容的场景(密码场景几乎不需要)。

内容的提问来源于stack exchange,提问作者Marcio

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 09:42:31