加密与安全_优雅存储用户密码的最佳实践

news2024/9/20 15:01:28

文章目录

  • Pre
  • 概述
  • 最佳实践
    • 避免使用MD5、SHA1等快速哈希算法
    • 加盐哈希 (不推荐)
    • 使用BCrypt、Argon2等慢哈希算法 (推荐)
      • BCrypt Code
        • 1. 自动生成和嵌入盐
        • 2. 哈希结果的格式
        • 3. 代价因子
      • BCrypt特点
    • 防止暴力破解
      • 1. 登录失败锁定
      • 2. 双因素认证(2FA)
      • 3. 图形验证码或短信验证
      • 4. 异常检测与应对
      • 5. 登录通知
      • 总结

在这里插入图片描述


Pre

MD5破解网站:https://www.cmd5.com/

在这里插入图片描述


计划

  1. 讨论密码保存的关键原则和最佳实践。
  2. 解析不应使用MD5单独保存密码的原因。
  3. 探讨加盐的必要性及注意事项。
  4. 推荐使用更安全的哈希算法,例如BCrypt,并解释其优点。
  5. 总结防止暴力破解和进一步保护用户账号的措施。

概述

在保存用户密码时,最重要的原则是不要以任何形式存储原始密码,而是存储经过哈希处理后的密码哈希值。传统的哈希算法如MD5虽然不可逆,但由于它们的速度快且容易构建彩虹表,这使得仅用它们来保存密码是不安全的。

最佳实践

避免使用MD5、SHA1等快速哈希算法

快速哈希算法,如MD5、SHA1等,因计算速度过快,容易被利用来构建彩虹表,导致密码被破解。因此,不建议使用这些算法来存储用户密码。

 import org.apache.commons.codec.digest.DigestUtils;

  @GetMapping("wrong1")
    public UserData wrong1(@RequestParam(value = "name", defaultValue = "artisan") String name, @RequestParam(value = "password", defaultValue = "Abcd1234") String password) {
        UserData userData = new UserData();
        userData.setId(1L);
        userData.setName(name);
        userData.setPassword(DigestUtils.md5Hex(password));
        return userRepository.save(userData);
    }
String password = "Abcd1234";
String s = DigestUtils.md5Hex(password);
System.out.println(s);

在这里插入图片描述

多次 MD5 依然不安全

  // 多次 MD5
  String s1 = DigestUtils.md5Hex(DigestUtils.md5Hex(password));
  System.out.println(s1);

在这里插入图片描述


加盐哈希 (不推荐)

加盐(Salt)是一种防御措施,通过在密码前后加入一个随机的字符串(盐值)来增加哈希的复杂度。正确的加盐方法如下:

  • 每个密码都应有一个唯一的盐值

  • 盐值应足够长和复杂,通常建议长度超过20位

      // 不能在代码中写死盐,且盐需要有一定的长度 ,如下是个错误的示例
      String s2 = DigestUtils.md5Hex("salt" + password);
       System.out.println(s2);
    

    在这里插入图片描述
    对于这样一串 MD5,虽然破解网站上找不到原始密码,但是黑客可以自己注册一个账号,
    使用一个简单的密码,比如 1 ,得到 55f312f84e7785aa1efa552acbf251db
    在这里插入图片描述
    然后,再去破解网站试一下这个 MD5,就可以得到原始密码是 salt,也就知道了盐值是salt

  • 盐值不应直接与用户的其他信息(如用户名)相关

     @GetMapping("wrong3")
        public UserData wrong3(@RequestParam(value = "name", defaultValue = "朱晔") String name, @RequestParam(value = "password", defaultValue = "Abcd1234") String password) {
            UserData userData = new UserData();
            userData.setId(1L);
            userData.setName(name);
            userData.setPassword(DigestUtils.md5Hex(name + password));
            return userRepository.save(userData);
        }
    

    如果世界上所有的系统都是按照这个方案来保存密码,那么 root、admin 这样的用户使再复杂的密码也总有一天会被破解,因为黑客们完全可以针对这些常用用户名来做彩虹表。

    所以,盐最好是随机的值,并且是全球唯一的,意味着全球不可能有现成的彩虹表给你用

  • 盐值和哈希后的密码应一同存储,盐值无需加密

    正确的做法是,使用全球唯一的、和用户无关的、足够长的随机值作为盐。比如,可以使用UUID 作为盐,把盐一起保存到数据库中

      @GetMapping("right")
        public UserData right(@RequestParam(value = "name", defaultValue = "朱晔") String name, @RequestParam(value = "password", defaultValue = "Abcd1234") String password) {
            UserData userData = new UserData();
            userData.setId(1L);
            userData.setName(name);
            userData.setSalt(UUID.randomUUID().toString());
            userData.setPassword(DigestUtils.md5Hex(userData.getSalt() + password));
            return userRepository.save(userData);
        }
    

    并且每次用户修改密码的时候都重新计算盐,重新保存新的密码。在我看来, 盐没有必要加密保存。盐的作用是,防止通过彩虹表快速实现密码“解密”,如果用户的盐都是唯一的,那么生成一次彩虹表只可能拿到一个用户的密码,这样黑客的动力会小很多。


使用BCrypt、Argon2等慢哈希算法 (推荐)

Spring Security 已经废弃了 MessageDigestPasswordEncoder,推荐使用BCryptPasswordEncoder

BCryptArgon2是为密码保存设计的慢哈希算法。这些算法通过引入计算成本(例如BCrypt的代价因子)使得暴力破解变得非常困难。

BCrypt 是为保存密码设计的算法,相比 MD5 要慢很多.

测试一下 MD5,以及使用不同代价因子的 BCrypt,看看哈希一次密码的耗时

 @GetMapping("performance")
 public void performance() {
     StopWatch stopWatch = new StopWatch();
     String password = "Abcd1234";
     stopWatch.start("MD5");
     DigestUtils.md5Hex(password);
     stopWatch.stop();
     stopWatch.start("BCrypt(10)");
     String hash1 = BCrypt.gensalt(10);
     BCrypt.hashpw(password, hash1);
     System.out.println(hash1);
     stopWatch.stop();
     stopWatch.start("BCrypt(12)");
     String hash2 = BCrypt.gensalt(12);
     BCrypt.hashpw(password, hash2);
     System.out.println(hash2);
     stopWatch.stop();
     stopWatch.start("BCrypt(14)");
     String hash3 = BCrypt.gensalt(14);
     BCrypt.hashpw(password, hash3);
     System.out.println(hash3);
     stopWatch.stop();
     log.info("{}", stopWatch.prettyPrint());
 }

在这里插入图片描述

MD5 的计算速度非常快(约 0.8 毫秒),这使得它容易被用于构建彩虹表或暴力破解密码。正因为它的速度快,攻击者可以在短时间内生成并查找大量的 MD5 哈希值,从而快速破解密码。

BCrypt 设计用于密码存储,考虑了安全性。它的计算速度相对较慢,特别是在设置较高代价因子(cost factor)的情况下。代价因子越高,BCrypt 的计算时间越长,这意味着攻击者需要花费更长时间才能生成彩虹表或进行暴力破解。例如:

  • 代价因子为 10 时,BCrypt 哈希耗时 82 毫秒。
  • 代价因子为 12 时,耗时增加到 312 毫秒。
  • 代价因子为 14 时,耗时更是达到了 1.2 秒。

这意味着,即使攻击者想要创建一个针对 BCrypt 的彩虹表,所需的时间和计算资源也大大增加。例如,若生成一个针对 8 位密码的 MD5 彩虹表需要 5 个月的时间,那么对 BCrypt 而言,这个时间可能会延长到几十年甚至更久。由于需要大量的计算资源和时间,大部分攻击者可能会选择放弃,转而寻找更容易的目标。

要估算生成一个针对 8 位密码的 MD5 彩虹表所需的时间,我们需要考虑以下因素:

  1. 密码空间大小:8 位密码的可能组合数。
  2. MD5 哈希的计算速度:每秒可以计算多少次 MD5 哈希。
  3. 总计算时间:生成彩虹表所需的总时间。
  4. 计算密码空间大小
    假设密码由 62 个字符组成(大写字母、小写字母和数字,共 26 + 26 + 10 = 62 个字符),那么8位密码的可能组合数为:[ 62^8 ]
    计算这个值:
    [ 62^8 = 218,340,105,584,896 \text{ (约为 218 万亿次计算)} ]
  5. 计算MD5哈希的速度
    根据数据,MD5 哈希的计算速度是 0.8 毫秒每次。那么,每秒的哈希计算次数为:
    [ \frac{1}{0.8 \text{ 毫秒}} = \frac{1}{0.0008 \text{ 秒}} = 1,250 \text{ 次/秒} ]
  6. 计算生成彩虹表的总时间
    生成彩虹表的总时间 = 密码空间大小 ÷ 每秒哈希计算次数:
    > [ \frac{218,340,105,584,896}{1,250} = 174,672,084,467 \text{ 秒} ]
    在这里插入图片描述

BCrypt Code

    private static BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

   @GetMapping("better")
    public UserData better(@RequestParam(value = "name", defaultValue = "朱晔") String name, @RequestParam(value = "password", defaultValue = "Abcd1234") String password) {
        UserData userData = new UserData();
        userData.setId(1L);
        userData.setName(name);
        userData.setPassword(passwordEncoder.encode(password));
        userRepository.save(userData);
        log.info("match ? {}", passwordEncoder.matches(password, userData.getPassword()));
        return userData;
    }
1. 自动生成和嵌入盐

BCrypt 的 encode 方法会自动生成一个随机盐,然后将这个盐与密码一起进行哈希计算。这个盐会作为哈希结果的一部分存储,因此在对密码进行校验时,BCrypt 可以直接从哈希结果中提取盐,重新计算哈希值并进行比对。这样做的好处是,你不需要手动生成和存储盐,也不需要在密码校验时额外传入盐。

2. 哈希结果的格式

BCrypt 的哈希结果格式是非常标准化的,结构如下:

$<ver>$<cost>$<salt><digest>
  • $:字段分隔符。
  • <ver>:算法版本,例如 2a 代表算法的版本。
  • <cost>:代价因子,控制哈希计算的复杂度。值越大,计算时间越长。
  • <salt>:生成的盐,通常为 22 个字符。
  • <digest>:最终的哈希值,也就是加盐后的密码哈希结果。

例如,$2a$10$wPWdQwfQO2lMxqSIb6iCROXv7lKnQq5XdMO96iCYCj7boK9pk6QPC 可以被解析为:

  • 版本2a
  • 代价因子10
  • wPWdQwfQO2lMxqSIb6iCRO
  • 哈希Xv7lKnQq5XdMO96iCYCj7boK9pk6QPC

由于盐是嵌入在哈希结果中的,因此无需单独存储盐,也不需要在校验时提供它。

3. 代价因子

代价因子(cost factor)控制了 BCrypt 哈希函数的计算复杂度。具体而言,代价因子的值越高,哈希计算所需的时间越长。这是 BCrypt 的一个重要特性,能够有效地防止暴力破解攻击。

  • 低代价因子:计算速度快,适用于对性能有要求的系统,但安全性相对较低。
  • 高代价因子:计算速度慢,提高了攻击者进行暴力破解的成本,适用于需要高安全性的场景。

实践建议

  • 默认值:通常设置为 10 是一个平衡的选择,既能保证足够的安全性,又不会影响用户体验。
  • 动态调整:随着硬件性能的提升,定期评估并增加代价因子值,以应对潜在的暴力破解威胁。

BCrypt特点

  • 内置加盐机制: BCrypt 把盐作为了算法的一部分,强制我们遵循安全保存密码的最佳实践。
  • 可调节的计算成本: 通过调整代价因子,可以增加哈希计算的时间,进一步增强密码的安全性。

BCrypt 通过引入计算时间和复杂的加盐机制,使得暴力破解和彩虹表攻击变得极其困难。相比之下,MD5 的速度和结构决定了它更容易被破解,因此不推荐用于密码存储。选择合适的代价因子(如 12 或 14)能在安全性和性能之间取得平衡,是保护用户密码的有效方式。


防止暴力破解

在密码存储和验证之外,配套的安全防御机制对于保护用户账户至关重要。以下是几种常见的安全防御措施及其作用:

1. 登录失败锁定

当用户多次输入错误的密码时,暂时锁定账号可以有效防止暴力破解攻击。通常的实现方式有:

  • 逐步延迟:在多次错误尝试后,每次登录尝试之间引入逐步增加的延迟时间。
  • 账号锁定:在一定次数的错误尝试后,暂时锁定账号一段时间(例如 5 分钟),或要求用户通过额外验证解锁(例如通过邮件或短信)。

这种机制可以大大增加攻击者暴力破解的难度,同时又不至于对用户造成过多的使用不便。

2. 双因素认证(2FA)

双因素认证要求用户在登录时,除了输入密码外,还需要提供另一个独立的认证信息(通常是一次性验证码),以进一步验证用户身份。常见的 2FA 方法包括:

  • 短信验证码:用户在输入密码后,会收到一条带有一次性验证码的短信,用户需在登录界面输入该验证码完成认证。
  • 认证应用:例如 Google Authenticator、Authy 等应用,会生成基于时间的动态验证码,用户需输入该验证码才能完成登录。
  • 硬件令牌:如 U2F 安全密钥,在登录时需要插入或接触物理设备以完成认证。

2FA 有效地增加了账户安全性,因为即使密码被泄露,攻击者仍需获得第二个认证因素才能登录。

3. 图形验证码或短信验证

图形验证码和短信验证可以防止自动化攻击,例如暴力破解工具或脚本批量尝试登录:

  • 图形验证码:在用户登录时,要求用户输入图片上显示的字符。这类验证码通过生成随机图像、扭曲字符等方式,阻止自动化脚本识别并输入正确的字符。
  • 短信验证:在检测到异常登录行为(例如来自不同地域的登录尝试)时,要求用户输入短信验证码以完成登录。

这种机制可以有效防止自动化攻击,提高系统的安全性。

4. 异常检测与应对

现代系统还可以通过分析用户行为,检测异常活动并采取措施:

  • 地理位置分析:如果用户在短时间内从两个远离的地点尝试登录,系统可以标记为异常行为,并要求额外的验证步骤。
  • 设备识别:如果用户从未使用过的设备或浏览器登录,系统可以要求进行额外验证,或者通知用户以防止潜在的账户劫持。

5. 登录通知

向用户发送登录通知(通过邮件或短信),告知他们账号的每次登录。这种做法可以让用户及时发现并报告未经授权的登录行为,从而迅速采取防御措施。

总结

除了使用安全的哈希算法外,配套的安全防御机制(如登录失败锁定、双因素认证、图形验证码、异常行为检测等)是构建健壮的用户认证系统的关键。这些机制共同作用,能显著降低账户被攻击的风险,保护用户数据的安全。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2123867.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MATLAB | 绘图复刻(十七) | 半小提琴图

嘿&#xff0c;真的是好久不见&#xff0c;最近有点过于忙了&#xff0c;今天更一个好久之前粉丝问的半小提琴图的绘制方法&#xff0c;要复刻这张图&#xff1a; 绘制效果如下&#xff1a; 还是挺好看的&#xff0c;下面直接进入正题&#xff1a; 教程部分 0 数据准备 这里…

Redis学习Day3——项目工程开发

扩展阅读推荐&#xff1a; 黑马程序员Redis入门到实战教程_哔哩哔哩_bilibili 一、项目介绍及其初始化 学习Redis的过程&#xff0c;我们还将遇到各种实际问题&#xff0c;例如缓存击穿、雪崩、热Key等问题&#xff0c;只有在实际的项目实践中解决这些问题&#xff0c;才能更好…

DPDI基础版安装部署说明

DispatchPDI下载 DPDI online部署包下载地址&#xff1a; Windows: http://files.pizzalord.site/api/public/dl/7Tnq6ScE/release/dpdi-community-win.zip Linux: http://files.pizzalord.site/api/public/dl/otCt9WuI/release/dpdi-community-linux.zip DPDI部署 DPDI应…

python构建深度学习模型开发数据采集利器,为模型提供充足图像数据

经常需要接触到各种各样的图像数据&#xff0c;为模型开发准备素材&#xff0c;在实际的项目中&#xff0c;一部分数据来源于真实的项目场景&#xff0c;但是这部分数据大都比较少&#xff0c;且获取的难度比较大&#xff0c;往往都是项目到了实施阶段的时候才有机会拿到数据&a…

基于SpringBoot的医院挂号预约管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于JavaSpringBootVueMySQL的医院挂号预约管理…

【有啥问啥】探索扫地机器人中的 SLAM 算法:原理、实现与未来展望

探索扫地机器人中的 SLAM 算法&#xff1a;原理、实现与未来展望 随着智能家居的普及&#xff0c;扫地机器人逐渐成为日常生活中的常见家电。其自主导航能力使得它能够在复杂的家庭环境中高效完成清洁任务&#xff0c;而这背后的核心技术之一就是 SLAM&#xff08;Simultaneou…

CCS12.2 以及以上版本如何使用C2000ware 4.03版本,发现直接导入工程不能正确识别地址变量?

C2000ware 5.02 地址变量为&#xff1a;COM_TI_C2000WARE_INSTALL_DIR&#xff0c;CCS12.2以上版本能够直接匹配识别&#xff01; 但是C2000ware4.03版本地址变量为&#xff1a;COM_TI_C2000WARE_SOFTWARE_PACKAGE_INSTALL_DIR&#xff0c;这个不能直接识别到头文件的地址&…

appium server gui详细按照步骤

1.安装appium server desktop Appium安装提供两种方式:桌面版和命令行版。其中桌面版又分为 Appium GuI 和 Appium Desktop 。作为初学者&#xff0c;用桌面版&#xff0c;对初学者比较友好。 官网下载地址&#xff1a;Releases appium/appium-desktop GitHubTags appium/…

基于FPGA与RK3588的多通道低延时3G-SDI视频信号

目录 简介 主要用途&#xff1a; 项目简介&#xff1a; 详细过程&#xff1a; BT1120视频时序转CEA861 视频像素编码格式转换 低延时处理 MIPI接口处理 视频处理模块 ​​​​​​​ 简介 主要解决 RK3588支持多种视频格式输入&#xff0c;但是没有支持多路SDI接口的…

【JUC】14-LongAddr源码分析

1. LongAddr底层实现过程 2. Striped64中变量或方法的定义 base&#xff1a;类似于AtomicLong中全局的value值。在没有竞争情况下数据直接累加到base上&#xff0c;或者cells扩容时&#xff0c;也需要将数据写入到base上。collide&#xff1a;表示扩容意向&#xff0c;false一…

反转链表 II

题目 给你单链表的头指针 head 和两个整数 left 和 right &#xff0c;其中 left < right 。请你反转从位置 left 到位置 right 的链表节点&#xff0c;返回 反转后的链表 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], left 2, right 4 输出&#xff1a;…

Java中的ArrayList类

继承实现关系 Arraylist就是一个可以动态扩容的容器&#xff0c;属于集合类的一种&#xff0c;要追根溯源的话它是间接实现了Collection接口&#xff0c;下面我画一下它的结构图 类定义 ArrayList类继承自抽象类AbstractList&#xff0c;同时实现了List和Collection接口&…

服务器数据恢复—通过拼接数据库碎片的方式恢复SQL Server数据库数据

服务器数据恢复环境&#xff1a; 一台服务器中有一组由4块STAT硬盘通过RAID卡组建的RAID10阵列&#xff0c;上层是XenServer虚拟化平台&#xff0c;虚拟机安装Windows Server操作系统&#xff0c;作为Web服务器使用。 服务器故障&#xff1a; 因机房异常断电导致服务器中一台V…

元学习之应用案例

现在在做元学习的时候&#xff0c;我们最常拿来测 试元学习技术的任务叫做少样本图像分类&#xff0c;简单来讲就是每一个任务都只有几张图片&#xff0c;每一 个类别只有几张图片。比如我们使用图1的案例为例说明。现在分类的任务是分为三个 类别&#xff0c;每个类别都只有两…

贪心-用最少的箭射球

一支弓箭可以沿着 x 轴从不同点完全垂直地射出。在坐标 x 处射出一支箭&#xff0c;若有一个气球的直径的开始和结束坐标为 xstart&#xff0c;xend&#xff0c; 且满足 xstart ≤ x ≤ xend&#xff0c;则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后…

MySQL从C盘迁移到D盘

文章目录 前言一、停止MySQL服务打开服务&#xff08;方式一&#xff09;打开服务&#xff08;方式二&#xff09;停止MySQL服务 二、找到C盘中的文件文件夹1文件夹2文件夹3 三、修改文件内容1.对应文件夹12.对应文件夹3 四、 修改注册表中文件路径1.打开注册表2. 修改注册表中…

微积分-积分应用5.5(函数的平均值)

很容易计算有限多个数字 y 1 , y 2 , … , y n y_1, y_2, \dots, y_n y1​,y2​,…,yn​ 的平均值&#xff1a; y ave y 1 y 2 ⋯ y n n y_{\text{ave}} \frac{y_1 y_2 \cdots y_n}{n} yave​ny1​y2​⋯yn​​ 但是&#xff0c;如果可以进行无限多次的温度读取&…

Java 入门指南:Java 并发编程 —— 同步工具类 CountDownLatch(倒计时门闩)

文章目录 同步工具类CountDownLatch常用方法使用步骤适用场景使用示例 同步工具类 JUC&#xff08;Java.util.concurrent&#xff09;是 Java 提供的用于并发编程的工具类库&#xff0c;其中包含了一些通信工具类&#xff0c;用于在多个线程之间进行协调和通信&#xff0c;特别…

【kafka】kafka如何保证数据的可靠性,kafka如何保证数据不丢失

1. Kafka架构&#xff1a; Producer - Broker - Consumer 回到问题上来&#xff0c;Kafka如何保证数据不丢失&#xff0c;我们先看看Kafka如何保证Producer端数据不丢失&#xff1a; 通过ack机制 最小副本数设置 生产者重试机制 2. Kafka Producer消息发送ACK机制&#xff1…

量化交易backtrader实践(一)_数据获取篇(2)_tushare与akshare

上一节回顾 在上一节中&#xff0c;从股票的基本功能和主要数据进行小结&#xff0c;明确了进行backtrader回测所需要的数据&#xff0c;并且学习了backtrader的数据来源以及PandasData的格式要求&#xff0c;已经做到假设拿到.txt或.csv文件后&#xff0c;能把里面的股票基本…