Hash,一般翻译做”散列“,也有直接音译为”哈希“的,就是把任意长度的输入通过散列算法变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值得空间通常远小于输入的空间,不同输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。
Hash的特点
1.算法是公开
2.对相同数据运算,得到的结果是一样的
3.对不同数据运算,如MD5得到的结果默认是128位,32个字符(16进制标识)
4.没法逆运算
5.信息摘要,信息”指纹“,是用来做数据识别的
Hash的使用场景1 - 登录密码加密
我们在开发的过程中首次登录需要向服务器发送用户密码进行账户验证。但是用户的密码是非常隐私的信息,所以一定要使用加密保护。
1.直接使用Hash
那么目前最优的解决方案就是使用密码的Hash值进行验证。
客户端
直接将用户输入的密码进行Hash运算,得到结果发送给服务器验证。因为Hash算法无法逆运算,所以就算Hash值泄漏,用户真实密码也不会泄漏。
服务端
需要服务器配合,在用户注册的时候,服务端的数据库中保存的就是用户密码的Hash值,而不是密码本身(根据Hash的特点,对相同的数据加密结果是一样的)。这样计算服务器被攻克,用户的隐私信息也能起到一定的保护作用。
这也是现在各类产品只提供重置密码
功能,而不再提供找回密码
的功能了。因为服务端本身也不知道用户的真实密码。
特别说明:用户的密码属于非常隐私的信息。因为大多数用户有一个特点,密码喜欢使用重复的。如果你的APP泄露了用户的密码,那么很有可能,黑客利用用户的手机号码加上密码,可以套出用户的支付信息。这种后果是非常严重的。
2.再加一点点东西
上面所说的案例理论上已经非常“安全”了,因为就算黑客知道了你的Hash值,也没法逆运算出用户的密码。但情况并不乐观,下面我们就来看看。
以MD5为例:在终端上演示一下(比如我的密码是123456)
$md5 -s "123456"
MD5的结果是:e10adc3949ba59abbe56e057f20f883e
接下来隆重介绍一个网站http://www.cmd5.com/
我们只需要将Hash值进行反向的查询。
那么问题来了,Hash既然不能反算为何这个网站能够查询出来?仔细看下网站的介绍会发现:其实它是一个巨大的数据库,利用明文和Hash的数据记录,进行反向查询。
当然,提供哈希反向查询服务的不仅仅只有这个网站,还有很多盈利性的公司提供有偿服务。所以如果我们单纯的直接使用Hash算法,用户的密码安全性会非常低。
于是出现了早期的解决方案加盐
//用户密码
NSString * pwd = @"123456";
//足够复杂 足够长!!
static NSString * salt = @")@#(*URJ(@FJ_(@JF_(IJEFIOJ_@(IJWD{OIJW_(DIJ!W";
//先将明文拼接一个盐
pwd = [pwd stringByAppendingString:salt];
//再进行Hash算法
const char *str = pwd.UTF8String;
uint8_t buffer[CC_MD5_DIGEST_LENGTH];
CC_MD5(str, (CC_LONG)strlen(str), buffer);
NSMutableString *md5Str = [NSMutableString string];
for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {
[md5Str appendFormat:@"%02x", buffer[i]];
}
这种方式,对于反向查询来说就比较困难了,安全系数也相对较高。
3.HMAC(Hash-based Message Authentication Code)
对于简单的使用盐的方式还是会有安全隐患,因为如果盐被泄漏了。那么整个项目将陷入被动。因为这种方式将盐写死在程序里面了,要想今后换掉是比较麻烦的。
接下来介绍一种加密方案HMAC。它使用一个密钥,并且做了两次散列。
注意的是
:在开发过程中,这个密钥KEY是从服务器获取的,并且一个用户对应一个KEY。
上代码
//用户密码
NSStirng * pwd = @"123456";
//加密用的KEY,注意是从服务器获取的
NSString * key = @"hmackey";
//转成C串
const char *keyData = key.UTF8String;
const char *strData = pwd.UTF8String;
uint8_t buffer[CC_MD5_DIGEST_LENGTH];
//hmac加密
CCHmac(kCCHmacAlgSHA1, keyData, strlen(keyData), strData, strlen(strData), buffer);
NSMutableString *hmacStr = [NSMutableString string];
for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {
[hmacStr appendFormat:@"%02x", buffer[i]];
}
对于这种加密方案,就可以很好的保护用户的隐私信息。因为就算泄露了KEY。这个KEY也只是一个用户的,不会污染整个项目。
所谓安全,无法做到绝对安全。他们灰产有句话:只要钱到位,没有什么不可能。我们要做到的就是相对安全。让破解的成本大于破解的利润。
4.加时间戳
刚才我们一直停留在黑客想要拿到用户的真实密码,那么如果黑客换了一个思路,只是想要拿到用户的登录权限呢?
那么我们这种加密,无论怎么加,都是给服务器一个Hash串进行验证,也就是黑客只要拿到你的Hash值,就可以模拟你的客户端进行登录。
这时候我们可以这样做。
注册的过程
还是一样,服务器保存的还是一串HMAC加密之后的HASH值,进行校验。但是登录时的验证做点修改。
客户端
1.通过服务器的KEY进行HMAC加密,得到HMAC的Hash串
2.将得到的Hash串拼接一个时间字符串,如@"201807102248",一般到分
3.然后将这个拼接完成的串,再次Hash,将这个结果发给服务器验证。
服务端
服务器保存了HMAC的Hash串,以同样的算法,拼接服务器的时间,进行运算,然后校验。比如时间是59秒发送的请求,服务器收到请求时正好跳过一个分钟,过程如下:
1.(服务器的Hash串+@"201807102249")Hash,这次不通过,再来一次。
2.(服务器的Hash串+@"201807102248")Hash,和上一分钟对比,一次通过算成功。
这样的好处是,可以做到每登录一次发给服务器的Hash值都是不一样的,黑客不能通过保存Hash值模拟登录。