V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
logto
V2EX  ›  程序员

编码,加密,与密码哈希演进简史

  logto · 285 天前 · 2385 次点击
这是一个创建于 285 天前的主题,其中的信息可能已经有所发展或是发生改变。

前言

字如其名,密码哈希是从密码计算哈希值的过程。哈希值通常存储在数据库中,在登录过程中,用户输入的密码的哈希值会被计算并与存储在数据库中的哈希值进行比较。如果匹配成功,则验证通过。

在我们深入了解密码哈希算法的演进之前,我们先简单探讨一下为什么需要它。

明文密码:一个重大的安全风险

想象一下,你是一个网站的用户,并在该网站注册了一个账户。有一天,该网站被黑客攻击,数据库泄漏了。如果网站以明文形式存储密码,黑客可以直接访问你的密码。由于许多人在多个网站上重复使用密码,黑客可以使用这个密码来未经授权地访问你的其他账户。如果你在电子邮件账户上使用相同或类似的密码,情况会变得更糟,因为黑客可以重置你的密码并接管你所有关联的账户。

即使没有数据泄漏,在大型团队中,任何有数据库访问权限的人都可以看到密码。与其他信息相比,密码具有高度敏感性,你绝对不希望任何人能够访问它们。

很明显,明文存储密码是一个业余的错误。不幸的是,如果你搜索“password leak plaintext”,你会发现像 FacebookDailyQuizGoDaddy 这样的大公司都曾遭遇明文密码泄漏。很可能还有许多其他公司犯了同样的错误。

编码( encoding ),加密( encryption ),哈希( hashing )

这三个术语经常被混淆,但它们是不同的概念。

编码( encoding )

编码是密码存储中首先要排除的内容。例如,Base64 是一种编码算法,将二进制数据转换为字符字符串( Node.js ):

const data = 'Hello, world!';
const encoded = Buffer.from(data).toString('base64');
console.log(encoded); // SGVsbG8sIHdvcmxkIQ==

知道编码算法后,任何人都可以解码编码后的字符串并恢复原始数据:

const encoded = 'SGVsbG8sIHdvcmxkIQ==';
const data = Buffer.from(encoded, 'base64').toString();
console.log(data); // Hello, world!

对于黑客来说,大多数编码算法等同于明文。

加密( encryption )

在哈希算法流行之前,加密也被用于存储密码,例如使用 AES 。加密涉及使用密钥(或一对密钥)对数据进行加密和解密。

加密的问题在于“解密”一词。这意味着加密是可逆的,如果黑客获得密钥,他们可以解密密码并获取明文密码。

哈希( hashing )

哈希、编码和加密之间的主要区别在于哈希是不可逆的。一旦密码经过哈希处理,就无法解密回其原始形式。

作为网站所有者,你实际上不需要知道密码本身,只要用户可以使用正确的密码登录即可。注册过程可以简化如下:

  1. 用户输入密码。
  2. 服务使用哈希算法计算密码的哈希值。
  3. 服务将哈希值存储在数据库中。

用户登录时的过程是:

  1. 用户输入密码。
  2. 服务使用相同的哈希算法计算密码的哈希值。
  3. 服务将哈希值与存储在数据库中的哈希值进行比较。
  4. 如果哈希值匹配,用户得到验证。

这两个过程都避免了以明文形式存储密码,并且由于哈希是不可逆的,即使数据库被入侵,黑客只能获取到看起来像随机字符串的哈希值。

哈希算法入门包

哈希可能看起来是密码存储的完美解决方案,但事实并非如此简单。为了了解其中的原因,让我们探讨一下密码哈希算法的演进。

MD5

1992 年,Ron Rivest 设计了 MD5 算法,这是一种消息摘要算法,可以从任意数据计算出一个 128 位的哈希值。MD5 已广泛用于各个领域,包括密码哈希。例如,"123456" 的 MD5 哈希值为:

e10adc3949ba59abbe56e057f20f883e

如前所述,哈希值看起来像随机字符串,并且是不可逆的。此外,MD5 速度快、易于实现,使其成为最流行的密码哈希算法。

然而,MD5 的优点也是密码哈希中的弱点。它的速度使得它容易受到暴力破解的攻击。如果黑客拥有常见密码列表和你的个人信息,他们可以计算每个组合的 MD5 哈希值,并将其与数据库中的哈希值进行比较。例如,他们可能将你的生日与你的姓名或宠物的名字组合起来。

如今,计算机的计算能力远远超过以前,使得暴力破解 MD5 密码哈希变得容易。

SHA 家族

那么,为什么不使用生成更长哈希值的算法呢?SHA 家族 似乎是一个不错的选择。SHA-1 是一种生成 160 位哈希值的哈希算法,而 SHA-2 是一系列生成 224 位、256 位、384 位和 512 位长度哈希值的哈希算法。让我们看看 "123456" 的 SHA-256 哈希值:

8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92

SHA-256 哈希值比 MD5 长得多,而且也是不可逆的。然而,还存在一个问题:如果你已经知道哈希值,比如上面那个哈希值,并且在数据库中看到了完全相同的哈希值,那么你就会知道密码是 "123456"。黑客可以创建一个常见密码和它们对应的哈希值列表,并将其与数据库中的哈希值进行比较。这个列表被称为彩虹表。

盐值( salt )

为了减轻彩虹表攻击,引入了盐值的概念。盐值是在哈希之前添加到密码的随机字符串。例如,如果盐值是 "salt",那就使用 SHA-256 将密码 "123456" 与盐值一起哈希。假设原有函数:

sha256('123456');

改成这样即可:

sha256('salt123456'); // 9898410d7f5045bc673db80c1a49b74f088fd7440037d8ce25c7d272a505bce5

正如你所看到的,结果与不带盐值的哈希完全不同。通常,每个用户在注册过程中被分配一个随机的盐值,并且存储在数据库中的密码哈希值旁边。在登录过程中,盐值用于计算输入密码的哈希值,然后将其与存储的哈希值进行比较。

迭代( iteration )

尽管添加了盐值,但随着硬件的增强,哈希值仍然容易受到暴力破解的攻击。为了增加难度,可以引入迭代(即多次运行哈希算法)。例如,将算法:

sha256('salt123456');

改成这样:

sha256('salt' + sha256('salt123456'));

增加迭代次数使暴力破解更加困难。然而,这也会影响登录过程,使其变慢。因此,需要在安全性和性能之间取得平衡。

中场休息

让我们休息一下,并总结一下一个好的密码哈希算法的特点:

  • 不可逆( preimage resistance )
  • 难以暴力破解
  • 抵抗彩虹表攻击

你可能已经注意到,盐值和迭代都是满足所有这些要求所必需的。问题在于,MD5 和 SHA 家族都不是专门为密码哈希而设计的;它们广泛用于完整性检查(或“消息摘要”)。因此,每个网站可能都有自己的盐值和迭代实现,使得标准化和迁移具有挑战性。

密码哈希算法

为了解决这个问题,一些专门为密码哈希而设计的哈希算法已经出现。让我们来看看其中一些。

bcrypt

bcrypt 是由 Niels Provos 和 David Mazières 设计的一种密码哈希算法。它在许多编程语言中广泛使用。下面是 bcrypt 哈希值的一个示例:

$2y$12$wNt7lt/xf8wRJgPU7kK2juGrirhHK4gdb0NiCRdsSoAxqQoNbiluu

尽管它看起来像另一个随机字符串,但它包含了额外的信息。让我们拆分一下:

[$2y][$12][$wNt7lt/xf8wRJgPU7kK2ju][GrirhHK4gdb0NiCRdsSoAxqQoNbiluu]
  • 第一部分 $2y 表示算法,即 2y
  • 第二部分 $12 表示迭代次数,即 12。这意味着哈希算法将运行 2^12=4096 次(迭代)。
  • 第三部分 wNt7lt/xf8wRJgPU7kK2ju 是盐值。
  • 最后部分 GrirhHK4gdb0NiCRdsSoAxqQoNbiluu 是哈希值。

bcrypt 有一些限制:

  • 密码的最大长度为 72 个字节。
  • 盐值的长度限制为 16 个字节。
  • 哈希值的长度限制为 184 位。

Argon2

考虑到现有密码哈希算法的争议和限制(也许是大家累了),在 2015 年举行了一场 密码哈希竞赛。其中细节在此不表,让我们聚焦于获胜者:Argon2 。

Argon2 是由 Alex Biryukov 、Daniel Dinu 和 Dmitry Khovratovich 设计的密码哈希算法。它引入了几个新特性:

  • Memory-hard:该算法设计成难以并行化,使得使用 GPU 进行暴力破解变得具有挑战性。
  • Time-hard:该算法设计成难以优化,使得使用 ASIC (专用集成电路)进行暴力破解变得困难。
  • 抵抗 side-channel 攻击:该算法设计成可以抵抗 side-channel 攻击,如 timing attack 。

Argon2 有两个主要版本,Argon2i 和 Argon2d 。Argon2i 对 side-channel 攻击最安全,而 Argon2d 对 GPU 破解攻击提供了最高的抵抗力。

-- Argon2

下面是 Argon2 哈希值的一个示例:

$argon2i$v=19$m=16,t=2,p=1$YTZ5ZnpXRWN5SlpjMHBDRQ$12oUmJ6xV5bIadzZHkuLTg

让我们拆分一下:

[$argon2i][$v=19][$m=16,t=2,p=1][$YTZ5ZnpXRWN5SlpjMHBDRQ][$12oUmJ6xV5bIadzZHkuLTg]
  • 第一部分 $argon2i 表示算法,即 argon2i
  • 第二部分 $v=19 表示版本,即 19
  • 第三部分 $m=16,t=2,p=1 表示内存成本、时间成本和并行度,分别为 1621
  • 第四部分 $YTZ5ZnpXRWN5SlpjMHBDRQ 是盐值。
  • 最后部分 $12oUmJ6xV5bIadzZHkuLTg 是哈希值。

在 Argon2 中,密码的最大长度为 2^32-1 个字节,盐值的长度限制为 2^32-1 个字节,哈希值的长度限制为 2^32-1 个字节。这在大多数情况下应该够用了。:-)

现在,Argon2 已经在许多编程语言中可用,比如 Node.jsnode-argon2 和 Python 的 argon2-cffi

结论

多年来,密码哈希算法经历了显著的演进。我们应该感谢安全社区几十年来在使互联网更安全方面所做的努力。由于他们的贡献,使得广大开发者可以更加专注于构建更好的服务和产品,而不用担心密码哈希的安全性。于此同时,虽然无法构建 100% 安全的系统(例如 social engineering 总是防不胜防),但是我们可以通过不同的手段不断降低安全风险发生的概率。

如果你想避免实现身份验证和授权的麻烦,可以尝试 Logto 。我们提供安全( Logto 使用 Argon2 !)、可靠和可扩展的开源及 cloud 解决方案。

网站 https://logto.io GitHub https://github.com/logto-io

20 条回复    2023-09-09 10:27:46 +08:00
Mutoo
    1
Mutoo  
   285 天前 via iPhone   ❤️ 3
文章讲得不错,能不能再讲讲啥是 side-channel 攻击?
cheese
    2
cheese  
   285 天前   ❤️ 2
文章阅读很顺畅,喜欢这种推广
willxiang
    3
willxiang  
   285 天前   ❤️ 1
喜欢这种推广
logto
    4
logto  
OP
   285 天前
@Mutoo 谢谢,有机会另写一篇文章介绍一下
@cheese 谢谢
dearmymy
    5
dearmymy  
   285 天前
其实程序员都应该看看 日本人写那本 密码学。
aikilan
    6
aikilan  
   285 天前
真不错
huangqihong
    7
huangqihong  
   285 天前
@dearmymy 哪本啊
dearmymy
    8
dearmymy  
   285 天前   ❤️ 4
@huangqihong https://book.douban.com/subject/26265544/ 跟看小说一样,非常经典,一点不枯燥。
huangqihong
    9
huangqihong  
   285 天前
@dearmymy 感谢
wangjiang
    10
wangjiang  
   285 天前
写的很好,感谢
duke807
    11
duke807  
   285 天前 via Android
我和大家最后还是选择 sha256 这样的通用算法
xiaochuaner
    12
xiaochuaner  
   285 天前
额,一般来说“密码哈希”这个词是指密码算法领域的哈希算法,即具有抗碰撞、原相等性质的哈希算法,应该跟数据库里的密码(正确翻译应该是口令,password )没啥关系吧。没想到还有这种用法。
kasusa
    13
kasusa  
   285 天前
有类似 cmd5 这种网站,可用解密常见的 hash 值
原理是他建立了巨大的数据库,把常见的口令、盐 都计算好结果,然后与你给出的结果比较。
如果你的密码是 p@ssword123 这种,hash 之后也能很轻松倍破解开。
如果你的密码是 Z'n|GW"@.;Y<Lh}sH:&d 这种(我用密码生成器随机生成的) 那么 99.9% 是无法被破解的。
dode
    14
dode  
   285 天前   ❤️ 1
@kasusa
bcrypt 算法
盐值的长度限制为 16 个字节

盐是每个密码随机产生的,整个数据库里所有的密码都不一样,同一个密码多设置几次都不一样,md5 撞库是不行的
mitoop
    15
mitoop  
   285 天前
行文很好👍
oColtono
    16
oColtono  
   285 天前
很赞,这个推广吃下了
chenjia404
    17
chenjia404  
   284 天前
我的这个方案,抗脱库,抗彩虹表,计算速度快 https://v2ex.com/t/956852
CodeCodeStudy
    18
CodeCodeStudy  
   284 天前
加密那里可以展开讲讲,对称加密,非对称加密
stamhe
    19
stamhe  
   232 天前
@kasusa 黑客和安全组织手上一大把。
stamhe
    20
stamhe  
   232 天前
邮箱验证码登录,手机验证码登录。。。怎么想的呢?
关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1159 人在线   最高记录 6543   ·     Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 · 27ms · UTC 18:18 · PVG 02:18 · LAX 11:18 · JFK 14:18
Developed with CodeLauncher
♥ Do have faith in what you're doing.