本文主要介绍Hash算法
Hash介绍
Hash,一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度的输入通过散列算法变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。
简单来说,hash算法(即散列函数),是一种单向密码*,即它是一个从明文到密文的不可逆的映射,只有加密过程,没有解密过程。同时,哈希函数可以将任意长度的输入经过变化以后得到固定长度的输出。哈希函数的这种单向特征和输出数据长度固定的特征使得它可以生成消息或者数据。
Hash的特点
-
算法是公开的
-
对相同数据运算,得到的结果是一样的(同样的数据,得到的结果是一样的)
-
对不同数据运算,得到的结果是定长的,如MD5得到的结果默认是128位,32个字符(16进制标识)。
-
无法逆运算
-
是信息摘要、信息“指纹”,是用来做数据识别的、完整性检查的。
Hash用途
-
1、用户密码的加密
-
2、搜索引擎
-
3、版权
-
4、数字签名
-
......
数据传输/存储原则:网络传输数据 + 本地保存数据 + 服务端保存数据等 ,这些是隐私数据,绝对不能明文,一定是密文传输/存储。
常见的Hash算法
常见的hash算法主要有MD5、SHA-1、SHA-256、SHA-512等
MD5
MD4(RFC 1320)是 MIT 的Ronald L. Rivest在 1990 年设计的,MD 是 Message Digest(消息摘要) 的缩写。它适用在32位字长的处理器上用高速软件实现——它是基于 32位操作数的位操作来实现的。
MD5(RFC 1321)是 Rivest 于1991年对MD4的改进版本。它对输入仍以512位分组,其输出是4个32位字的级联,与 MD4 相同。MD5比MD4来得复杂,并且速度较之要慢一点,但更安全,在抗分析和抗差分方面表现更好。
Message Digest Algorithm MD5(消息摘要算法5)为计算机安全领域广泛使用的一种散列函数,用以提供消息的完整性保护。是计算机广泛使用的杂凑算法之一,将数据(如汉字)运算为另一固定长度值,是杂凑算法的基础原理,MD5的前身有MD2、MD3和MD4。
特点
-
1、压缩性:任意长度的数据,算出的MD5值长度都是固定的。
-
2、容易计算:从原数据计算出MD5值很容易。
-
3、抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别。
-
4、强抗碰撞:已知原数据和其MD5值,想找到一个具有相同MD5值的数据(即伪造数据)是非常困难的。
应用场景
-
一致性验证
-
数字签名
-
安全访问认证
SHA-1、SHA-256、SHA-512
安全哈希算法(Secure Hash Algorithm)主要适用于数字签名标准里面定义的数字签名算法(Digital Signature Algorithm DSA)。对于长度小于2^64位的消息,SHA1会产生一个160位的消息摘要。当接收到消息的时候,这个消息摘要可以用来验证数据的完整性。在传输的过程中,数据很可能会发生变化,那么这时候就会产生不同的消息摘要。
SHA1是由NIST NSA设计为同DSA一起使用的,它对长度小于264的输入,产生长度为160bit的散列值,因此抗穷举(brute-force)性更好。SHA-1 设计时基于和MD4相同原理,并且模仿了该算法。
SHA-2 SHA-224、SHA-256、SHA-384,和SHA-512并称为SHA-2。新的哈希函数并没有接受像SHA-1一样的公众密码社区做详细的检验,所以它们的密码安全性还不被大家广泛的信任。虽然至今尚未出现对SHA-2有效的***,它的算法跟SHA-1基本上仍然相似;因此有些人开始发展其他替代的哈希算法。
注:这里只做简单介绍,并不展开详细说明,有兴趣的可以参考以下链接
常见的hash算法及其原理
常用哈希函数介绍
安全系列之——主流Hash散列算法介绍和使用
密码加密
这里我们分别来对比以下四种,对密码的加密方式
-
1、直接使用MD5(信息摘要算法)
-
2、MD5加盐
-
3、HMAC加密方案
-
4、添点东西
准备工作
在进行演示前,首先封装常用的Hash算法
<!--h文件-->
#import <Foundation/Foundation.h>
@interface NSString (Hash)
#pragma mark - 散列函数
/**
* 计算MD5散列结果
*
* 终端测试命令:
* @code
* md5 -s "string"
* @endcode
*
* <p>提示:随着 MD5 碰撞生成器的出现,MD5 算法不应被用于任何软件完整性检查或代码签名的用途。<p>
*
* @return 32个字符的MD5散列字符串
*/
- (NSString *)md5String;
/**
* 计算SHA1散列结果
*
* 终端测试命令:
* @code
* echo -n "string" | openssl sha1
* @endcode
*
* @return 40个字符的SHA1散列字符串
*/
- (NSString *)sha1String;
/**
* 计算SHA256散列结果
*
* 终端测试命令:
* @code
* echo -n "string" | openssl sha -sha256
* @endcode
*
* @return 64个字符的SHA256散列字符串
*/
- (NSString *)sha256String;
/**
* 计算SHA 512散列结果
*
* 终端测试命令:
* @code
* echo -n "string" | openssl sha -sha512
* @endcode
*
* @return 128个字符的SHA 512散列字符串
*/
- (NSString *)sha512String;
#pragma mark - HMAC 散列函数
/**
* 计算HMAC MD5散列结果
*
* 终端测试命令:
* @code
* echo -n "string" | openssl dgst -md5 -hmac "key"
* @endcode
*
* @return 32个字符的HMAC MD5散列字符串
*/
- (NSString *)hmacMD5StringWithKey:(NSString *)key;
/**
* 计算HMAC SHA1散列结果
*
* 终端测试命令:
* @code
* echo -n "string" | openssl sha -sha1 -hmac "key"
* @endcode
*
* @return 40个字符的HMAC SHA1散列字符串
*/
- (NSString *)hmacSHA1StringWithKey:(NSString *)key;
/**
* 计算HMAC SHA256散列结果
*
* 终端测试命令:
* @code
* echo -n "string" | openssl sha -sha256 -hmac "key"
* @endcode
*
* @return 64个字符的HMAC SHA256散列字符串
*/
- (NSString *)hmacSHA256StringWithKey:(NSString *)key;
/**
* 计算HMAC SHA512散列结果
*
* 终端测试命令:
* @code
* echo -n "string" | openssl sha -sha512 -hmac "key"
* @endcode
*
* @return 128个字符的HMAC SHA512散列字符串
*/
- (NSString *)hmacSHA512StringWithKey:(NSString *)key;
#pragma mark - 文件散列函数
/**
* 计算文件的MD5散列结果
*
* 终端测试命令:
* @code
* md5 file.dat
* @endcode
*
* @return 32个字符的MD5散列字符串
*/
- (NSString *)fileMD5Hash;
/**
* 计算文件的SHA1散列结果
*
* 终端测试命令:
* @code
* openssl sha -sha1 file.dat
* @endcode
*
* @return 40个字符的SHA1散列字符串
*/
- (NSString *)fileSHA1Hash;
/**
* 计算文件的SHA256散列结果
*
* 终端测试命令:
* @code
* openssl sha -sha256 file.dat
* @endcode
*
* @return 64个字符的SHA256散列字符串
*/
- (NSString *)fileSHA256Hash;
/**
* 计算文件的SHA512散列结果
*
* 终端测试命令:
* @code
* openssl sha -sha512 file.dat
* @endcode
*
* @return 128个字符的SHA512散列字符串
*/
- (NSString *)fileSHA512Hash;
@end
<!--m文件-->
#import "NSString+Hash.h"
#import <CommonCrypto/CommonCrypto.h>
@implementation NSString (Hash)
#pragma mark - 散列函数
- (NSString *)md5String {
const char *str = self.UTF8String;
uint8_t buffer[CC_MD5_DIGEST_LENGTH];
CC_MD5(str, (CC_LONG)strlen(str), buffer);
return [self stringFromBytes:buffer length:CC_MD5_DIGEST_LENGTH];
}
- (NSString *)sha1String {
const char *str = self.UTF8String;
uint8_t buffer[CC_SHA1_DIGEST_LENGTH];
CC_SHA1(str, (CC_LONG)strlen(str), buffer);
return [self stringFromBytes:buffer length:CC_SHA1_DIGEST_LENGTH];
}
- (NSString *)sha256String {
const char *str = self.UTF8String;
uint8_t buffer[CC_SHA256_DIGEST_LENGTH];
CC_SHA256(str, (CC_LONG)strlen(str), buffer);
return [self stringFromBytes:buffer length:CC_SHA256_DIGEST_LENGTH];
}
- (NSString *)sha512String {
const char *str = self.UTF8String;
uint8_t buffer[CC_SHA512_DIGEST_LENGTH];
CC_SHA512(str, (CC_LONG)strlen(str), buffer);
return [self stringFromBytes:buffer length:CC_SHA512_DIGEST_LENGTH];
}
#pragma mark - HMAC 散列函数
- (NSString *)hmacMD5StringWithKey:(NSString *)key {
const char *keyData = key.UTF8String;
const char *strData = self.UTF8String;
uint8_t buffer[CC_MD5_DIGEST_LENGTH];
CCHmac(kCCHmacAlgMD5, keyData, strlen(keyData), strData, strlen(strData), buffer);
return [self stringFromBytes:buffer length:CC_MD5_DIGEST_LENGTH];
}
- (NSString *)hmacSHA1StringWithKey:(NSString *)key {
const char *keyData = key.UTF8String;
const char *strData = self.UTF8String;
uint8_t buffer[CC_SHA1_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA1, keyData, strlen(keyData), strData, strlen(strData), buffer);
return [self stringFromBytes:buffer length:CC_SHA1_DIGEST_LENGTH];
}
- (NSString *)hmacSHA256StringWithKey:(NSString *)key {
const char *keyData = key.UTF8String;
const char *strData = self.UTF8String;
uint8_t buffer[CC_SHA256_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA256, keyData, strlen(keyData), strData, strlen(strData), buffer);
return [self stringFromBytes:buffer length:CC_SHA256_DIGEST_LENGTH];
}
- (NSString *)hmacSHA512StringWithKey:(NSString *)key {
const char *keyData = key.UTF8String;
const char *strData = self.UTF8String;
uint8_t buffer[CC_SHA512_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA512, keyData, strlen(keyData), strData, strlen(strData), buffer);
return [self stringFromBytes:buffer length:CC_SHA512_DIGEST_LENGTH];
}
#pragma mark - 文件散列函数
#define FileHashDefaultChunkSizeForReadingData 4096
- (NSString *)fileMD5Hash {
NSFileHandle *fp = [NSFileHandle fileHandleForReadingAtPath:self];
if (fp == nil) {
return nil;
}
CC_MD5_CTX hashCtx;
CC_MD5_Init(&hashCtx);
while (YES) {
@autoreleasepool {
NSData *data = [fp readDataOfLength:FileHashDefaultChunkSizeForReadingData];
CC_MD5_Update(&hashCtx, data.bytes, (CC_LONG)data.length);
if (data.length == 0) {
break;
}
}
}
[fp closeFile];
uint8_t buffer[CC_MD5_DIGEST_LENGTH];
CC_MD5_Final(buffer, &hashCtx);
return [self stringFromBytes:buffer length:CC_MD5_DIGEST_LENGTH];
}
- (NSString *)fileSHA1Hash {
NSFileHandle *fp = [NSFileHandle fileHandleForReadingAtPath:self];
if (fp == nil) {
return nil;
}
CC_SHA1_CTX hashCtx;
CC_SHA1_Init(&hashCtx);
while (YES) {
@autoreleasepool {
NSData *data = [fp readDataOfLength:FileHashDefaultChunkSizeForReadingData];
CC_SHA1_Update(&hashCtx, data.bytes, (CC_LONG)data.length);
if (data.length == 0) {
break;
}
}
}
[fp closeFile];
uint8_t buffer[CC_SHA1_DIGEST_LENGTH];
CC_SHA1_Final(buffer, &hashCtx);
return [self stringFromBytes:buffer length:CC_SHA1_DIGEST_LENGTH];
}
- (NSString *)fileSHA256Hash {
NSFileHandle *fp = [NSFileHandle fileHandleForReadingAtPath:self];
if (fp == nil) {
return nil;
}
CC_SHA256_CTX hashCtx;
CC_SHA256_Init(&hashCtx);
while (YES) {
@autoreleasepool {
NSData *data = [fp readDataOfLength:FileHashDefaultChunkSizeForReadingData];
CC_SHA256_Update(&hashCtx, data.bytes, (CC_LONG)data.length);
if (data.length == 0) {
break;
}
}
}
[fp closeFile];
uint8_t buffer[CC_SHA256_DIGEST_LENGTH];
CC_SHA256_Final(buffer, &hashCtx);
return [self stringFromBytes:buffer length:CC_SHA256_DIGEST_LENGTH];
}
- (NSString *)fileSHA512Hash {
NSFileHandle *fp = [NSFileHandle fileHandleForReadingAtPath:self];
if (fp == nil) {
return nil;
}
CC_SHA512_CTX hashCtx;
CC_SHA512_Init(&hashCtx);
while (YES) {
@autoreleasepool {
NSData *data = [fp readDataOfLength:FileHashDefaultChunkSizeForReadingData];
CC_SHA512_Update(&hashCtx, data.bytes, (CC_LONG)data.length);
if (data.length == 0) {
break;
}
}
}
[fp closeFile];
uint8_t buffer[CC_SHA512_DIGEST_LENGTH];
CC_SHA512_Final(buffer, &hashCtx);
return [self stringFromBytes:buffer length:CC_SHA512_DIGEST_LENGTH];
}
#pragma mark - 助手方法
/**
* 返回二进制 Bytes 流的字符串表示形式
*
* @param bytes 二进制 Bytes 数组
* @param length 数组长度
*
* @return 字符串表示形式
*/
- (NSString *)stringFromBytes:(uint8_t *)bytes length:(int)length {
NSMutableString *strM = [NSMutableString string];
for (int i = 0; i < length; i++) {
[strM appendFormat:@"%02x", bytes[i]];
}
return [strM copy];
}
@end
方式一:直接使用MD5
终端演示
-
md5 -s "123456"
-
MD5 ("123456") ,结果为 e10adc3949ba59abbe56e057f20f883e
代码演示
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSString *pwd = @"123456";
//传递数据
pwd = pwd.md5String;
NSLog(@"pwd - %@", pwd);
}
<!--运行结果-->
pwd - e10adc3949ba59abbe56e057f20f883e
结论:直接使用MD5不安全,大部分MD5现在通过网站都可以还原
方式二:MD5加固定盐
在方式一的基础上,加上一个固定的字符串,代码演示如下
//加盐
static NSString *salt = @"LKSJDFLKJ";
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSString *pwd = @"123456";
//传递数据
pwd = [pwd stringByAppendingString:salt].md5String;
NSLog(@"pwd - %@", pwd);
}
<!--运行结果-->
pwd - 2cac01acb03a3cc2b96cee3aae4d2276
结论:加固定盐,现在也不安全,因为如果盐泄漏了,也会有数据泄漏的风险
方式三:HMAC加密
HMAC加密,其原理就是判断是否有key,这个key是由服务器动态提供的,保存在手机侧。简单来说,就相当于现在的授权设备,HMAC主要就是检测你的设备是否有授权。其代码演示如下
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSString *pwd = @"123456";
//传递数据
//key是动态的,由服务器提供(即一个账号一个key)
pwd = [pwd hmacMD5StringWithKey:@"CJL"];
NSLog(@"pwd - %@", pwd);
}
<!--打印结果-->
pwd - b123975de0f19edef5a546bf212bd918
结论:现在这种加密方式,用户密码确实安全了。但是用于服务器登录验证是一个hash值,而不是密码。所以这个hash值也有被拦截的风险。也是不安全的
方式四:HMAC + 时间戳
基于方式三的基础上,我们载增加一些东西一起加密,这个增加的就是时间戳。对于密码来说,加密方式还是采用HMAC加密。只是验证方式发生了变化。原本只是验证hash值即可。现在是在hash值的基础上加上了一个时间期限。例如hash值的有效时间最多是2分钟,超过这个时间就失效了。类似于现在验证码的验证。
主要流程如下:
-
1、客户端:对密码使用HMAC加密,得到一个hash值A,hash值带上服务器给的时间戳,求得另一个hash值B
-
2、将客户端得到的Hash值A、Hash值B发送到服务器进行验证
-
3、服务器:将传过来的Hash值A,带上服务器的时间戳,得到一个Hash值C,判断Hash值C是否等于传过来的Hash值B,如果不等于,则表示不通过,在时间范围内,再继续验证上一分钟
举例说明
假设现在用户的密码是:123456,
-
1、客户端:
-
使用HMAC加密得到的Hash值A = b123975de0f19edef5a546bf212bd918
-
HMAC哈希值 + 时间戳(假设是2021-4-10 17:15:10) 得到的hash值B = 430a60f4989e104e9e903dbdcb84c69a
-
-
2、发送到服务器的Hash值
-
Hash值A : b123975de0f19edef5a546bf212bd918
-
hash值B : 430a60f4989e104e9e903dbdcb84c69a
-
-
3、服务端:
-
HMAC哈希值 + 服务器时间戳(假设是2021-4-10 17:15:11) 得到的hash值C = 0cf1dceda0fd58aefd4c7a7b2b08e193
-
与客户端传入的hash值C不等,继续求HMAC带上上一个时间(2分钟内)的 hash值D = 430a60f4989e104e9e903dbdcb84c69a.与hash值B相等,所以验证通过
-
例子中所用的演示代码如下
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSString *pwd = @"123456";
//传递数据
pwd = [pwd hmacMD5StringWithKey:@"CJL"];//key是动态的,由服务器提供
NSLog(@"pwd - %@", pwd);
pwd = [NSString stringWithFormat:@"%@%@",pwd, [self getOtherTimeStrWithString:@"2021-4-10 17:15:10"]].md5String;
NSLog(@"pwd - %@", pwd);
}
- (NSString *)getOtherTimeStrWithString:(NSString *)formatTime{
NSLog(@"formatTime - - - - - -%@",formatTime);
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateStyle:NSDateFormatterMediumStyle];
[formatter setTimeStyle:NSDateFormatterShortStyle];
[formatter setDateFormat:@"YYYY-MM-dd HH:mm:ss"]; //(@"YYYY-MM-dd hh:mm:ss") ----------设置你想要的格式,hh与HH的区别:分别表示12小时制,24小时制
//设置时区选择北京时间
NSTimeZone* timeZone = [NSTimeZone timeZoneWithName:@"Asia/Beijing"];
[formatter setTimeZone:timeZone];
NSDate* date = [formatter dateFromString:formatTime]; //------------将字符串按formatter转成nsdate
//时间转时间戳的方法:
NSInteger timeSp = [[NSNumber numberWithDouble:[date timeIntervalSince1970]] integerValue] * 1000;
NSLog(@"将某个时间转化成 时间戳timeSp:%ld",(long)timeSp); //时间戳的值
return [NSString stringWithFormat:@"%ld",(long)timeSp];
}
数字签名
目的:验证二进制数据是不是颁发机构颁发的
早期只是给一个数据,扔给我,我就用,这是不安全的
-
1、数据报文,即原始数据
早期
后来的数字签名,为了安全,不仅给数据,还给数据的Hash值,但是还是不安全,因为数据、hash值都有可能同时篡改
-
1、数据报文,即原始数据
-
2、原始数据的Hash值
后来
现在的数字签名,除了给数据,同时还给原始数据Hash值进行RSA加密后得到的hash值,其中原始数据HASH值RSA加密后得到的值称为原始数据的 数字签名
-
1、数据报文,即原始数据
-
2、原始数据的Hash值
-
3、数字签名:原始数据Hash值通过RSA加密后得到的结果
现在
总结
-
HASH算法(散列函数)
-
不可逆运算(因为无限的数据,存在有限的表示形式,所以会出现 多个不同的数据,有同样的hash值)
-
相同的数据结果相同
-
不同的数据长度相同
-
一般用于做数据的识别,例如密码、版权、百度云数据识别
-
-
密码加密:
-
HMAC:比较好的方案,因为破解的成本 大于 破解后的获益,所以相对安全
-
HASH + 时间戳:这样的方式,每次加密结果不一样,因为受时间的影响比较大
-
-
数字签名
-
1、数据报文,即原始数据
-
2、原始数据的Hash值
-
3、数字签名:原始数据Hash值通过RSA加密后得到的结果
-
算法:RSA + HASH
-
目的:验证数据的完整性,不被篡改
-
主要逻辑:
-