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

文章目录

  • 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 依然不安全

  // 多次 MD5String 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,看看哈希一次密码的耗时

import org.springframework.security.crypto.bcrypt.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

   import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;private static BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();@GetMapping("better")public UserData better(@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(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.mzph.cn/news/879529.shtml

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

相关文章

Golang | Leetcode Golang题解之第409题最长回文串

题目&#xff1a; 题解&#xff1a; func longestPalindrome(s string) int {mp : map[byte]int{}for i : 0; i < len(s); i {mp[s[i]]}res : 0for _, v : range mp {if v&1 1 {res v - 1} else {res v}}if res<len(s) {res}return res }

Python酷库之旅-第三方库Pandas(117)

目录 一、用法精讲 516、pandas.DataFrame.add_suffix方法 516-1、语法 516-2、参数 516-3、功能 516-4、返回值 516-5、说明 516-6、用法 516-6-1、数据准备 516-6-2、代码示例 516-6-3、结果输出 517、pandas.DataFrame.align方法 517-1、语法 517-2、参数 51…

Go语言基本语法

Go语言&#xff08;通常称为Golang&#xff09;是由Google开发的一种静态类型、编译型语言&#xff0c;它旨在简化系统编程、网络编程和并发编程的复杂性。 Go语言以其简洁、高效和易于理解的语法而受到开发者的喜爱。 Go语言的一些基本语法元素&#xff1a; 1. 包&#xff…

protobuf中c、c++、python使用

文章目录 protobuf实例&#xff1a;例题1&#xff1a;[CISCN 2023 初赛]StrangeTalkBot分析&#xff1a;思路&#xff1a;利用&#xff1a; 例题2&#xff1a;[CISCN 2024]protoverflow分析&#xff1a; protobuf Protocol Buffers&#xff0c;是Google公司开发的一种数据描述语…

数学学习记录

9月14日 1.映射&#xff1a; 2.函数: 9月15日 3.反函数&#xff1a; 4.收敛数列的性质 5.反三角函数&#xff1a; 9月16日 6.函数的极限&#xff1a; 7.无穷小和无穷大 极限运算法则&#xff1a;

远程Linux网络连接( Linux 网络操作系统 04)

接下来我们准备开始进入Linux操作系统的第二个模块的学习&#xff0c;不过在学习之前我们需要对如下进行简单的配置&#xff0c;通过外接辅助软件MobaXterm来进行虚拟操作系统的访问。接下来的课程我们会一直在MobaXterm中进行命令和相关知识的学习。 一、准备阶段 1.1 软件 …

学习笔记JVM篇(三)

一、垃圾回收机制 垃圾回收&#xff08;Garbage Collection&#xff09;机制&#xff0c;是自动回收无用对象从而释放内存的一种机制。Java之所以相对简单&#xff0c;很大程度是归功于垃圾回收机制。&#xff08;例如C语言申请内存后要手动的释放&#xff09; 优点&#xff…

数据清洗-缺失值填充-K-NN算法(K-Nearest Neighbors, K-NN算法)

目录 一、安装所需的python包二、采用K-NN算法进行缺失值填充2.1可直接运行代码2.2以某个缺失值数据进行实战2.2.1代码运行过程截屏&#xff1a;2.2.2填充后的数据截屏&#xff1a; 三、K 近邻算法 (K-Nearest Neighbors, KNN) 介绍3.1 K 近邻算法定义3.2 K 近邻算法的基本思想…

福建科立讯通信 指挥调度管理平台 SQL注入漏洞

北峰通信-福建科立讯通信 指挥调度管理平台 SQL注入漏洞 厂商域名和信息收集 域名&#xff1a; 工具sqlmap python sqlmap.py -u "http://ip:端口/api/client/down_file.php?uuid1" --batch 数据包 GET /api/client/down_file.php?uuid1%27%20AND%20(SELECT%20…

替换 Oracle ,江河信息用 TDengine 解决高基数查询写入问题

在数字经济快速发展的背景下&#xff0c;智慧水利作为重要的基础设施之一&#xff0c;正逐步成为提升水资源管理效率、优化生态环境的重要力量。江西省水投江河信息技术有限公司&#xff08;以下简称“江河信息”&#xff09;作为高新技术国有企业&#xff0c;坚定致力于打造数…

Leetcode 每日一题:Longest Increasing Path in a Matrix

写在前面&#xff1a; 今天我们继续看一道 图论和遍历 相关的题目。这道题目的背景是在一个矩阵当中找寻最长的递增数列长度。思路上非常好想&#xff0c;绝对和 DFS 相关&#xff0c;但是题目的优化要求非常高&#xff0c;对于语言和内存特性的考察特别丰富&#xff0c;如果是…

15. Springboot集成Redis

目录 1、前言 2、为什么选择Spring Boot集成Redis&#xff1f; 3、快速上手 3.1、引入依赖 3.2、 配置连接信息 3.3、自定义配置类 4、RedisTemplate的使用 4.1、String类型操作 4.2、 Hash类型操作 4.3、List类型操作 4.4、Set类型操作 4.5、SortedSet类型操作 4…

第十一章 【后端】商品分类管理微服务(11.2)——Lombok

11.2 Lombok 官网:https://projectlombok.org/ 较新版本的 idea 已默认安装 lombok 插件 Lombok 工具提供一系列的注解,使用这些注解可以不用定义 getter、setter、equals、constructor 等,可以消除 java 代码的臃肿,编译时它会在字节码文件中自动生成这些通用的方法,简…

ElK 8 收集 Nginx 日志

1. 说明 elk 版本&#xff1a;8.15.0 2. 启个 nginx 有 nginx 可以直接使用。我这里是在之前环境下 docker-compose.yml 中启动了个 nginx&#xff1a; nginx:restart: alwaysimage: nginx:1.26.1ports:- "80:80"- "443:443"volumes:#- ./nginx/html:/…

【题解】—— LeetCode一周小结37

&#x1f31f;欢迎来到 我的博客 —— 探索技术的无限可能&#xff01; &#x1f31f;博客的简介&#xff08;文章目录&#xff09; 【题解】—— 每日一道题目栏 上接&#xff1a;【题解】—— LeetCode一周小结36 9.合并零之间的节点 题目链接&#xff1a;2181. 合并零之间…

Unity实战案例全解析:PVZ 植物放置分析

前篇&#xff1a;Unity实战案例全解析&#xff1a;PVZ 植物卡片状态分析-CSDN博客 植物应该如何从卡牌状态转为实物&#xff1f; 其实就只需要考虑两个步骤加一个后续处理&#xff1a; 1.点击卡牌后就实例化 需要一个植物状态枚举&#xff0c;因为卡牌分为拿在手上和种植下…

CS61C 2020计算机组成原理Lecture01-数字表示,溢出

1. 原码 原码就是符号化的数值&#xff0c;其编码规则简单直观&#xff1a;正数符号位用0表示&#xff0c;负数符号位用1表示&#xff0c;数值位保持不变。 x0.1101&#xff0c;则[x]原0.1101&#xff1b;x1101&#xff0c;则[x]原01101x -0.1111&#xff0c;则[x]原1.1111&…

Oracle从入门到放弃

Oracle从入门到放弃 左连接和右连接Where子查询单行子查询多行子查询 from子句的子查询select子句的子查询oracle分页序列序列的应用 索引PL/SQL变量声明与赋值select into 赋值变量属性类型 异常循环游标存储函数存储过程不带传出参数的存储过程带传出参数的存储过程 左连接和…

opencv之Canny边缘检测

文章目录 前言1.应用高斯滤波去除图像噪声2.计算梯度3.非极大值抑制4.应用双阈值确定边缘5.Canny函数及使用 前言 Canny边缘检测是一种流行的边缘检测算法&#xff0c;用于检测图像中的边缘。它通过一系列步骤将图像中的像素边缘突出显示出来&#xff0c;主要分为以下几个步骤…

PCL 点云随机渲染颜色

目录 一、概述 1.1原理 1.2实现步骤 1.3 应用场景 二、代码实现 2.1关键函数 2.2完整代码 三、实现效果 PCL点云算法汇总及实战案例汇总的目录地址链接&#xff1a; PCL点云算法与项目实战案例汇总&#xff08;长期更新&#xff09; 一、概述 本文将介绍如何使用PCL库…