种一棵树最好的时间是十年前,其次是现在。

消息认证码-识别伪造的数据

村里有两个好朋友小明和小红。他们喜欢通过写信来分享自己的秘密和计划。有一天,小明写了信给小红,告诉她后山发现了一片美丽的花园,约她第二天一起去看。可是,小红收到信时,发现信上写的地点变成了村庄的集市。原来,这封信在传递过程中,被小黑改了。为了确保这些秘密不被其他人知道,他们想出了一种方法:每次写信前,他们先约定一个“秘密符号”,这个“秘密符号”只有他们俩知道。从那以后,每次写信时,小明都会在信件内容中附上“秘密符号”。小红收到信后,都会检查信件内容中的秘密符号。如果符号正确,她就知道信件没有被改动过。

这个“秘密符号”就是消息认证码的简单版本。它帮助小明和小红确认信件的完整性和真实性,如果别人篡改通信内容,他们就能立即识别出来。

消息认证码(Message Authentication Code,MAC)的概念最早可以追溯到1920年代,当时用于通信协议中防止消息被篡改。直到20世纪70年代,随着计算机科学和密码学的进步,MAC 才得到广泛应用。

消息认证码就像带密钥的单向哈希函数。同一个文件,使用单向哈希函数生成的哈希值始终是一样的。而如果使用消息认证码计算的值,相同的密钥得出来的结果肯定是相同的,不同密钥的结果肯定是不同的。当然,同一个密钥计算不同内容的值也不一样。

消息认证码可以用来验证数据来源、验证数据是否完整、防止篡改和伪造等。在 IP 协议中,用消息认证码来检查 IP 数据包是否被篡改,确保数据包的完整性和真实性;在实现无状态服务的场景中,能确认客户端的访问令牌是否真实完整,未被篡改。在银行交易中,消息认证码用来确认交易请求的来源和交易信息的完整性。

MAC 的类型

HMAC(Hash-based Message Authentication Code)是一种广泛使用的消息认证码算法,NIST 的 FIPS PUB 198-1 标准定义了 HMAC 的规范,它基于单向哈希函数(SHA-256 或 SHA-3)。像单向哈希函数那样,消息认证码算法的输出都长度也是固定的。它的计算过程会将消息(输入)与密钥一同计算,然后将结果传递给哈希函数进行多次迭代处理。

HMAC 不加密消息,它只是带密钥的单向哈希函数,它的安全性取决于其所依赖的的哈希函数,输出长度和密钥质量也对安全性有影响。HMAC 的性能取决于所使用的哈希算法和密钥长度,不过在实践中通常具有良好的性能。

GCM(Galois Counter Mode)是对称加密分组密码的一种认证加密操作模式,通常与 AES 一起使用,它在 CTR 模式的基础上加入了认证功能,能够提供加密和认证双重功能。专门用于消息认证码的 GCM 称为 GMAC(Galois/Counter Mode Authentication Code)。计算 GMAC 和 AES 加密可以使用同一个密钥,在密钥管理上存在优势。

不过 GMAC 的最大优势是性能,在廉价的硬件上也有很高的吞吐率。GCM 采用的是 CTR 模式加密,CTR 模式是一种流式加密模式,允许对数据进行逐块加密处理,可以在现代多核心 CPU 上并行执行。GCM 通常和 AES 一起使用,现代 CPU 大都内置了 AES 扩展指令集,在硬件加速支持下,GCM 能够以非常高的速度处理数据。

重放攻击

对消息认证码的攻击有伪造篡改的攻击,先通过中间人截获 MAC,试图篡改内容而达到伪造目的,这类攻击通常是实时的,需要强大的计算能力,针对那些已被攻破的哈希函数,如 MD5 或 SHA-1,只要我们能避免使用不安全的哈希函数就能避免这些问题。而重放攻击就不那么容易避免了,因为它并不单单是密码学的问题,还涉及程序设计问题。

重放攻击指的是截获合法用户请求,然后将请求发送多次给接收方,以此欺骗接收方相信请求来自合法用户。比如小明要向小红汇款,在收到汇款请求后,银行 A 使用密钥将转账信息生成 MAC,并将 MAC 附加到转账请求中发送给银行 B,银行 B 使用共享密钥验证接收到的 MAC,确保请求完整未被篡改。验证通过后,银行 B 将1000元从小明的账户转移到小红的账户。

假设攻击者小黑拦截了小明的转账请求,并记录了 MAC。小黑稍后进行了重放攻击,如果银行 B 仅验证 MAC 而不检查其它条件,银行 B 会认为此次时合法的转账请求,再次将1000元从小明的账户转移到小红的账户。

防止重放攻击的方法通常基于这几个原则:

  • 时间戳或随机数:发送方在生成消息的 MAC 时,可以将消息与当前的时间戳或一个随机数相关联。即使攻击者截获了消息和相应的MAC,也无法简单地将消息重放,因为时间戳或随机数不同。

  • 单次性标记:发送方可以使用单次性标记(One-Time Token)来防止重放攻击。这些标记只能使用一次,因此即使攻击者截获了消息和相应的 MAC,也无法在其他时间重新使用它们。

  • 序列号:每个消息都可以附带一个单调递增的序列号。接收方在验证消息时会检查序列号,如果收到的序列号小于之前收到的序列号,则拒绝消息,从而防止重放攻击。

  • 限制消息有效期:发送方可以在消息中包含一个有效期限制,告知接收方消息的有效期。接收方在验证消息时会检查消息的时间戳或其他信息,确保消息在有效期内,如果消息过期,则拒绝接受。

鉴定用户

对于访问网站的用户,如果每次请求都被要求输入账号和密码验证身份,那么网站的体验一定非常糟糕。接下来我们使用消息认证码来解决这个问题。

在用户登录通过之后,网站会对用户唯一标识和其它必要信息生成消息认证码,然后将原始信息连同消息认证码一同返回给客户端。后续客户端的请求,只需将原始信息和消息认证码连同请求一起发送,服务端验证原始信息和消息认证码匹配之后,就可以确定原始信息是真实有效的。

对于一个设计合理的系统,user_id 始终是唯一的,而且不会改变。user_id 适合作用户的唯一标识。在校验用户名和密码正确之后,网站对 user_id 生成消息认证码,连同 user_id 发回客户端。后续请求时,客户端将原始信息 user_id 和消息认证码一同发送到服务无端。服务端验证 user_id 与消息认证码是否匹配,从而鉴定请求的用户身份。

这是一个最简单的访问令牌,但是存在很多问题,有两个问题最关键:

  • 令牌长期有效:一旦令牌被盗或泄露,攻击者可以一直使用被盗或泄露的令牌进行各种恶意操作。给令牌加上有效期可以缩短被攻击的时间窗口。

  • 令牌无法吊销:如果令牌被盗或泄露,还应当可以吊销令牌;当用户修改密码之后(可能由于密码泄露),网站也应当立即吊销当前用户的所有令牌。

为了避免这些问题,我们需要给原始消息加上一个随机数和有效期。随机数相当于令牌的唯一序号,网站系统会在数据库(如 Redis)中保存这个序号。令牌的序号也可以使用时间戳替代。当需要吊销令牌的时候,只需要把相应用户下的令牌序号删掉就可以了。有效期表示令牌只要不被吊销,在这个日期之前都是有效的。

网站系统需要验证这个令牌的消息认证码与原始消息是否匹配,如果匹配说明确实是网站签发的令牌,且完整未被篡改。接着验证原始消息的有效期,确认令牌确实在有效期内。最后比对令牌的唯一序号,系统会检查数据库相应用户记录里是否存在匹配的序号,如果存在,则表明令牌序号验证通过;如果不存在,或标记过期(这取决于你系统的设计),则令牌序号验证不通过。

三者依次验证,全部通过之后才能确认令牌是有效的,当前是合法访问,原始消息中的 user_id 正是当前访问者的身份。反之都表示令牌无效。

版权声明: 本文为原创内容,未经许可,不得转载。