iOS:密码学(哈希算法)
iOS中的hash算法
概要
常见的加密算法有三种,哈希算法(散列函数),对称加密算法,以及非对称加密算法。三种加密算法分别有不同的应用场景,最常用的是综合搭配使用。
本篇内容主要说明Hash算法的使用。
算法特点
- 算法公开;
- 对不同数据的加密结果是定长的32位字符;
- 加密不可逆;
- 破解方式:散列碰撞,找出两个不同的数据,加密之后得到相同的Hash值。
经典算法
- MD5;
- SHA1(256/512);
- HMAC。
MD5
特点
- MD5(“信息指纹”,“信息-摘要”算法);
- 压缩性 : 任意长度的数据,算出的 MD5 值长度都是固定的;
- 容易计算 : 从原数据计算出 MD5 值容易且高效;
- 抗修改性 : 对原数据进行任何改动,哪怕只修改一个字节,所得到的 MD5 值都有很大区别;
- 弱抗碰撞 : 已知原数据和其 MD5 值,想找到一个具有相同 MD5 值的数据(即伪造数据)是非常困难的;
- 强抗碰撞: 想找到两个不同数据,使他们具有相同的 MD5 值,是非常困难的。
应用
- 密码加密;
- 利用 MD5 来进行文件校验,被大量应用在软件下载站,论坛数据库,系统文件安全等方面(是否认为添加木马,篡改文件内容等);
- 搜索引擎:iOS Sean 与 Sean iOS ,将关键字分别MD5后进行位运算可得相同结果;
- 云盘的秒传,根据MD5判断文件是否存在于服务器中;
- 一致性验证:MD5将整个文件当做一个大文本信息,通过不可逆的字符串变换算法,产生一个唯一的MD5信息摘要.就像每个人都有自己独一无二的指纹,MD5对任何文件产生一个独一无二的数字指纹;
- 数字签名;
- 安全访问认证。
SHA1(256/512)
特点
SHA1(256/512)是继MD5之后的算法,一定程度上是MD5的替代
应用
SHA1(256/512)加密
HMAC
特点
在MD5和SHA1之上的补充,用一个密钥加密,然后做两次散列
应用
- 加密;
- 设备锁(KEY保存到钥匙串中,以KEY为设备锁的依据)
实例
MD5直接加密
我们通过代码模拟一下注册登录的流程;直接MD5加密简单直接,但安全系数低,一般不单独采用。
首先定义常量:
~~~objective-c
static NSString *ServerReceivedAccount;
static NSString *ServerReceivedPassword;
~~~
然后模拟注册流程:
~~~objective-c
/**
* 注册加密处理
*/
- (void)registerBasedMD5 {
/// 本地账号密码处理
NSString * account = @"18918186688";
NSString * password = @"Sean12345678";
password = password.md5String;
/// 发送到服务器接收
ServerReceivedAccount = account;
ServerReceivedPassword = password;
NSLog(@"The account server received is:%@", ServerReceivedAccount);
NSLog(@"The password server received is:%@", ServerReceivedPassword);
}
~~~
再模拟登陆:
~~~objective-c
/**
* 登录加密处理
*/
- (void)loginBasedMD5 {
/// 本地账号密码处理
NSString * account = @"18918186688";
NSString * password = @"Sean12345678";
password = password.md5String;
/// 发送到服务器接收后,服务器做验证
if ([account isEqualToString:ServerReceivedAccount] && [password isEqualToString:ServerReceivedPassword]) {
NSLog(@"登陆成功");
} else {
NSLog(@"登陆失败");
}
}
~~~
调用:
~~~objective-c
/**
* 注册登录流程模拟
*/
- (void)handleUserAccountPasswordBasedMD5 {
[self registerBasedMD5];
[self loginBasedMD5];
}
~~~
加盐后MD5
密码加盐后MD5加密比单纯加密要安全,但是弊端很明显:盐写死在程序,要更换盐代价很大,而且盐的泄漏风险很高。
首先定义常量:
~~~objective-c
static NSString * ServerReceivedAccount;
static NSString * ServerReceivedPassword;
///盐:盐要足够长,足够复杂.
static NSString * salt = @"THIBSNNABKDAINDND91i2e9uqENSDDSJNDJNJSDNJ81838jaw92";
~~~
然后模拟注册流程:
~~~objective-c
/**
* 注册加密处理
*/
- (void)registerBasedSaltMD5 {
/// 本地账号密码处理
NSString * account = @"18918186688";
NSString * password = @"Sean12345678";
password = [password stringByAppendingString:salt].md5String;
/// 发送到服务器接收
ServerReceivedAccount = account;
ServerReceivedPassword = password;
NSLog(@"The account server received is:%@", ServerReceivedAccount);
NSLog(@"The password server received is:%@", ServerReceivedPassword);
}
~~~
再模拟登陆:
~~~objective-c
/**
* 登录加密处理
*/
- (void)loginBasedSaltMD5 {
/// 本地账号密码处理
NSString * account = @"18918186688";
NSString * password = @"Sean12345678";
password = [password stringByAppendingString:salt].md5String;
/// 发送到服务器接收后,服务器做验证
if ([account isEqualToString:ServerReceivedAccount] && [password isEqualToString:ServerReceivedPassword]) {
NSLog(@"登陆成功");
} else {
NSLog(@"登陆失败");
}
}
~~~
调用:
~~~objective-c
/**
* 注册登录流程模拟
*/
- (void)handleUserAccountPasswordBasedSaltMD5 {
[self registerBasedSaltMD5];
[self loginBasedSaltMD5];
}
~~~
HMAC加密
HMAC加密,KEY从服务器获取,此种加密相对较为安全,但同样有漏洞:网络传输过程中加密的HMAC字符串很容易泄漏,进而被攻击者模拟用户行为进行登录,获取用户数据。
首先定义常量:
~~~objective-c
#define KEY_ACCOUNTKEY @"KEY_ACCOUNTKEY"
#define BUNDLEID [[NSBundle mainBundle] bundleIdentifier]
static NSString * ServerReceivedAccount;
static NSString * ServerReceivedPassword;
~~~
然后模拟注册流程:
~~~objective-c
/**
* 注册加密处理
*/
- (void)registerBasedHMAC {
/// 从服务器获取Key并保存到keychain
NSString * key = @"useraccount281818";
[SAMKeychain setPassword:key forService:BUNDLEID account:KEY_ACCOUNTKEY];
/// 本地账号密码处理
NSString * account = @"18918186688";
NSString * password = @"Sean12345678";
password = [password hmacMD5StringWithKey:key];
/// 发送到服务器接收
ServerReceivedAccount = account;
ServerReceivedPassword = password;
NSLog(@"The account server received is:%@", ServerReceivedAccount);
NSLog(@"The password server received is:%@", ServerReceivedPassword);
}
~~~
再模拟登陆:
~~~objective-c
/**
* 登录加密处理
*/
- (void)loginBasedHMAC {
/// 本地账号密码处理
NSString * account = @"18918186688";
NSString * password = @"Sean12345678";
NSString * key = [SAMKeychain passwordForService:BUNDLEID account:KEY_ACCOUNTKEY];
if (!key) { // 从服务器获取Key并保存到keychain
key = @"useraccount281818";
[SAMKeychain setPassword:key forService:BUNDLEID account:KEY_ACCOUNTKEY];
}
password = [password hmacMD5StringWithKey:key];
/// 发送到服务器接收后,服务器做验证
if ([account isEqualToString:ServerReceivedAccount] && [password isEqualToString:ServerReceivedPassword]) {
NSLog(@"登陆成功");
} else {
NSLog(@"登陆失败");
}
}
~~~
调用:
~~~objective-c
/**
* 注册登录流程模拟
*/
- (void)handleUserAccountPasswordBasedHMAC {
[self registerBasedHMAC];
[self loginBasedHMAC];
}
~~~
HMAC+时间戳再MD5
密码+key进行HMAC加密,后拼接时间戳,然后再整体进行MD5加密。 这种方式有时间限定,加密的结果受时间影响很大,即使黑客获取密文模仿用户进行登录,也必须在2分钟内完成此操作,很大程度上增加了安全性。一般采用这种方式进行注册登录流程的加密。
首先定义常量:
~~~objective-c
static NSString * ServerReceivedAccount;
static NSString * ServerReceivedPassword;
~~~
然后模拟注册流程:
~~~objective-c
/**
* 注册加密处理:(HMAC加密+时间戳)再MD5加密(注册时发送给服务器的密码是不添加时间戳和再MD5处理的)
*/
- (void)registerBasedHMACTimeStamp {
/// 从服务器获取Key并保存到keychain
NSString * key = @"useraccount281818";
[SAMKeychain setPassword:key forService:BUNDLEID account:KEY_ACCOUNTKEY];
/// 本地账号密码处理
NSString * account = @"18918186688";
NSString * password = @"Sean12345678";
password = [password hmacMD5StringWithKey:key];
/// 发送到服务器接收
ServerReceivedAccount = account;
ServerReceivedPassword = password;
NSLog(@"The account server received is:%@", ServerReceivedAccount);
NSLog(@"The password server received is:%@", ServerReceivedPassword);
}
~~~
再模拟登陆:
~~~objective-c
/**
* 登录加密处理:(HMAC加密+时间戳)再MD5加密
*/
- (void)loginBasedHMACTimeStamp {
/// 本地账号密码处理
NSString * account = @"18918186688";
NSString * password = @"Sean12345678";
NSString * key = [SAMKeychain passwordForService:BUNDLEID account:KEY_ACCOUNTKEY];
if (!key) { // 从服务器获取Key并保存到keychain
key = @"useraccount281818";
[SAMKeychain setPassword:key forService:BUNDLEID account:KEY_ACCOUNTKEY];
}
/// 加时间戳
NSDateFormatter * dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"YY-MM-dd hh:mm"];
NSString * timeScale = [dateFormatter stringFromDate:[NSDate dateWithTimeIntervalSinceNow:0]];
password = [[password hmacMD5StringWithKey:key] stringByAppendingString:timeScale].md5String;
/// 发送到服务器验证
BOOL isAccount = [account isEqualToString:ServerReceivedAccount];
/// 服务器添加时间戳后验证
NSString * timeScaleNow = [dateFormatter stringFromDate:[NSDate dateWithTimeIntervalSinceNow:0]];
NSString * timeScaleLast = [dateFormatter stringFromDate:[NSDate dateWithTimeIntervalSinceNow:0 - 60]];
NSString * passwordNow = [ServerReceivedPassword stringByAppendingString:timeScaleNow].md5String;
NSString * passwordLast = [ServerReceivedPassword stringByAppendingString:timeScaleLast].md5String;
BOOL isPassword = [password isEqualToString:passwordNow] || [password isEqualToString:passwordLast];
if (isAccount && isPassword) {
NSLog(@"登陆成功");
} else {
NSLog(@"登陆失败");
}
}
~~~
调用:
~~~objective-c
/**
* 注册登录流程模拟:(HMAC加密+时间戳)再MD5加密
*/
- (void)handleUserAccountPasswordBasedHMACTimeStamp {
[self registerBasedHMACTimeStamp];
[self loginBasedHMACTimeStamp];
}
~~~
OC的Hash封装
代码实例作为NSData的Category存在,首先导入头文件:
~~~objective-c
#import "NSData+HASH.h"
#include <CommonCrypto/CommonDigest.h>
~~~
定义枚举:
~~~objective-c
typedef enum : NSUInteger {
//md2 16字节长度
CCDIGEST_MD2 = 1000,
//md4 16字节长度
CCDIGEST_MD4,
//md5 16字节长度
CCDIGEST_MD5,
//sha1 20字节长度
CCDIGEST_SHA1,
//SHA224 28字节长度
CCDIGEST_SHA224,
//SHA256 32字节长度
CCDIGEST_SHA256,
//SHA384 48字节长度
CCDIGEST_SHA384,
//SHA512 64字节长度
CCDIGEST_SHA512,
} CCDIGESTAlgorithm;
~~~
返回NSData对应的Hash字符串:
~~~objective-c
/**
返回data的Hash字符串
@return hashString
*/
- (NSString *)hashString {
NSMutableString *result = nil;
if (self.length <1) {
return nil;
}
result = [[NSMutableString alloc] initWithCapacity:self.length * 2];
for (size_t i = 0; i < self.length; i++) {
[result appendFormat:@"%02x", ((const uint8_t *) self.bytes)[i]];
}
return result;
}
~~~
返回 hash string的 data:
~~~objective-c
/**
返回 hash string的 data
@param hexString hash string
@return data
*/
+ (NSData *)dataWithHexString:(NSString *)hexString {
NSParameterAssert(hexString != nil);
NSMutableData * result;
NSUInteger cursor;
NSUInteger limit;
result = nil;
cursor = 0;
limit = hexString.length;
if ((limit % 2) == 0) {
result = [[NSMutableData alloc] init];
while (cursor != limit) {
unsigned int thisUInt;
uint8_t thisByte;
if (sscanf([hexString substringWithRange:NSMakeRange(cursor, 2)].UTF8String, "%x", &thisUInt) != 1){
result = nil;
break;
}
thisByte = (uint8_t) thisUInt;
[result appendBytes:&thisByte length:sizeof(thisByte)];
cursor += 2;
}
}
return result;
}
~~~
根据不同的算法计算数据的hash值:
~~~objective-c
/**
根据不同的算法计算数据的hash值
@param ccAlgorithm 算法参数
@return 加密data
*/
- (NSData *)hashDataWith:(CCDIGESTAlgorithm )ccAlgorithm {
NSData *retData = nil;
if (self.length <1) {
return nil;
}
unsigned char *md;
switch (ccAlgorithm) {
case CCDIGEST_MD2:
{
md = malloc(CC_MD2_DIGEST_LENGTH);
bzero(md, CC_MD2_DIGEST_LENGTH);
CC_MD2(self.bytes, (CC_LONG)self.length, md);
retData = [NSData dataWithBytes:md length:CC_MD2_DIGEST_LENGTH];
}
break;
case CCDIGEST_MD4:
{
md = malloc(CC_MD4_DIGEST_LENGTH);
bzero(md, CC_MD4_DIGEST_LENGTH);
CC_MD4(self.bytes, (CC_LONG)self.length, md);
retData = [NSData dataWithBytes:md length:CC_MD4_DIGEST_LENGTH];
}
break;
case CCDIGEST_MD5:
{
md = malloc(CC_MD5_DIGEST_LENGTH);
bzero(md, CC_MD5_DIGEST_LENGTH);
CC_MD5(self.bytes, (CC_LONG)self.length, md);
retData = [NSData dataWithBytes:md length:CC_MD5_DIGEST_LENGTH];
}
break;
case CCDIGEST_SHA1:
{
md = malloc(CC_SHA1_DIGEST_LENGTH);
bzero(md, CC_SHA1_DIGEST_LENGTH);
CC_SHA1(self.bytes, (CC_LONG)self.length, md);
retData = [NSData dataWithBytes:md length:CC_SHA1_DIGEST_LENGTH];
}
break;
case CCDIGEST_SHA224:
{
md = malloc(CC_SHA224_DIGEST_LENGTH);
bzero(md, CC_SHA224_DIGEST_LENGTH);
CC_SHA224(self.bytes, (CC_LONG)self.length, md);
retData = [NSData dataWithBytes:md length:CC_SHA224_DIGEST_LENGTH];
}
break;
case CCDIGEST_SHA256:
{
md = malloc(CC_SHA256_DIGEST_LENGTH);
bzero(md, CC_SHA256_DIGEST_LENGTH);
CC_SHA256(self.bytes, (CC_LONG)self.length, md);
retData = [NSData dataWithBytes:md length:CC_SHA256_DIGEST_LENGTH];
}
break;
case CCDIGEST_SHA384:
{
md = malloc(CC_SHA384_DIGEST_LENGTH);
bzero(md, CC_SHA384_DIGEST_LENGTH);
CC_SHA384(self.bytes, (CC_LONG)self.length, md);
retData = [NSData dataWithBytes:md length:CC_SHA384_DIGEST_LENGTH];
}
break;
case CCDIGEST_SHA512:
{
md = malloc(CC_SHA512_DIGEST_LENGTH);
bzero(md, CC_SHA512_DIGEST_LENGTH);
CC_SHA512(self.bytes, (CC_LONG)self.length, md);
retData = [NSData dataWithBytes:md length:CC_SHA512_DIGEST_LENGTH];
}
break;
default:
md = malloc(1);
break;
}
free(md);
md = NULL;
return retData;
}
~~~
base64编码进行网络传输:
~~~objective-c
/** 将传入的二进制数据,编码成base64格式的字符串
@param data 需要编码的二进制数据
@return base64编码以后的string
*/
static NSString *base64_encode_data(NSData *data) {
data = [data base64EncodedDataWithOptions:0];
if (!data) return @"";
NSString *ret = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
return ret;
}
/** 将base64编码的String,解码成二进制数据
@param str base64编码以后的数据
@return 原始二进制数据
*/
static NSData *base64_decode(NSString *str) {
NSData *data = [[NSData alloc] initWithBase64EncodedString:str options:NSDataBase64DecodingIgnoreUnknownCharacters];
return data;
}
~~~
总结
md5,sha1,hmac,等算法,在不同的场景适于不同的使用方式,加密并不是一成不变的固定套路,要根据具体业务探索适合的加密方案;
安全方面的攻击和防御,是一场不对等的战争,防者千虑,可能也免不了疏忽,作为一线技术,能做的就是最大化的保证应用安全,而企业级的项目安全,需要的是成体系的安全防范方案,哈希加密只是其中的一部分。