在本地工程中,需要存储很多账号密码:mysql、redis、es等等;只是用明文存储账号密码,有一定的风险,这个时候就需要一种策略来对本地密码进行加密,确保不是明文。

本文是对BCrypt、Argon2、PBKDF2 安全地存储和验证密码的讲解,希望对大家有所帮助。

密码密钥派生算法

密码通常是以加密的形式存储在服务器上,这样即使数据泄露也能最大限度地保护用户的隐私安全。这意味着服务器并不知道你原始的密码是什么。

因此,当你忘记密码时,系统无法告诉你密码是多少,只能通过验证你的身份信息后重置密码。

密钥派生函数(Key Derivation Function)就是从一个密码产生出一个或多个密钥,具体就是从一个master key,password或者passphrase派生出一个或多个密钥,派生的过程使用PRF(Pseudo Random Function)。是一种实现key stretching(密钥延长算法,即一种更慢的哈希算法,用于将初始密钥转换成增强密钥,在计算过程中刻意延长时间或者消耗空间,这样有利于保护弱密码)的方法。

●最简单的KDF可以直接使用某种密码学散列算法,如:SHA256。将一个密码转换为一个散列值作为密钥。但易受字典攻击。

●HKDF(HMAC-based key derivation),基于HMAC的密钥派生算法

●从一个密码派生出一个或多个密钥:PBKDF2、Bcrypt、Scrypt、Argon2 

密码加密策略

慢哈希算法阶段 - Argon2、Bcrypt、Scrypt和PBKDF2

Argon2[2]、Bcrypt[3]、Scrypt[4]和PBKDF2[5]是目前主流的慢哈希算法,它们与SHA256等快速哈希算法的主要差异点如下:

  1. 计算速度更慢,需要消耗更多CPU和内存资源,从而对抗硬件加速攻击;
  2. 使用更复杂的算法,组合密码学原语,增加破解难度;
  3. 可以配置资源消耗参数,调整安全强度;
  4. 特定优化使并行计算困难;
  5. 经过长时间的密码学分析,仍然安全可靠。

1:BCrypt

  • 定义:BCrypt 是一种基于Blowfish加密算法的密码散列函数,专为密码存储设计。

  • 优点:自动包含salt,且可调整迭代次数以适应未来的硬件进步。

  • 缺点:计算密集型,可能会占用较多服务器资源。

Java示例

import org.mindrot.jbcrypt.BCrypt;

public class BcryptExample {
    public static void main(String[] args) {
        String password = "mySecurePassword";
        String hashedPassword = BCrypt.hashpw(password, BCrypt.gensalt());

        System.out.println("Original Password: " + password);
        System.out.println("Hashed Password: " + hashedPassword);

        // 检查输入的密码是否正确
        boolean matches = BCrypt.checkpw(password, hashedPassword);
        System.out.println("Does the password match? " + matches);
    }
}

2:Argon2

  • 定义:Argon2 是一种现代的密码散列函数,获得了2015年的密码散列竞赛冠军。

  • 优点:支持内存和CPU消耗的调整,对GPU攻击有很好的防御能力。

  • 缺点:实现较为复杂,资源消耗较高。

Java示例

import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple;
import org.apache.commons.text.StringEscapeUtils;
import org.argon2.Argon2;
import org.argon2.Argon2Factory;
import org.argon2.exceptions.VerificationException;

public class Argon2Example {
    public static void main(String[] args) {
        String password = "mySecurePassword";
        Argon2 argon2 = Argon2Factory.create(Argon2Factory.Argon2Types.ARGON2id);
        String hashedPassword = argon2.hash(1, 1024 * 1024, 1, password.toCharArray());

        System.out.println("Original Password: " + password);
        System.out.println("Hashed Password: " + hashedPassword);

        // 检查输入的密码是否正确
        boolean matches = argon2.verify(hashedPassword, password.toCharArray());
        System.out.println("Does the password match? " + matches);
    }
}

3:PBKDF2(Password-Based Key Derivation Function 2)

  • 定义:PBKDF2 是一种使用HMAC-SHA256作为PRF的密码散列函数。

  • 优点:支持可配置的迭代次数,可以在未来增加计算成本以提高安全性。

  • 缺点:不如BCrypt和Argon2安全,特别是在面对GPU攻击时。

Java示例

import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;

public class PBKDF2Example {
    private static final int ITERATIONS = 10000; // Number of iterations
    private static final int KEY_LENGTH = 256;   // Key length in bits

    public static void main(String[] args) {
        String password = "mySecurePassword";
        byte[] salt = generateSalt();

        String hashedPassword = pbkdf2(password, salt);

        System.out.println("Original Password: " + password);
        System.out.println("Hashed Password: " + hashedPassword);
    }

    private static byte[] generateSalt() {
        SecureRandom random = new SecureRandom();
        byte[] salt = new byte[16]; // Salt length in bytes
        random.nextBytes(salt);
        return salt;
    }

    private static String pbkdf2(String password, byte[] salt) {
        try {
            SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
            PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, ITERATIONS, KEY_LENGTH);
            byte[] hash = skf.generateSecret(spec).getEncoded();
            StringBuilder hexString = new StringBuilder();
            for (byte b : hash) {
                String hex = Integer.toHexString(0xff & b);
                if(hex.length() == 1) hexString.append('0');
                hexString.append(hex);
            }
            return hexString.toString();
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            e.printStackTrace();
            return null;
        }
    }
}

小结

通过以上介绍,我们可以看出,为了保证用户的密码安全,系统只能采用重置密码的方式来解决忘记密码的问题。

同时,合理的密码加密策略也是必不可少的,它可以大大增强系统的安全性。

BCrypt 和 Argon2 是目前最推荐使用的密码散列函数,它们的安全性和性能都非常出色。

1、PBKDF2(CPU-Hard algorithm)

是一种基于密钥派生出密钥的算法,需要消耗很多算力,可防止暴力破解加密。

passphrase -> [dklen, salt, c] > 1000] -> hash

DK = PBKDF2(PRF, Password, Salt, c, dkLen)
其中,

passphrase:用于用户认证或者加密程序的操作步骤

dklen:派生所产生的密钥的长度

salt:盐值是一串随机生成的比特

c:迭代的次数

DK:期望的密钥derived key

PRF(Pseudorandom function):伪随机数产生的密钥,如:hmac-sha256

2、Scrypt(Memory-Hard algorithm)

是一种password-base KDF算法,比起PBKDF2需要消耗更多的资源。Scrypt内部用的是PBKDF2算法,不过内部会长时间的维护一组比特数据,这些数据会在生成复杂的salt的过程中反复加密。

3、区别

总结:

  1. PBKDF2是算力消耗性的,Scrypt是资源消耗性的。
  2. PBKDF2(过时)<Bcrypt(开始淘汰)<Scrypt< Argon2(含Argon2d、Argon2i、Argon2id)

PBKDF2和Scrypt都是密钥派生函数(KDFs),它们通过故意减慢计算速度来实现密钥延长,特别是通过用一个可调参数来控制速度。不同之处在于,Scryp需要大量且可调整的内存量才能进行高效计算。这样能防止专有硬件ASIC/FPGA的暴力破解。

更多推荐