iOS:密码学(哈希算法)

Page content

iOS中的hash算法

概要

常见的加密算法有三种,哈希算法(散列函数),对称加密算法,以及非对称加密算法。三种加密算法分别有不同的应用场景,最常用的是综合搭配使用。

本篇内容主要说明Hash算法的使用。

算法特点

  1. 算法公开;
  2. 对不同数据的加密结果是定长的32位字符;
  3. 加密不可逆;
  4. 破解方式:散列碰撞,找出两个不同的数据,加密之后得到相同的Hash值。

经典算法

  1. MD5;
  2. SHA1(256/512);
  3. HMAC。

MD5

特点

  1. MD5(“信息指纹”,“信息-摘要”算法);
  2. 压缩性 : 任意长度的数据,算出的 MD5 值长度都是固定的;
  3. 容易计算 : 从原数据计算出 MD5 值容易且高效;
  4. 抗修改性 : 对原数据进行任何改动,哪怕只修改一个字节,所得到的 MD5 值都有很大区别;
  5. 弱抗碰撞 : 已知原数据和其 MD5 值,想找到一个具有相同 MD5 值的数据(即伪造数据)是非常困难的;
  6. 强抗碰撞: 想找到两个不同数据,使他们具有相同的 MD5 值,是非常困难的。

应用

  1. 密码加密;
  2. 利用 MD5 来进行文件校验,被大量应用在软件下载站,论坛数据库,系统文件安全等方面(是否认为添加木马,篡改文件内容等);
  3. 搜索引擎:iOS Sean 与 Sean iOS ,将关键字分别MD5后进行位运算可得相同结果;
  4. 云盘的秒传,根据MD5判断文件是否存在于服务器中;
  5. 一致性验证:MD5将整个文件当做一个大文本信息,通过不可逆的字符串变换算法,产生一个唯一的MD5信息摘要.就像每个人都有自己独一无二的指纹,MD5对任何文件产生一个独一无二的数字指纹;
  6. 数字签名;
  7. 安全访问认证。

SHA1(256/512)

特点

SHA1(256/512)是继MD5之后的算法,一定程度上是MD5的替代

应用

SHA1(256/512)加密

HMAC

特点

在MD5和SHA1之上的补充,用一个密钥加密,然后做两次散列

应用

  1. 加密;
  2. 设备锁(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,等算法,在不同的场景适于不同的使用方式,加密并不是一成不变的固定套路,要根据具体业务探索适合的加密方案;

安全方面的攻击和防御,是一场不对等的战争,防者千虑,可能也免不了疏忽,作为一线技术,能做的就是最大化的保证应用安全,而企业级的项目安全,需要的是成体系的安全防范方案,哈希加密只是其中的一部分。