现代安全密码哈希算法

在过去,MD5常常用于密码存储和加密,但由于其被发现容易受到碰撞攻击(即不同的输入可能产生相同的哈希值),现在已经不被推荐用于密码加密。当前,更加安全的密码存储方式使用了更为复杂和安全的哈希算法,并且通常结合了盐(salt)迭代加密的技术来增强安全性。

目前最常用的安全密码哈希算法包括:

1. bcrypt

  • 适用场景:非常适合大多数现代应用,尤其是对性能要求不是特别高的场景。
  • 特点
    • 简单易用:bcrypt 提供了非常简洁的 API,易于集成。
    • 自动盐生成:bcrypt 自动为每个哈希值生成一个盐(salt),确保相同的密码生成不同的哈希。
    • 多次加密:通过设置迭代次数来增加密码哈希的计算复杂度,从而提高安全性。
  • 优点
    • 经过多次加密(可调整的迭代次数)使其在面对暴力破解时具有较强的防御能力。
    • 适合大多数中小型应用,性能开销相对较低。
  • 缺点
    • 相较于 Argon2,它对内存的消耗较少,因此对于基于定制硬件(如 FPGA 或 GPU)进行的大规模并行暴力破解,防护能力相对较弱。
  • 常见应用 :很多企业和开源项目广泛使用,像是现代的 web 框架和系统(如 Node.js 的 bcrypt 包、Python 的 bcrypt 库等)都有支持。
Maven 依赖
复制代码
<dependency>
    <groupId>org.mindrot</groupId>
    <artifactId>jbcrypt</artifactId>
    <version>0.4</version>
</dependency>
Java 代码实现
复制代码
import org.mindrot.jbcrypt.BCrypt;

public class BCryptExample {

    public static void main(String[] args) {
        String password = "SecurePassword12345678";

        // 生成盐并对密码进行加密
        String hashedPassword = BCrypt.hashpw(password, BCrypt.gensalt(12)); // 12 是工作因子,越大越慢
        System.out.println("Hashed password: " + hashedPassword);

        // 验证密码
        boolean isPasswordCorrect1 = BCrypt.checkpw(password, hashedPassword);
        System.out.println("Password match 1: " + isPasswordCorrect1); // true

        boolean isPasswordCorrect2 = BCrypt.checkpw("WrongPassword", hashedPassword);
        System.out.println("Password match 2: " + isPasswordCorrect2); // false
    }
}
解释:
  • BCrypt.gensalt() 生成一个包含盐的加密哈希。
  • BCrypt.checkpw() 用来验证用户输入的密码是否与存储的哈希密码匹配。

2. Argon2

  • 适用场景:非常适合对安全性要求极高的场景,尤其是需要防止大规模硬件破解(如 GPU、FPGA)的应用。
  • 特点
    • 内存密集型:Argon2 允许通过配置内存消耗来增加密码哈希计算的复杂度,能够有效防止使用高并行度的硬件进行攻击。
    • 可配置性:可以配置迭代次数、内存消耗和并行度,灵活调整安全性与性能之间的平衡。
    • 最新标准:Argon2 是目前密码学界推荐的最安全的哈希算法之一,具有更强的抵御攻击的能力。它在 2015 年获得了密码哈希大赛的冠军。
  • 优点
    • 高内存消耗使其对 GPU 和专用硬件的抗性更强。
    • 可调的内存、迭代次数和并行度让用户可以根据需求选择最合适的配置。
  • 缺点
    • 相较于 bcrypt,其性能稍差,尤其是在低端硬件上,内存和计算开销较大。
  • 常见应用:越来越多的新系统开始采用 Argon2,尤其是在需要极高安全性的场景下(例如密码管理器、银行系统等)。
Maven 依赖
复制代码
<dependency>
    <groupId>de.mkammerer</groupId>
    <artifactId>argon2-jvm</artifactId>
    <version>2.11</version>
</dependency>
Java 代码实现
复制代码
import de.mkammerer.argon2.Argon2;
import de.mkammerer.argon2.Argon2Factory;

public class Argon2Example {

    public static void main(String[] args) {
        String password = "SecurePassword12345678";

        // 创建 Argon2 实例
        Argon2 argon2 = Argon2Factory.create();

        // 哈希密码
        char[] passwordChars = password.toCharArray();
        String hashedPassword = argon2.hash(2, 65536, 1, passwordChars); // 2是迭代次数,65536是内存使用,1是并发度
        System.out.println("Hashed password: " + hashedPassword);

        // 验证密码
        boolean isPasswordCorrect1 = argon2.verify(hashedPassword, passwordChars);
        System.out.println("Password match 1: " + isPasswordCorrect1); // true

        boolean isPasswordCorrect2 = argon2.verify(hashedPassword, "WrongPassword".toCharArray());
        System.out.println("Password match 2: " + isPasswordCorrect2); // false

        // 清除密码
        argon2.wipeArray(passwordChars);
    }
}
解释:
  • argon2.hash() 生成带盐的加密哈希,参数中包括迭代次数、内存使用量和并发度。
  • argon2.verify() 验证密码是否与生成的哈希匹配。

3. PBKDF2

  • 适用场景:适合需要兼容性和合规性的系统,尤其是要求符合如 FIPS(美国联邦信息处理标准)等标准的场景。
  • 特点
    • 广泛支持:PBKDF2 被广泛支持,包括 Java 标准库和许多操作系统和库中。它通常用于需要兼容性和标准化的系统中。
    • 灵活性:支持迭代次数、密钥长度等配置选项,用户可以根据需求调整。
  • 优点
    • 由于其标准化的广泛支持,适合要求兼容性的应用,尤其是在合规性要求较高的行业中。
    • 可以与现有系统兼容,使用简单且在许多语言和平台中都有实现。
  • 缺点
    • 比较简单,且主要基于 CPU 计算,不如 Argon2 在防御硬件破解(例如 GPU 和 FPGA)方面强大。
    • 相较于 Argon2,它的内存使用和并行性支持较弱,虽然能通过迭代次数来增加安全性,但对于某些攻击场景(如大规模并行暴力破解),其防御能力较为有限。
  • 常见应用:PBKDF2 在一些企业级系统和较老的系统中仍然被使用。
PBKDF2 是 Java 标准库中内置支持的,你可以直接使用 javax.crypto 来实现。
Java 代码实现
复制代码
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.SecureRandom;
import java.util.Base64;

public class PBKDF2Example {

    // 模拟数据库存储的盐和哈希密码
    private static String storedSaltStr;        // 存储的盐(Base64编码)
    private static String storedHashedPassword; // 存储的哈希密码

    // 生成盐
    public static byte[] generateSalt() {
        SecureRandom random = new SecureRandom();
        byte[] salt = new byte[16];  // 16字节盐
        random.nextBytes(salt);
        return salt;
    }

    // 使用PBKDF2加密密码
    public static String hashPassword(String password, byte[] salt) throws Exception {
        int iterations = 10000;  // 迭代次数
        int keyLength = 256;     // 哈希输出长度
        PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iterations, keyLength);
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        byte[] hash = factory.generateSecret(spec).getEncoded();
        return Base64.getEncoder().encodeToString(hash);
    }

    // 保存密码和盐到"数据库"
    public static void storePassword(String password) throws Exception {
        byte[] salt = generateSalt();  // 生成盐
        storedSaltStr = Base64.getEncoder().encodeToString(salt);  // 盐以Base64编码存储
        storedHashedPassword = hashPassword(password, salt);  // 哈希密码存储
    }

    // 验证密码
    public static boolean verifyPassword(String password, String storedSaltStr, String storedHashedPassword) throws Exception {
        byte[] salt = Base64.getDecoder().decode(storedSaltStr);  // 从存储中提取盐
        String hashedInputPassword = hashPassword(password, salt);  // 使用相同盐和算法生成哈希值
        return hashedInputPassword.equals(storedHashedPassword);  // 比较哈希值
    }

    public static void main(String[] args) throws Exception {
        // 1. 模拟存储密码到数据库
        String password = "SecurePassword12345678";
        storePassword(password);  // 存储盐和哈希密码到全局变量(模拟数据库)

        System.out.println("Salt (Base64): " + storedSaltStr);
        System.out.println("Hashed Password: " + storedHashedPassword);

        // 2. 验证密码
        String inputPassword = "SecurePassword12345678";  // 用户输入的密码
        boolean isPasswordCorrect = verifyPassword(inputPassword, storedSaltStr, storedHashedPassword);
        System.out.println("Password is correct: " + isPasswordCorrect);  // true

        // 尝试使用错误密码验证
        String incorrectPassword = "WrongPassword";
        boolean isIncorrectPasswordCorrect = verifyPassword(incorrectPassword, storedSaltStr, storedHashedPassword);
        System.out.println("Incorrect password is correct: " + isIncorrectPasswordCorrect);  // false
    }
}
解释:
  • storedSaltStrstoredHashedPassword这两个全局变量来模拟数据库中存储的盐和哈希密码。在实际应用中,这些数据会存储在数据库中。
  • storePassword()方法生成一个随机盐,并使用PBKDF2算法将密码哈希。然后将盐(Base64编码)和哈希密码保存到全局变量中。
  • verifyPassword()方法从存储的盐(Base64解码)和哈希密码开始,重新计算输入密码的哈希值并与存储的哈希密码进行比较。

企业通常会选择 bcrypt、Argon2 或 PBKDF2 等算法来存储用户密码。安全性要求较高的企业倾向于使用 Argon2 或 bcrypt,因为它们具有防止硬件加速暴力破解攻击的能力。此外,企业还会结合 多方面认证来进一步提高安全性。

总结:

  • 对于大多数现代应用 ,如果不需要对硬件攻击进行特别强的防护,且注重简单易用,bcrypt 是一个不错的选择。它已经足够强大,能够抵御大部分常规的攻击,且具有广泛的支持。经过多年的广泛应用,已被认为非常安全。

  • 对于高安全性要求的应用 ,特别是需要防止并行硬件破解的情况,Argon2 是目前推荐的最安全的选择。它能够提供更强的防护,尤其是在针对高并发硬件的攻击时。

  • 对于需要兼容性和合规性的应用PBKDF2 可能是最适合的选择,特别是在要求符合某些法规(如 FIPS)的系统中。它已经是一个成熟且广泛支持的标准,但其安全性不如 Argon2 强。

其他安全思考:

盐的长度足够且随机性好,生成相同盐的概率极低,盐碰撞且密码相同的联合事件的发生概率在实际应用中几乎为零,是一个非常低的风险。上面的加密算法通常使用了强随机数生成器,几乎没有重复盐的风险。即使数据库中多个用户有相同的哈希密码也没什么,且有重复的微乎其微。

相关推荐
跟着珅聪学java27 分钟前
spring boot +Elment UI 上传文件教程
java·spring boot·后端·ui·elementui·vue
我命由我1234532 分钟前
Spring Boot 自定义日志打印(日志级别、logback-spring.xml 文件、自定义日志打印解读)
java·开发语言·jvm·spring boot·spring·java-ee·logback
lilye6633 分钟前
程序化广告行业(55/89):DMP与DSP对接及数据统计原理剖析
java·服务器·前端
ACRELKY4 小时前
【黑科技护航安全】分布式光纤测温:让隐患无处可藏
科技·安全
叠叠乐4 小时前
rust Send Sync 以及对象安全和对象不安全
开发语言·安全·rust
想跑步的小弱鸡4 小时前
Leetcode hot 100(day 3)
算法·leetcode·职场和发展
战族狼魂4 小时前
CSGO 皮肤交易平台后端 (Spring Boot) 代码结构与示例
java·spring boot·后端
zhu12893035565 小时前
网络安全的现状与防护措施
网络·安全·web安全
澳鹏Appen5 小时前
AI安全:构建负责任且可靠的系统
人工智能·安全
xyliiiiiL5 小时前
ZGC初步了解
java·jvm·算法