在做聊天服务器的时候,有考虑过将用户名和密码按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;
}