你驻足于春色中,于那独一无二的春色之中.
我们一般自己写个小程序做用户验证的时候,大多数情况都是用MD5,sha1,好一点的再加个盐,这次让我们来看看开源软件中的用户密码加密都是怎么实现的。
Project Harbor是由VMware公司中国团队为企业用户设计的Registry server开源项目。主要用于Docker镜像仓库的管理。
嗯,简介就到这里。
在Harbor的数据库中看到了这样一条用户验证的存储信息。
| username | password | salt |
| test | 65e900b5a2bdff474e29d0d2b21f4945 | gktqer4zml32472wmht9xeuixvg5pvjd |
test账户的明文密码为 123QWEqwe
看到如上的数据库结构,32位的散列,猜测多半就是MD5(password+salt)的加密套路,然而事实证明我还是太年轻,无论我如何改变密码和盐的组合,都无法得到数据库中的密码散列,为了节省大家尝试的时间,我就把几种组合方式的结果罗列在下面:
MD5(p+s): 7bd52852dd48c4375aa29bd73e125183
MD5(s+p): c01fb693df3c524442149ff16d7d5fc8
MD5(MD5(p)+s): 87b9168b430edb9fcfd03474c7f35ac0
MD5(MD5(s)+p): d70ffc1ace99fa8d7e52ef3e29907a54
……
常识性的MD5猜测竟然都没有正确,那么可能的加密方式就难以捉摸了,SHA1后截断?,HMAC?
首先想到的是去Harbor的官档里找答案,不过大概翻阅了可能的官方文档后,并没有找到对它加密方式的记录。但是在Github的一个issue中,有许多人关于PBKDF2的讨论,会不会是使用这种加密方式呢?
通过查阅资料得知(中文维基竟然没有关于PBKDF2的解释条目,我的姿势不对?),PBKDF2是一种基于密码的密钥生成函数。
这种算法有5个输入参数,如下:
1 | DK = PBKDF2(PRF, Password, Salt, c, dkLen) |
PRF是一个伪随机函数,Password是主密码,Salt是盐值,c是算法迭代次数,dkLen是产生密钥的长度。
那么到这里,我们就需要去读Harbor的源代码来获取算法中的几个常数参数来验证我们的思路。
Harbor中的代码主要有两种语言构成,Go和AngularJS,这两种都是我没有实践过的语言,定位它的加密函数花费了不少时间。
首先我们找到Harbor src源码中ui的main.go,可以发现其中有关于密码方面的操作调用了dao这个包,而在dao包中有一个文件叫user.go,其中有个函数LoginByDb,LoginByDb有代码段如下:
1 | if user.Password != utils.Encrypt(auth.Password, user.Salt) |
可以得知,它的加密用的是utils中的Encrypt。
再跟踪Encrypt函数
func Encrypt(content string, salt string) string {
return fmt.Sprintf("%x", pbkdf2.Key([]byte(content), []byte(salt), 4096, 16, sha1.New))
}
果然,Harbor确实使用了pbkdf2算法,调用的Hash函数为Sha1,迭代4096次,密钥长度为int型16位。
为了验证方便,我在github上https://github.com/mitsuhiko/python-pbkdf2/blob/master/pbkdf2.py找到了一个pbkdf2的python实现,主要代码如下:
1 | import hmac |
调用相关函数进行测试,
1 | def check(data, salt, iterations, keylen, expected): |
得到了和数据库中相同的结果。
上面提出的Hash密码的算法在国外普遍被接受并运用于密码保护中,可以有效抵抗彩虹表爆破,但是国内这方面的加密往往还是用最基本的几种方式。在今后的程序编写中,可以尝试使用这些更安全的Hash算法。