Created by 大劉 liuxing8807@126.com
MD5是哈希算法的一種, 即給定一個(gè)字符串或文件, Hash算法根據(jù)源文件本身通過(guò)一系列數(shù)學(xué)計(jì)算得到一串字符串, 不同的文件得出的Hash值不同. 所以哈希算法多用于校驗(yàn), 比如我們?cè)谝恍┚W(wǎng)站上下載文件時(shí)會(huì)有一些文件的哈希值, 下載下來(lái)后通過(guò)得新計(jì)算哈希值得到我們下載的文件是否被篡改過(guò).
MD5一般對(duì)任意數(shù)據(jù)源計(jì)算, 生成32個(gè)字符固定長(zhǎng)度的字符串, 示例:
% md5 test.png
MD5 (test.png) = b50666b631a48936bb4a3d828feaffc1
% md5 test-copy.png # test-copy.png是test.png拷貝而來(lái)
MD5 (test-copy.png) = b50666b631a48936bb4a3d828feaffc1
在比如, 對(duì)于銀行卡6位取款密碼, 如果這個(gè)密碼丟了, 即使是銀行的工作人員可能也無(wú)法知道, 因?yàn)榧僭O(shè)使用了哈希算法, 而理論上安全性較高的哈希算法不具有可逆性,從源得出哈希值, 但是哈希值不可以導(dǎo)出源; 當(dāng)然, 沒(méi)有絕對(duì)的安全, 如果原密碼設(shè)置的太簡(jiǎn)單, 反向?qū)С鲈艽a有很大的可能性, 網(wǎng)上有大量的方式可以破解MD5算法.
現(xiàn)在Apple已有建議使用MD5算法,調(diào)用CC_MD5方法,編譯器會(huì)提出警示:
'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).
但是其使用場(chǎng)景依然很多。
加鹽,其實(shí)就是在原密碼的基礎(chǔ)上加一點(diǎn)額外操作,而這個(gè)操作是我們自己所知道的,然后把加鹽后的密碼再M(fèi)D5,這樣即使別人解出來(lái),也只是解出加鹽后的密碼,并不是原密碼,相對(duì)于原來(lái)方式,這樣提高了安全性。
參考雪峰,關(guān)于加鹽操作,HMAC通過(guò)一個(gè)標(biāo)準(zhǔn)算法,在計(jì)算哈希的過(guò)程中,把key混入計(jì)算過(guò)程中。
另外, 由于HTTP總是無(wú)法避免被他人監(jiān)聽, 因此還有一種方式是在MD5過(guò)程中加上時(shí)間限制, 使密碼每次都不一樣. 即使其他人截獲了這個(gè)密碼, 但這個(gè)密碼有時(shí)間限制, 如果破解的時(shí)間大于這個(gè)時(shí)間限制, 等破解之后上傳到服務(wù)器, 服務(wù)器就可以判定此密碼無(wú)效, 從而進(jìn)一步提高安全性.
而且這種規(guī)則可以隨意設(shè)計(jì),只要是和server約定好即可. 比如我們自定義一個(gè)規(guī)則:
- 一個(gè)特定字符串Key, 然后md5計(jì)算 --> A
- 把原密碼和之前生成的md5值進(jìn)行hmac加密 --> B
- 從serve獲取當(dāng)前時(shí)間(客戶端時(shí)間用戶可以隨意更改,因此統(tǒng)一使用server的時(shí)間) --> C
- 把第二步產(chǎn)生的hmac值+時(shí)間, 和第一步產(chǎn)生的md5值進(jìn)行hmac加密 --> D
經(jīng)過(guò)這4步之后再上傳到server, server根據(jù)同樣的規(guī)則匹配
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值,假設(shè)為A,下次用戶登錄時(shí)對(duì)用戶的輸入進(jìn)行MD5后得到值B,對(duì)比A和B
}
- (void)md5EncryptWithSalt {
// 簡(jiǎn)單的加鹽操作
// 加一勺鹽
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之后的值存入數(shù)據(jù)庫(kù),假設(shè)值為A
// 下次用戶登錄時(shí),同樣把登錄時(shí)的密碼+同樣的鹽并MD5后的值B和數(shù)據(jù)庫(kù)中的A進(jìn)行比較
// 注:鹽可以加多勺
}
// HMAC: 原密碼+一個(gè)字符串 > MD5 > 把結(jié)果 + 原密碼再M(fèi)D5
// 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); // 這里使用一種笨的方法代碼當(dāng)前用戶的這次請(qǐng)求以便服務(wù)器記錄獲取時(shí)間和登錄是基于同一個(gè)用戶, 實(shí)際項(xiàng)目中不使用這種做法
// 1. 一個(gè)特定字符串key, 然后md5計(jì)算 --> A
NSString *md5Key = [NSString MD5ForLower32:@"liuxing8807@126.com__!#@$^*&^%~"];
// 2. 把原密碼和之前生成的md5值進(jìn)行hmac加密 --> B
NSString *hmacKey = [[originPassword dataUsingEncoding:NSUTF8StringEncoding] hmacMD5StringWithKey:md5Key];
// 3. 從serve獲取當(dāng)前時(shí)間(客戶端時(shí)間用戶可以隨意更改,因此統(tǒng)一使用server的時(shí)間) --> C
NSString *urlStr = [NSString stringWithFormat:@"localhost:8080/daliu/HMacLoginServlet?uuid=%@", self.UUID];
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:urlStr]]; // 作為demo, 簡(jiǎn)單起見
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
NSDictionary *resultData = [dict objectForKey:@"data"];
NSString *time = [resultData objectForKey:@"time"];
// 4. 把第二步產(chǎn)生的hmac值+時(shí)間, 和第一步產(chǎn)生的md5值拼接, 然后進(jìn)行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(@"網(wǎng)絡(luò)錯(cuò)誤: %@", 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