Lec 21 安全介绍&身份认证
阅读参考书 §11.2
思考题
- 解释哈希函数的基本性质,并将其与各种密码存储方案联系起来(例如,哈希函数的抗碰撞性如何帮助我们存储密码?)。
- 我们的目标(policy)是什么?我们的假设/ 威胁模型(thread model)是什么?
- 你不需要理解哈希函数背后的数学原理,感兴趣的话可以wiki SHA-2。我们关注的是将哈希函数作为一种加密基础原语,用于构建更安全的系统。
- 解释攻击者如何用彩虹表破解用户的密码,即使系统存储的是哈希后的密码;给定一张彩虹表,尽可能多地恢复用户密码
- 解释”慢“哈希函数的好处
- 解释攻击者如何采用彩虹表破解(部分)用户密码,即使系统存储的是使用慢哈希函数处理后的代码;给定一张彩虹表,尽可能多地恢复密码
- 解释盐化哈希如何缓解彩虹表攻击。
- 针对上述任意密码存储方案,实现用户身份验证(即实现
check_password函数) - 解释 Cookie 和challenge-response协议所解决的问题
- 身份验证的目标是什么?
计算机系统安全模型
保护计算机系统免受攻击的核心问题是身份认证。系统在执行操作前需要回答以下问题:
- 真实性(Authenticity):声明的身份是否真实?
- 完整性(Integrity):请求是否由该主体发出,未被篡改?
- 授权(Authorization):该主体是否有权限执行该操作?
认证的基本概念
认证(Authentication) 旨在可靠识别请求的主体。身份认证通常涉及两个阶段:
- 会面阶段(Rendezvous):个人访问权威机构,建立身份标识符,并商定认证方法。
- 身份验证阶段(Verification of Identity):主体提交身份凭证,系统验证其真实性。
身份认证方法:
- 生物特征(指纹、人脸等)
- 持有物品(磁条卡、智能卡)
- 记忆信息(密码、PIN 码、加密密钥)
逐步改进的密码存储方案
版本1——明文存储

验证用户身份接口
# 第一个版本,明文存储到服务器上
check_password(username, inputted_password):
stored_password = account_table[username]
return stored_password == inputted_password问题: 攻击者能够直接读取到用户密码的
版本2——哈希存储
计算机系统中最常用的身份验证的方法——基于密码,
这种方式有显著的缺点,存储在系统中的密码副本称为攻击者的目标。为了解决密码直接存储带来的弱点,可以采用密码的加密哈希值而非明文存储密码。
加密哈希函数,将任意长的字节数组M映射到固定长度的值V,将会有如下性质:
- 确定性,对于给定的输入M,很容易计算出V <- H(M), H是哈希函数
- 单向性,如果只知道V,很难计算出M
- 难以找到另外一输入M'使得H(M) = H(M'), 这也是很困难的
- 抗碰撞性。计算出的哈希值v尽可能短,但需要保证足够长,来保持很低的哈希冲突的概率(即让第3点成立),典型的大小是160-256个比特
这种设计的难点在于如何设计这样的加密哈希函数,特别是性质3。SHA(secure hash algorithms)为该方式一种解决方案。像大多数加密函数一样,是计算上安全的。它们的设计使得破解它们在计算上是不可行的,而不是不可能的。其理念是,如果破解一个特定函数很长很长时间,那么我们可以认为这个函数是安全的。像这类加密哈希函数少之又少,而且仅存的哈希函数的存在也都是被大众所知的。
验证用户身份接口
# 第2版本
check_password(username, inputted_password):
stored_hash = accounts_table[username]
inputted_hash = hash(inputted_password)
return stored_hash == inputted_hash这样一来,一个攻击者在只获得用户3密码的哈希值的情况下,是否能推断出用户3的实际密码?
不能, 现在攻击者无法直接从表中读取密码,也不能通过反转哈希函数来获得密码。
But,
问题: 攻击者可以采用“彩虹表”攻击。 攻击者可以预先创建一个表格,将密码映射到它们的哈希值,并将这些信息保存起来。这样做的目的是为了在需要时快速地通过比较哈希值来确定用户的密码。这种预先计算和存储哈希值的方式称为“彩虹表”攻击。

NOTE
冷知识,在我手提电脑上算1000万个哈希值,也只需要8秒上下 。
因为用户而言,总会有人设置一些比较简单的密码,比如tfboy,这样以来,可以通过常用的几个哈希函数将其映射(“彩虹表”),我只需要将获取到的哈希值与彩虹表对比,看是否命中。命中了,我就知道用什么哈希函数,及其密码了。
版本3——慢哈希函数
其他地方可能叫密钥派生函数(key derivation functions)
慢哈希函数,将任意长的字节数组M映射到固定长度的值V,将会有如下性质:
- 确定性,如果x1=x2则H(x1) = H(x2)
- 单向性,给定x,很容易但却很慢计算出H(x),但是知道H(x)(x除外),但是很难推出x
- 之前是纳秒级,现在是秒级
- 抗碰撞性。如果x1 ≠ x2, 则H(x1) = H(x2)的概率非常低,可视为0
验证用户身份接口
# 第三个版本
check_password(username, inputted_password):
stored_hash = accounts_table[username]
inputted_hash = slow_hash(inputted_password)
return stored_hash == inputted_hash对于慢哈希函数方案,攻击者是否可以像以前一样生成彩虹表?对较少的密码来说是否可行?
攻击者可以尝试生成彩虹表,但由于每次计算的时间较长,他们能覆盖的密码数量会受到极大限制。
我们希望使得攻击者即使对于常见密码也很难预先计算出哈希值。这又该怎么做呢?
办法: 添加随机数混淆
版本4——加盐哈希(Salted Hashing)
我们对每个用户关联一个随机数,我们称之为“盐值”(salt),这些盐以明文形式存储,它们不是一个秘密。我们将密码和盐值进行拼接,然后进行哈希处理存储

验证用户身份接口
# 第四个版本
check_password(username, inputted_password)
stored_hash = accounts_table[username].hash
salt = accounts_table[username].salt
inputted_hash = slow_hash(inputted_password | salt)
return stored_hash == inputted_hash对于加盐哈希方案,攻击者是否可以像以前一样生成彩虹表?
为了预先计算出哈希表,即使只是最常见的密码,攻击者也需要为每一个可能的盐制作一个单独的表;这是不可行的。
要针对特定用户——比如user1——攻击者可以读取user1的盐,然后使用该盐制作一个表。user1的密码越常见,越有可能被破解,他们事先不知道盐,就不太可能预先计算这个表。
Cookie 机制与身份验证
为了密码最小化曝光,Cookies允许用户在一段时间内不需要身份验证,来避免重复提交密码
这里我们需微调一下我们的威胁模型: 对手可以观察到 cookie,并尝试获取用户的密码,或者获取足够的信息以便创建长时间有效的 cookie。
首先我们了解他的大致流水,可看出cookie就是服务器给用户的身份凭证。
为了解释cookie长什么样,我们首先需要知道服务器有一个与其绑定的唯一密钥server_key,并并以不需要担心它的存储安全性为前提。
cookie = {user, expiration, H(server_key|user|expiration)}防范措施:
- 限制 cookie 过期时间
- 绑定用户设备信息
- 采用 HTTPS 保护 cookie 传输
挑战-响应协议
针对网络钓鱼攻击,我们可以采取哪些防范? 挑战-响应协议(challenge-response protocols)是一种身份验证方法,其特点是在不传输密码的情况下验证用户身份。这些协议通常涉及服务器向客户端发送一个挑战(challenge),而客户端则根据事先协商好的算法和密钥生成一个响应(response),用于证明其身份的有效性。
为了便于说明, 我们设想服务器仅存储用户名和密码。首先,不是客户端发送账号密码,而是我们的服务器先发送一个随机数458653,这是一个挑战,客户端会回复他们的密码和随机数串联后得出的hash值。在服务器端,可以计算出相同的hash值则通过。

这里我们再微调一下我们的威胁模型: 对手经营着一个服务器,并尝试说服用户在他们的服务器发送密码

优势:避免密码在网络中直接传输,防止中间人攻击。
有没有替代密码登陆方案?
- 密码管理器,但是你需要付诸对密码管理器编写者的信任。
- 使用用户的唯一物理特性,例如面部、声音、指纹等
- 双因素认证(2FA)(额外设备验证)
设计安全协议的原则
- 认证主体身份与服务身份。
- 密码不通过网络传输。
- 限制每个会话的认证凭据使用。
- 采用消息认证机制(如 HMAC)防止篡改。