网络安全-密码存储

Posted by 周思进 on October 4, 2019

在做聊天服务器的时候,有考虑过将用户名和密码按linux系统的用户名和密码存储方式保存到文件。

linux的账户密码保存在/etc/shadow文件,该文件每行包含9个字段,每个字段通过冒号隔开,前2个字段分别是用户名和加密后的密码。

加密的密码字段又有几部分组成,其格式如下:(从glibc2版本开始支持,可以通过”ldd –version”或者”getconf GNU_LIBC_VERSION”查看glibc版本号)
$id$salt$encrypted

通过id指明使用的加密算法,常用算法如下(老的glibc版本使用DES算法,这个应该基本不用了):

ID 算法
1 MD5
5 SHA-256 (glibc 2.7开始支持)
6 SHA-512 (glibc 2.7开始支持)

从算法来看都是哈希摘要算法,也就是单向加密算法,对原始数据进行加密后,不能从密文计算出原始明文数据,也即不可逆,这是摘要算法的特性。

从安全角度讲,密码肯定是不能明文存储的,那最简单的方式就是哈希计算后将计算结果作为密码存储到本地。对端发起认证,最终也是将对端输入的明文密码通过同样的摘要算法进行计算,然后和存储的对应用户密码比对确认是否一致,也就是摘要认证。

不过摘要算法有一个特性,就是对应同样的原始明文,使用相同的摘要算法,最终的结果一定是一样的,这就容易通过彩虹表(也就是明文密码和哈希计算的结果进行映射的关系表)等方式来暴力破解密码。

为此常用的办法就是再加个盐值混入计算,也就是上面格式中第二个组成部分salt,该盐值需要本地生成并存储好,盐值和原始明文密码想要怎么结合使用也可以自己定,比如简单的HASH(pass+salt)。这样对方不知道盐值,就不容易破解密码了。

最后一部分就是计算后存储的加密密码了。


在嵌入式开发中,经常会用到SSH协议登入设备,该方式用户名和密码登入验证就是通过/etc/shadow中保存的密码信息。

比如原有使用的摘要算法是MD5,如果想要提高安全性,使用SHA512,在确认glibc版本支持的情况下,可以通过指定id来更改使用的算法。

下面来看看这个接口声明:

char *crypt(const char *key, const char *salt);

key就是对应输入的明文密码。

salt就是对应的盐值了,这里盐值对于MD5算法,最多取8字节。对于SHA256和SHA512则最多只取16个字节,也即可以不足16个字节,但不会超过16个字节,即使你传递的是20个字节,也和传递16个字节计算结果一样。
另外需要注意这里传入的盐值字符串需要带有ID号以指明使用的摘要算法,否则运行会崩溃。

最后对应计算结果返回的长度如下:

算法 长度
MD5 22个字节
SHA-256 43个字节
SHA-512 86个字节

需要注意这是crypt接口使用对应摘要算法计算的加密密码结果长度,不代表摘要算法返回的结果长度,比如单纯的MD5摘要算法计算结果就是默认128位,也即16个字节,但是通过摘要算法生成的数据会存在不可打印字符,所以一般摘要算法结果会再做一层转换,常见的有对摘要计算结果进行base64编码。

下面是简单的使用示例代码,注意编译时需要链接 crypt库。

int main(void)
{
	char *endata = NULL;
	const char *pass = "12345678";
	const char *salt = "$6$abcdabcdabcdabcd";

	endata = crypt(pass, salt);
	if (NULL == endata)
	{
		printf("[FILE:%s] [FUNC:%s] [Line:%d]    \n", __FILE__, __func__, __LINE__ );
		return -1;
	}

	printf("endata:%s\n", endata);

	return 0;
}