CREATE TABLE `user`(
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',
`account` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '账号',
`password_hash` VARCHAR(1024) NOT NULL DEFAULT '' COMMENT '密码散列值',
`salt` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '盐',
PRIMARY KEY(`id`),
)ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='用户账号表';
在开发登录功能时,我们需要存储用户的密码。通常情况下,用户的密码不会以明文形式存储,而是经过散列或加密处理后保存。本文将介绍几种常用的密码保存算法。考虑以下的User表。
$ md5 -s "patrick"
MD5 ("patrick") = 6c84cbd30cf9350a990bad2bcc1bec5f
$ md5 -s "Patrick"
MD5 ("Patrick") = f87567f2159b425795ebb7ba9bc406ec
MD5消息摘要算法是一种广泛使用的密码散列函数,它可以生成一个128位(16字节)的散列值,用于确保信息传输的完整性和一致性。需要注意的是,MD5并不是一种加密算法,而是一种信息压缩算法。当输入值稍有变化时,结果也会产生很大的差异。请看下面的例子。
MySQL [(none)]> SET @str='patirck';
Query OK, 0 rows affected (0.00 sec)
MySQL [(none)]> SELECT MD5(@str),LENGTH(MD5(@str)) AS len_md5,LENGTH(UNHEX(MD5(@str))) as len_unhex;
+----------------------------------+---------+-----------+
| MD5(@str) | len_md5 | len_unhex |
+----------------------------------+---------+-----------+
| 91325f7a12f20fef31a48b5d691c279c | 32 | 16 |
+----------------------------------+---------+-----------+
1 row in set (0.01 sec)
mysql> SELECT UNHEX('4D7953514C');
-> 'MySQL'
mysql> SELECT X'4D7953514C';
-> 'MySQL'
mysql> SELECT UNHEX(HEX('string'));
-> 'string'
mysql> SELECT HEX(UNHEX('1267'));
-> '1267'
在MySQL中保存MD5密码时,除了可以使用CHAR(32)来存储外,还可以使用BINARY(16)来存储。示例说明如下:
- 在MySQL中,BINARY类型用于存储二进制字符串(字节字符串),MySQL会为BINARY类型的变量添加.
- 根据MySQL文档,UNHEX(str)函数将字符串的字符解析为十六进制,并转换为二进制字符串。
mysql> SELECT UNHEX('Patick');
+-----------------+
| UNHEX('Patick') |
+-----------------+
| NULL |
+-----------------+
mysql> SELECT X'616263', HEX('abc'), UNHEX(HEX('abc'));
-> 'abc', 616263, 'abc'
mysql> SELECT HEX(255), CONV(HEX(255),16,10);
-> 'FF', 255
并且,输入的字符必须是合法的十六进制数字(’0’…’9’,’a’…’f’,’A’…’F’)。与UNHEX相对应的函数是HEX。根据MySQL文档,HEX(str)函数返回一个字符串的十六进制表示,字符串的每个字符都被转换为两个十六进制数字。
aaaaaa -> 281DAF40 -> sgfnyd -> 920ECF10 -> kiebgt
H R H R
MD5 + salt是一种基本的信息摘要算法,它将长度不同的密码p映射为一个128位的散列值hash(p)。那么,是否可以从散列值hash(p)反推出密码p呢?暴力攻击和彩虹表攻击是两种常见的方法。
暴力攻击是指对于给定的hash(p),尝试计算出满足q = hash(p)的p。一种方法是对密码集合中的每个p计算hash(p),直到结果等于q;另一种方法是使用查表法,建立一个包含每个p和对应hash(p)的大型数据库,并根据q进行索引。这两种方法在理论上都可行,但前一种方法可能需要大量时间,而后一种方法需要大量存储空间。因此,暴力攻击并不可行。
彩虹表攻击是一种通过预先计算哈希链集来降低存储空间需求的方法。预计算的哈希链集是通过以下步骤生成的:
H函数是要破解的哈希函数,约简函数R是在构建链时定义的函数,它的值域和定义域与H函数相反。通过约简函数,可以将哈希值约简为与原文相同格式的值。只需存储每条链的起始节点和终止节点,而不需要存储所有节点,即可生成哈希链集。
预计算的哈希链集的使用:破解一个hash值时,对于长度为k的预计算哈希链集,每次破解计算次数不超过k,因此大大节约时间。每条链只需存储起始节点和终止节点,储存空间只需约1/k,因此也大大节约了空间。
为了防止彩虹表攻击,可以使用加盐(salt)的方法。盐是在散列之前将散列内容(例如密码)的特定字符串插入的一种方式。这种处理可以增加额外的安全性。例如,用户的密码是passwd,盐是my,组合后的值是mypasswd,MD5的散列值是3102125cae72c19f215480ddf2d0d5c3。这样,即使MD5的散列值被破解,获得mypasswd,也难以获得用户的原始密码。甚至可以进行两次MD5散列操作,以增加密码的安全性。
SHA(Secure Hash Algorithms)是一组密码散列函数,是FIPS认证的安全散列算法。SHA可以计算出一个数字消息对应的固定长度字符串(也称为消息摘要),且不同输入的消息对应不同的字符串的概率很高。SHA-1是SHA算法家族中最常用的算法,但在2010年后,其安全性已不被广泛接受。SHA-2是SHA算法的后继者,包括SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256。SHA-3是一种与之前算法不同且可替代的加密散列算法。
SHA1和SHA2的一个主要区别是,SHA1生成的散列值为160位,而SHA2可以生成不同长度的散列值,例如SHA-256生成256位的散列值。在Linux上,可以使用命令计算字符串”patrick”的SHA256值。
sudo cat /etc/shadow
patrick:$6$C/vGzhVe$aKKGdhzTmYyxp8.E68gCBkPhlWQ4W7/OpCFQYV.qsCtKaV00bToWh286yzy73jedg6i0qSlZkZqQy.wmiUdje:18447:0:99999:7:::
$id$salt$encrypted
bcrypt(cpst,salt,pwd)
state <- EksBlowfishSetup(cost,salt,key)
ctext <- "OrpheanBeholderScryDoubt"
repeat(64)
ctext <- EncryptECB(sate,ctext)
return Concatenate(cost,salt,ctext)
目前,SHA1算法已不再推荐使用,而SHA2算法被广泛采用。
Linux用户登录的密码保存在/etc/shadow文件中,示例如下:
id表示加密算法,1代表MD5,5代表SHA-256,6代表SHA-512。salt表示盐,由系统随机生成。encrypted表示密码的散列值。
然而,无论是MD5还是SHA,它们都不是设计用于密钥派生函数(KDF)的。随着计算能力的增强,即使是SHA3散列函数,被破解的时间也大大缩短。
密钥派生函数(KDF)是使用伪随机函数从主密钥或密码等秘密值中派生一个或多个密钥的函数。KDF可用于扩展密钥长度或获取所需格式的密钥。密码散列函数是最常见的密钥派生函数示例。其使用可以表示为DK = KDF(Key,Salt,Iterations),其中DK是派生密钥,KDF是密钥派生函数,Key是原始密钥或密码,Salt是作为密码盐的随机数,Iterations是子函数的迭代次数。使用派生密钥替代原始密钥或密码作为系统的密钥。盐的值和迭代次数与散列密码一起存储或以加密消息的明文形式发送。
在这里,我们介绍了一种名为bcrypt的密码散列算法。bcrypt是一种运行速度较慢的算法,设计用于密码散列,这使得生成字典攻击所需的时间更长。bcrypt算法主要分为两个步骤:
- 使用cost、salt和password初始化状态eksblowfish,然后bcrypt使用大量时间运行密钥派生,从主密钥中生成一系列子密钥,其中主密钥是输入的password。
- 使用OrpheanBeholderScryDoubt字符串进行64次加密。