大刘的 iOS 自学笔记

Objective-C_MD5加盐

2022-06-23  本文已影响0人  大刘

Created by 大刘 liuxing8807@126.com

MD5是哈希算法的一种, 即给定一个字符串或文件, Hash算法根据源文件本身通过一系列数学计算得到一串字符串, 不同的文件得出的Hash值不同. 所以哈希算法多用于校验, 比如我们在一些网站上下载文件时会有一些文件的哈希值, 下载下来后通过得新计算哈希值得到我们下载的文件是否被篡改过.
MD5一般对任意数据源计算, 生成32个字符固定长度的字符串, 示例:

% md5 test.png
MD5 (test.png) = b50666b631a48936bb4a3d828feaffc1
% md5 test-copy.png # test-copy.png是test.png拷贝而来
MD5 (test-copy.png) = b50666b631a48936bb4a3d828feaffc1

在比如, 对于银行卡6位取款密码, 如果这个密码丢了, 即使是银行的工作人员可能也无法知道, 因为假设使用了哈希算法, 而理论上安全性较高的哈希算法不具有可逆性,从源得出哈希值, 但是哈希值不可以导出源; 当然, 没有绝对的安全, 如果原密码设置的太简单, 反向导出原密码有很大的可能性, 网上有大量的方式可以破解MD5算法.

现在Apple已有建议使用MD5算法,调用CC_MD5方法,编译器会提出警示:

'CC_MD5' is deprecated: first deprecated in iOS 13.0 - This function is cryptographically broken and should not be used in security contexts. Clients should migrate to SHA256 (or stronger).

但是其使用场景依然很多。

加盐,其实就是在原密码的基础上加一点额外操作,而这个操作是我们自己所知道的,然后把加盐后的密码再MD5,这样即使别人解出来,也只是解出加盐后的密码,并不是原密码,相对于原来方式,这样提高了安全性。

参考雪峰,关于加盐操作,HMAC通过一个标准算法,在计算哈希的过程中,把key混入计算过程中。

另外, 由于HTTP总是无法避免被他人监听, 因此还有一种方式是在MD5过程中加上时间限制, 使密码每次都不一样. 即使其他人截获了这个密码, 但这个密码有时间限制, 如果破解的时间大于这个时间限制, 等破解之后上传到服务器, 服务器就可以判定此密码无效, 从而进一步提高安全性.
而且这种规则可以随意设计,只要是和server约定好即可. 比如我们自定义一个规则:

  1. 一个特定字符串Key, 然后md5计算 --> A
  2. 把原密码和之前生成的md5值进行hmac加密 --> B
  3. 从serve获取当前时间(客户端时间用户可以随意更改,因此统一使用server的时间) --> C
  4. 把第二步产生的hmac值+时间, 和第一步产生的md5值进行hmac加密 --> D

经过这4步之后再上传到server, server根据同样的规则匹配

Demo:

#import "ViewController.h"
#import "DemoScrollView.h"
#import "NSString+UTIL.h"
#import "NSData+UTIL.h"

@interface ViewController ()

@property (nonatomic, copy) NSString *UUID;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    DemoScrollView *scrollView = [DemoScrollView attachOnView:self.view];
    [scrollView addButtonWithTitle:@"MD5 Simple" action:@selector(md5Encrypt) target:self];
    [scrollView addButtonWithTitle:@"MD5 加盐" action:@selector(md5EncryptWithSalt) target:self];
    [scrollView addButtonWithTitle:@"HMAC" action:@selector(hmac) target:self];
    [scrollView addButtonWithTitle:@"MD5 And time" action:@selector(md5AndTime) target:self];
}

- (void)md5Encrypt {
    NSString *password = @"123456";
    NSString *md5Password = [NSString MD5ForLower32:password];
    NSLog(@"md5 password: %@", md5Password); // e10adc3949ba59abbe56e057f20f883e
    // 保存此MD5值,假设为A,下次用户登录时对用户的输入进行MD5后得到值B,对比A和B
}

- (void)md5EncryptWithSalt {
    // 简单的加盐操作
    // 加一勺盐
    NSString *password = @"123456";
    NSString *salt = @"1234567890*&^%$#WERTYUIOLNBVCFDSasXCVBNM<>?}{+_)!~";
    NSString *passwordAfterSalt = [password stringByAppendingString:salt];
    NSString *md5PasswordAfterSalt = [NSString MD5ForLower32:passwordAfterSalt];
    NSLog(@"md5 password after salt: %@", md5PasswordAfterSalt); // d9f805c197219c11d258321d6c9dd46d
    // 把加盐后并MD5之后的值存入数据库,假设值为A
    // 下次用户登录时,同样把登录时的密码+同样的盐并MD5后的值B和数据库中的A进行比较
    // 注:盐可以加多勺
}

// HMAC: 原密码+一个字符串 > MD5 > 把结果 + 原密码再MD5
// https://www.liaoxuefeng.com/wiki/1016959663602400/1183198304823296
- (void)hmac {
    NSString *password = @"123456";
    NSData *data = [password dataUsingEncoding:NSUTF8StringEncoding];
    NSString *hmacStr = [data hmacMD5StringWithKey:@"!@#$%^&*()_{}~!"];
    NSLog(@"hmac string: %@", hmacStr); // 15f20f9a7c1ded910eddd30594b263dd
}

- (void)md5AndTime {
    [self loginWithUserName:@"DaLiu" password:@"123456"];
}

- (void)loginWithUserName:(NSString *)username password:(NSString *)password {
    __weak typeof(self) weakSelf = self;
    [self getSaltAndTimePassword:password completion:^(NSString *newPassword) {
        [weakSelf loginWithUserName:username newPassword:newPassword];
    }];
}

- (void)getSaltAndTimePassword:(NSString *)originPassword completion:(void (^)(NSString *newPassword))completion {
    self.UUID = [self uuidString];
    NSLog(@"UUID: %@", self.UUID); // 这里使用一种笨的方法代码当前用户的这次请求以便服务器记录获取时间和登录是基于同一个用户, 实际项目中不使用这种做法
    
    // 1. 一个特定字符串key, 然后md5计算    --> A
    NSString *md5Key = [NSString MD5ForLower32:@"liuxing8807@126.com__!#@$^*&^%~"];
    
    // 2. 把原密码和之前生成的md5值进行hmac加密 --> B
    NSString *hmacKey = [[originPassword dataUsingEncoding:NSUTF8StringEncoding] hmacMD5StringWithKey:md5Key];
    
    // 3. 从serve获取当前时间(客户端时间用户可以随意更改,因此统一使用server的时间) --> C
    NSString *urlStr = [NSString stringWithFormat:@"localhost:8080/daliu/HMacLoginServlet?uuid=%@", self.UUID];
    NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:urlStr]]; // 作为demo, 简单起见
    NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
    NSDictionary *resultData = [dict objectForKey:@"data"];
    NSString *time = [resultData objectForKey:@"time"];
    
    // 4. 把第二步产生的hmac值+时间, 和第一步产生的md5值拼接, 然后进行hmac加密 --> D
    NSString *str = [hmacKey stringByAppendingString:time];
    NSString *result = [[str dataUsingEncoding:NSUTF8StringEncoding] hmacMD5StringWithKey:md5Key];
    if (completion) {
        completion(result);
    }
}

- (void)loginWithUserName:(NSString *)username newPassword:(NSString *)newPassword {
    NSString *body = [NSString stringWithFormat:@"uuid=%@&username=%@&password=%@", self.UUID, username, newPassword];
    NSURL *url = [NSURL URLWithString:@"http://localhost:8080/daliu/HMacLoginServlet"];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"post";
    request.HTTPBody = [body dataUsingEncoding:NSUTF8StringEncoding];
    NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (error) {
            NSLog(@"网络错误: %@", error.localizedRecoverySuggestion);
        }
        NSError *jsonError = nil;
        NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];
        if (result && !error) {
            NSLog(@"%@", result);
        } else {
            NSLog(@"json error: %@", jsonError.localizedRecoverySuggestion);
        }
    }];
    [dataTask resume];
}

- (NSString *)uuidString
{
    CFUUIDRef uuid_ref = CFUUIDCreate(NULL);
    CFStringRef uuid_string_ref= CFUUIDCreateString(NULL, uuid_ref);
    NSString *uuid = [NSString stringWithString:(__bridge NSString *)uuid_string_ref];
    CFRelease(uuid_ref);
    CFRelease(uuid_string_ref);
    return [uuid lowercaseString];
}

@end
@interface NSString (UTIL)

+ (NSString *)MD5ForLower32:(NSString *)str;
+ (NSString *)MD5ForUpper32:(NSString *)str;

@end

#import "NSString+UTIL.h"
#import <CommonCrypto/CommonDigest.h>
#import <CommonCrypto/CommonCryptor.h>

@implementation NSString (UTIL)

+ (NSString *)MD5ForLower32:(NSString *)str {
    const char* input = [str UTF8String];
    unsigned char result[CC_MD5_DIGEST_LENGTH];
    CC_MD5(input, (CC_LONG)strlen(input), result);
    // 'CC_MD5' is deprecated: first deprecated in iOS 13.0 - This function is cryptographically broken and should not be used in security contexts. Clients should migrate to SHA256 (or stronger).
    NSMutableString *digest = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
    for (NSInteger i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {
        [digest appendFormat:@"%02x", result[i]];
    }
    
    return digest;
}

#pragma mark 32位 大写
+ (NSString *)MD5ForUpper32:(NSString *)str {
    const char* input = [str UTF8String];
    unsigned char result[CC_MD5_DIGEST_LENGTH];
    CC_MD5(input, (CC_LONG)strlen(input), result);
    // 'CC_MD5' is deprecated: first deprecated in iOS 13.0 - This function is cryptographically broken and should not be used in security contexts. Clients should migrate to SHA256 (or stronger).
    NSMutableString *digest = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
    for (NSInteger i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {
        [digest appendFormat:@"%02X", result[i]];
    }
    
    return digest;
}

@end
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface NSData (UTIL)

- (NSString *)hmacMD5StringWithKey:(NSString *)key;

@end

NS_ASSUME_NONNULL_END

#import "NSData+UTIL.h"
#import <CommonCrypto/CommonHMAC.h>

@implementation NSData (UTIL)

- (NSString *)hmacMD5StringWithKey:(NSString *)key {
    return [self hmacStringUsingAlg:kCCHmacAlgMD5 withKey:key];
}

- (NSString *)hmacStringUsingAlg:(CCHmacAlgorithm)alg withKey:(NSString *)key {
    size_t size;
    switch (alg) {
        case kCCHmacAlgMD5: size = CC_MD5_DIGEST_LENGTH; break;
        case kCCHmacAlgSHA1: size = CC_SHA1_DIGEST_LENGTH; break;
        case kCCHmacAlgSHA224: size = CC_SHA224_DIGEST_LENGTH; break;
        case kCCHmacAlgSHA256: size = CC_SHA256_DIGEST_LENGTH; break;
        case kCCHmacAlgSHA384: size = CC_SHA384_DIGEST_LENGTH; break;
        case kCCHmacAlgSHA512: size = CC_SHA512_DIGEST_LENGTH; break;
        default: return nil;
    }
    unsigned char result[size];
    const char *cKey = [key cStringUsingEncoding:NSUTF8StringEncoding];
    CCHmac(alg, cKey, strlen(cKey), self.bytes, self.length, result);
    NSMutableString *hash = [NSMutableString stringWithCapacity:size * 2];
    for (int i = 0; i < size; i++) {
        [hash appendFormat:@"%02x", result[i]];
    }
    return hash;
}

@end
上一篇下一篇

猜你喜欢

热点阅读