加密哈希算法的实现原理
哈希算法(Hash function)又称散列算法,是一种从任何数据(文件、字符等)中创建小的数字“指纹”的方法。哈希算法只需满足把一个散列对象映射到另一个区间的需求,因此根据使用场景的不同,可将哈希算法分为加密哈希与非加密哈希。
概述
加密哈希被认为是单向函数,也就是说极难由散列函数输出的结果,回推输入的数据是什么。加密哈希函数的输入数据,通常被称为消息(message),而它的输出结果通常被称为摘要(digest)。一个理想的密码散列函数通常具有以下三个特性:
- 单向性:极难由一个已知的散列数值,推算出原始的消息;
- 唯一性:在不改动散列数值的前提下,修改消息内容是不可行的;
- 抗碰撞性:对于两个不同的消息,它不能给与相同的散列数值。
其中不可碰撞性是指以当前的算法与算力水平,哈希碰撞的开销超出人类可以接受的水平。以 SHA-256 为例,其哈希数值可能性约有 1077 种,而目前人类估计的宇宙原子总数约 1080。 虽然有概率论 生日悖论 问题存在,N 位长度的哈希表可能发生碰撞测试次数不是2N次而是只有2N/2次,但仍然是一个巨大的数字。
常见的加密哈希函数有 MD5、SHA-1、SHA-2(包含 SHA-224、SHA-256、SHA-512 等),虽然种类繁多,但除了生成摘要的长度 、循环体内容等有一些差异外,算法的基本结构是一致的。下面以 SHA-256 为例,详细介绍加密哈希算法的执行步骤。
SHA-256 实现原理
常量初始化
SHA-256 算法中用到了 8 个哈希初值以及 64 个哈希常量,其中,8 个哈希初值是对自然数前 8 个质数(2,3,5,7,11,13,17,19)的平方根的小数部分取前 32 bit:
|
|
64个哈希常量是对自然数中前 64 个质数的立方根的小数部分取前 32 bit,标记为 k[t]:
|
|
这些常量将会在算法循环体中用到。
附加长度值
SHA-256 用一个 64 位的数据来表示原始消息的长度,而在信息处理的过程中给需要将消息分解成 512bit 大小的块,因此补位后的消息长度应该是 512 的整数倍。附加长度值分为两个步骤:
- 第一个 bit 位补 1,然后都补 0,直到长度满足对 512 取模后余数是 448,如果长度已经满足对 512 取模后余数是 448,需要填充 512 个bit;
- 附加长度为 64bit 的长度值。
以 ASCII 编码的字符串abc
为例,其十六进制表示为0x616263
,补位后的数据如下:
|
|
主循环
在进行消息摘要计算之前,需要将消息分解成 512bit 大小的块,分成块的数量也是算法需要循环的次数。每个块又将分解为 16 个 32bit 大端存储的字,记为w[0]~w[15]。由于在计算过程中需要 64 个『字』,前 16 个字直接由消息的第 i 个块分解得到,其余的 48 个字由下面的迭代公式得到:
|
|
在摘要计算过程中用到了六个运算函数:
|
|
计算过程如下:
|
|
每个区块运算的结果为最终的 a~h 值加上 h0~h7,并作为下一个区块的初值,如果是最后一个区块将作为散列数值输出。
以上文的字符串abc
为例,算法流程如下:
|
|
上述流程可用下面的简图表示。可以看出,摘要算法通过分组区块进行操作,对消息不断进行压缩,最终生成密文。
总结
随着攻击算法的不断改进与计算能力的增强,加密哈希算法逐渐被『破解』,例如 MD5 和 SHA-1 已经被证明不具有不可碰撞性,即MD5(m1) == MD5(m2)
:
- 2004 年,王小云教授证明 MD5 可以产生碰撞:Collisions for Hash Functions;
- 2005 年,王小云教授改进攻击算法,在 263 时间复杂度内找到 SHA-1 碰撞:New Cryptanalytic Results Against SHA-1;
- 2017 年,Google 公司创建了两个有着相同的 SHA-1 值但内容不同的 PDF 文件,代表 SHA-1 算法已被正式攻破:Google: Announcing the first SHA1 collision 。
为什么不可碰撞性对加密哈希算法如此重要?从 SHA-256 算法的实现步骤可以看出,加密哈希的逆向计算几乎是不可能的,暴力破解法的成本也太高,因此对加密哈希算法所谓的攻击实际是利用哈希碰撞为突破口进行数据伪造。以常见的保存用户密码为例,如果是明文存储,一旦发生数据泄露,那么所有的账户都会被盗用,因此常用下面一些方法进行 Hash 加密:
- Hash 加密:单纯对密码进行 Hash 加密无法保证密码的安全性,因为用户密码通常是短字符,无论采用哪种加密算法,都可以利用暴力破解或彩虹表攻击破解。
- Hash 加盐:在原消息上添加随机盐再进行哈希加密,并将盐与密码保存起来,以便下次登陆验证,添加随机盐增加了彩虹表破解的难度,促使攻击者放弃破解。但是如果对密码进行不安全的散列函数(MD5)计算,数据库泄露后,攻击者可以根据散列值找出碰撞的消息,不管这个消息是否与密码相同,都可以通过验证。
- 专用哈希函数加密:使用
bcrypt
等专门用来密码加密的哈希函数进行加密,这类函数通常运算时间较长,大大增加了攻击成本。
密码加密不单单是一个技术问题,对于攻击者来说,如果破解成本大于收益成本,即使攻破了加密的密码也是无意义的。而那些被证明可以产生散列碰撞的 Hash 函数,攻击它们的成本较低,随着算法的改进与硬件水平的提升,破解的成本也不断降低。从安全的角度考虑,应该及时更换不安全的哈希算法。
加密哈希的应用比较广泛,笔者详细地学习哈希算法的实现原理,也是为了更好地理解它们使用与被攻击的方式,而不仅仅是在编程中调一个函数库。在这之前以为密码学的知识比较晦涩难懂,实际上抛开中间的数学运算,整体的逻辑十分清晰,基本结构很容易理解。