Objective-C_MD5加鹽

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ī)則:

  1. 一個(gè)特定字符串Key, 然后md5計(jì)算 --> A
  2. 把原密碼和之前生成的md5值進(jìn)行hmac加密 --> B
  3. 從serve獲取當(dāng)前時(shí)間(客戶端時(shí)間用戶可以隨意更改,因此統(tǒng)一使用server的時(shí)間) --> C
  4. 把第二步產(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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容