目录

哈希加密,对称加密,非对称加密三者区别:

MD5

bcrypt加密

问题:bcrypt,如果用一个字符串加密两次,得到的加密字符串相同吗?

为什么“慢哈希”能阻止攻击者?

加盐

成本因子

彩虹表


B站:MD5是什么?它又是如何计算的 一条视频讲清楚

稀土掘金:面试官:为什么忘记密码要重置而不是告诉你原密码?

JavaGuide:常见加密算法总结

PS:本文对上面的学习资料进行补充,请务必要学习上面的资料。

关于密码加密相关的关键词有:

哈希加密(包含加密哈希算法、非加密哈希算法,慢哈希加密是哈希加密的一种特殊设计)、对称加密,非对称加密、加盐、成本因子、碰撞、撞库、彩虹表

哈希加密,对称加密,非对称加密三者区别:

哈希用于密码存储和完整性校验,不涉及密钥,哈希算法是单向的,不可逆,只能加密,不能解密,因此数据库中存储的是单向转换后的密码,在进行用户身份验证时需要将用户输入的密码进行单向转换,然后与数据库的密码进行比较;

对称加密(如AES)用同一密钥加密/解密,适合大数据量处理;

非对称加密(如RSA)用公钥/私钥对,解决密钥分发问题。

实际应用中,对称加密和非对称加密常结合使用。例如HTTPS先用RSA交换AES密钥,再用AES加密通信数据。

三者的区别是:哈希用于存储,对称/非对称加密既可用于传输也可用于存储,但非对称加密因性能问题通常不直接加密大数据。

MD5

MD5是不可逆的,从哈希值反向计算出原始输入是不可行的。之所以MD5不安全是因为:
  • 彩虹表攻击:可以撞库
  • 碰撞攻击:MD5已被证明可人为制造碰撞(两个不同输入产生相同哈希)
  • 速度过快:现代硬件可每秒计算数十亿次MD5哈希
  • 无盐值:直接MD5容易受到彩虹表攻击
使用场景:
  • 密码加密
  • 文件的完整性校验:在传输一个文件的时候,在发送端计算一次文件的MD5,并把这个结果发送给接收端,接收端在文件传输后再根据传输过来的文件计算一次MD5,然后比较这两个MD5是否相同,如果相同,那么说明文件没有缺失或者被修改。
  • 数字签名:比如发布了一个程序,为了防止程序被植入木马,在发布程序的时候,同时发布这个程序的MD5,别人在下载程序的时候,计算一下MD5然后比较一下。
  • 云盘秒传:有时候上传一个很大的文件几乎是秒传,并不是真的把文件上传了,而是计算一下文件的MD5,并在自己的数据库中搜索一下这个MD5是否存在,如果存在就不用上传了,直接使用已经存在的文件就行了,从而实现了文件秒传。

bcrypt加密

bcrypt 是一种慢哈希算法(Slow Hashing Algorithm),并且也是加盐哈希算法,其主要目的是为了密码存储安全。
下面代码使用的是 bcrypt 加密算法,这是密码哈希的一种安全算法。bcrypt 主要用于安全地存储密码,并且具有以下特点:
  1. 不可逆加密:bcrypt 是一种哈希算法,不是对称或非对称加密,生成的哈希值是不可逆的。这意味着你无法通过哈希值还原出原始密码。
  2. 抗暴力破解:bcrypt 设计时考虑了抗暴力破解攻击,它通过引工作因子(Cost Factor) 控制算法运行的时间复杂度,增加了暴力破解的难度。bcrypt.DefaultCost 是默认的工作因子,通常是 10。
  3. 哈希加盐:bcrypt 内部自动对每个密码进行 加盐(salt),也就是在密码哈希前,添加一个随机生成的值。这避免了彩虹表攻击,因为同一个密码每次加密的结果都会不同。
package main

import (
    "fmt"
    "golang.org/x/crypto/bcrypt"
)

// bcrypt.DefaultCost 是一个默认的计算成本参数,可以影响加密和解密的速度,通常值为 10。你可以根据需要调整这个值,越高的值会增加安全性但也会降低速度。
// 加密后的密码是一个字符串,可以直接存储在数据库中。
// 验证密码时,只需将用户输入的密码与数据库中存储的哈希密码进行比较。

// HashPassword 加密密码。使用 bcrypt.GenerateFromPassword() 方法来加密密码。
func HashPassword(password string) (string, error) {
    hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
    if err != nil {
       return "", err
    }
    return string(hashedPassword), nil
}

// CheckPasswordHash 验证密码。使用 bcrypt.CompareHashAndPassword() 方法来验证输入的密码是否正确。
func CheckPasswordHash(password, hashedPassword string) bool {
    err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
    return err == nil
}

func main() {
    // 用户输入的密码
    inputPassword := "Ab123456"
    encryptedPassword, _ := HashPassword(inputPassword)
    fmt.Println("加密密码为", encryptedPassword)

    p := "Ab123456" // 用户再次输入的密码
    // 比较用户输入的密码和数据库中的哈希密码
    if CheckPasswordHash(p, encryptedPassword) {
       fmt.Println("密码正确") // 密码正确
    } else {
       fmt.Println("密码错误")
    }

    if CheckPasswordHash(p, inputPassword) {
       fmt.Println("密码正确")
    } else {
       fmt.Println("密码错误") // 密码错误
    }
}

问题:bcrypt,如果用一个字符串加密两次,得到的加密字符串相同吗?

不相同。因为bcrypt 在加密过程中每次都会生成一个随机的盐值(salt)。即使是相同的密码,生成的哈希值也会不同,因为盐值不同。即使是相同的输入,输出的哈希值也是唯一的。所以,使用 bcrypt 对同一个密码进行两次加密,得到的加密字符串是不同的。

为什么“慢哈希”能阻止攻击者?

让合法用户的单次登录稍微变慢(几百毫秒),但让攻击者的破解成本变得极高(几年甚至几个世纪)。
以 bcrypt 为例,它的计算速度由成本因子(Cost Factor) 控制,比如:
bcrypt.hash(password, cost=12) // 迭代2^12=4096次
  • 合法用户:每次登录只需计算 1次 慢哈希(比如300ms)
  • 攻击者:如果尝试暴力破解(每秒100万次),现在只能尝试 ~3次/秒(因为每次哈希要300ms),所以破解一个8位密码的时间可能要几百年。
攻击者的行为通常分为两种:
  1. 在线攻击(如撞库):直接向服务器发送大量密码尝试登录。这样慢哈希就可以直接拖慢攻击速度,服务器可以轻松检测并封禁异常请求。
  2. 离线攻击(如数据库泄露后破解哈希):攻击者拿到哈希值后,在自己的机器上暴力破解。慢哈希会强制攻击者花费更多计算资源:原本MD5每秒可计算10亿次,现在bcrypt每秒只能计算几千次。生成彩虹表的成本从 1TB硬盘+1天 变成 需要整个Google的数据中心+100年。
以 bcrypt 的流程为例:
  1. 输入:密码 + 随机盐(Salt)。
  2. 迭代加密:使用Blowfish算法,反复加密盐和密码的组合(如迭代4096次)。每次迭代都依赖前一次结果,无法并行优化。
  3. 输出:最终的哈希值(包含盐和成本因子)。

加盐

盐(Salt)在密码学中,是指通过在密码任意固定位置插入字符串,让哈希后的结果和使用原始密码的哈希结果不相符,这种过程称之为“加盐”。在密码加盐处理后,同一个原始密码每次生成的字符串通常是不一样的。

盐值的作用和原理:

在密码存储过程中,加盐是一种增强密码安全性的常见技术。“盐” 是一个随机生成的字符串,它会与用户的原始密码相结合,然后再进行哈希运算。这样做的主要目的是防止彩虹表攻击,因为即使多个用户使用相同的密码,由于盐值不同,经过哈希处理后的结果也会不同。

特殊情况:在某些特定的场景下,也可以使用固定的盐值。例如,为了某种特定的业务需求或者在测试环境中,可能会使用一个固定的字符串作为盐值。在这种情况下,同一个密码每次生成的加盐哈希字符串是相同的,但这种做法会降低密码的安全性,不建议在生产环境中使用。

成本因子

成本因子(Cost Factor) 是慢哈希算法(如 bcrypt、PBKDF2)的核心参数,它决定了:
  • 哈希计算的迭代次数(直接影响计算时间)。
  • 计算资源的消耗(CPU/内存占用)。
  • 最终的安全强度(破解难度)。
成本因子影响迭代次数:
以  bcrypt 为例:成本因子 cost=N,实际迭代次数为 2^N 次。
  • cost=10 → 2^10 = 1,024 次迭代
  • cost=12 → 2^12 = 4,096 次迭代(推荐默认值)
  • cost=15 → 2^15 = 32,768 次迭代(更高安全性)

成本因子(cost)

迭代次数

单次哈希时间(示例)

破解10亿密码所需时间

10

1,024

~5ms

~2个月(GPU集群)

12(推荐)

4,096

~300ms

~200年

15

32,768

~2.5s

~16,000年

问题1: 成本因子是否可以无限增大?

答:不可以。需要平衡安全性和用户体验:成本过高会导致登录延迟(如cost=20需30秒计算),用户无法接受。通常选择使单次哈希在100ms~1s之间的cost值。

问题2: 如果未来硬件变快,是否需要调整cost?

答:是的!建议每2~3年增加1次cost(如从12→13),以抵消硬件进步带来的破解速度提升。

问题3: bcrypt的cost和PBKDF2的iterations有何区别?

答:

bcrypt.cost=12 → 固定迭代2^12=4096次。

PBKDF2.iterations=100000 → 直接指定迭代次数。两者本质相同,但bcrypt的cost更直观(指数增长)。

彩虹表

彩虹表攻击(Rainbow Table Attack)是一种密码破解技术,用于破解密码哈希的安全性。它是一种 预先计算密码哈希和其对应明文的巨大数据表的方法。虽然彩虹表攻击不是直接暴力破解密码,但它可以更快地破解加密密码,尤其是当使用弱密码时。
密码通常不会以明文形式存储在数据库中,而是将其哈希后的值存储在数据库中。哈希是一种单向函数,将输入数据转换为固定长度的哈希值。例如,常用的哈希函数如MD5、SHA-1、SHA-256等。因此,当用户登录时,系统会将其提供的密码进行哈希,然后将其与数据库中存储的哈希值进行比较。
彩虹表攻击的基本原理如下:
  • 预计算阶段:攻击者使用密码哈希函数和彩虹表生成算法,预先计算大量的哈希值和对应的明文密码,并将结果存储在一个庞大的彩虹表中。这个表可以包含数亿条数据项。
  • 破解阶段:当攻击者获得了存储在目标系统中的密码哈希值后,他们可以在彩虹表中查找匹配项。如果哈希值在彩虹表中找到了匹配,攻击者就可以通过查找对应的明文密码来获取用户的原始密码。
尽管彩虹表攻击是一种有效的破解方法,但它有一些限制:
  • 彩虹表攻击的成功率受到彩虹表的规模和生成算法的影响。如果目标密码很复杂,彩虹表需要更大的规模才能成功破解。
  • 一些安全措施可以减轻彩虹表攻击的影响,如“盐”(Salt)。盐是一个随机值,附加在用户密码上然后一同进行哈希。这使得即使两个用户使用相同的密码,由于不同的盐,他们的哈希值也是不同的。
  • 使用更强大、更缓慢的哈希函数(如bcrypt、scrypt)可以大幅降低彩虹表攻击的成功率。

更多推荐