標籤:
簡介
雖然目前市面上有一些不錯的加密相簿App,但不是內建廣告,就是對上傳的張數有所限制。本文介紹了一個加密相簿的製作過程,該加密相簿將包括多密碼(輸入不同的密碼即可訪問不同的空間,可掩人耳目)、WiFi傳圖、照片檔案加密等功能。目前項目和文章會同時前進,項目的原始碼可以在github上下載。
點擊前往GitHub
概述
本文主要介紹加密相簿的登入驗證與註冊模組的實現。註冊時只要求輸入密碼,每個密碼對應一個獨立的儲存空間,登入時通過Touch ID或密碼驗證。如果有多套密碼,Touch ID會被綁定到一個主密碼上(可更改)。
賬戶資料存放區設計賬戶類設計
由於加密相簿只用於本地,當前設計還未考慮密碼找回,因此賬戶只要求輸入密碼這一欄位即可,為了統計當前已有賬戶數量,再使用一個id欄位,賬戶類SGAccount設計如下。
@interface SGAccount : NSObject <NSSecureCoding>@property (nonatomic, assign) NSInteger accountId;@property (nonatomic, copy) NSString *password;@end
為了進行Archive Storage,需要實現NSCoding的相關方法,如下。
#import "SGAccount.h"NSString * const kSGAccountId = @"kSGAccountId";NSString * const kSGAccountPwd = @"kSGAccountPwd";@implementation SGAccount- (void)encodeWithCoder:(NSCoder *)encoder { [encoder encodeInteger:self.accountId forKey:kSGAccountId]; [encoder encodeObject:self.password forKey:kSGAccountPwd];}- (instancetype)initWithCoder:(NSCoder *)decoder { if (self = [super init]) { self.accountId = [decoder decodeIntegerForKey:kSGAccountId]; self.password = [decoder decodeObjectForKey:kSGAccountPwd]; } return self;}+ (BOOL)supportsSecureCoding { return YES;}@end
賬戶集合類設計
對於多個賬戶,使用一個賬戶集合類來管理,賬戶集合類管理所有的賬戶,由於登入驗證時需要查詢密碼對應的賬戶是否存在,為了高效尋找,應該使用以密碼為key的Map,也就是NSDictionary來儲存。
除此之外,還需要記錄Touch ID對應的密碼,綜上所述,設計如下。
@interface SGAccountSet : NSObject <NSSecureCoding>@property (nonatomic, strong) NSMutableDictionary<NSString *, SGAccount *> *accountMap;@property (nonatomic, copy) NSString *touchIDPassword;@end
同理這些屬性也需要在NSCoding的相關方法裡處理,類的實現如下。
#import "SGAccountSet.h"NSString * const kSGAccountSetAccountMap = @"kSGAccountSetAccountMap";NSString * const kSGAccountSetTouchIDPassword = @"kSGAccountSetTouchIDPassword";@implementation SGAccountSet+ (BOOL)supportsSecureCoding { return YES;}- (instancetype)initWithCoder:(NSCoder *)decoder { if (self = [super init]) { self.accountMap = [decoder decodeObjectForKey:kSGAccountSetAccountMap]; self.touchIDPassword = [decoder decodeObjectForKey:kSGAccountSetTouchIDPassword]; } return self;}- (void)encodeWithCoder:(NSCoder *)encoder { [encoder encodeObject:self.accountMap forKey:kSGAccountSetAccountMap]; [encoder encodeObject:self.touchIDPassword forKey:kSGAccountSetTouchIDPassword];}- (NSMutableDictionary<NSString *,SGAccount *> *)accountMap { if (_accountMap == nil) { _accountMap = @{}.mutableCopy; } return _accountMap;}@end
對於accountMap的懶載入,可以保證在沒有賬戶資料時拿到的字典不為空白。
賬戶管理類的設計公有介面設計
賬戶管理類對外提供的介面主要是註冊與驗證,為了方便,作為單例使用。
註冊時只需提供密碼即可,而驗證封裝括兩種情況,其一是通過密碼驗證,第二是通過Touch ID驗證,當驗證成功時直接返回賬戶類。
除此之外,賬戶管理類還有一個屬性currentAccount記錄當前驗證成功的賬戶,以便後續使用,具體設計如下。
@interface SGAccountManager : NSObject+ (instancetype)sharedManager;- (void)registerAccountWithPassword:(NSString *)password errorMessage:(NSString * __autoreleasing *)errorMessage;- (SGAccount *)getAccountByPwd:(NSString *)pwd;- (SGAccount *)getTouchIDAccount;/* * 用於AppDelegate擷取視窗的根控制器 * 沒有註冊過賬戶則進入註冊頁面 * 註冊過使用者則進入登入驗證頁面 */- (UIViewController *)getRootViewController;@property (nonatomic, strong) SGAccount *currentAccount;@end
私人介面設計
私人介面用於管理類內部的邏輯實現,其中accountSet用於儲存所有使用者資料,accountPath用於儲存賬戶資料儲存和載入的路徑。
@interface SGAccountManager ()@property (nonatomic, strong) SGAccountSet *accountSet;@property (nonatomic, copy) NSString *accountPath;@end
賬戶集合accountSet的懶載入
賬戶集合類的初始化包括兩個步驟,首先從硬碟載入資料,如果硬碟上沒有資料,則初始化一個。之所以分解為兩個方法,是因為從硬碟載入資料的方法loadAccountSet會被在其他地方調用,實現如下。
- (SGAccountSet *)accountSet { if (_accountSet == nil) { [self loadAccountSet]; } return _accountSet;}- (void)loadAccountSet { SGAccountSet *set = [NSKeyedUnarchiver unarchiveObjectWithFile:self.accountPath]; if (!set) { set = [SGAccountSet new]; } _accountSet = set;}
賬戶存取路徑accountPath的懶載入
賬戶資料的儲存路徑會在載入和寫入賬戶集合類資料時使用,實現如下。
- (NSString *)accountPath { if (_accountPath == nil) { _accountPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"account.agony"]; } return _accountPath;}
註冊的實現
註冊時傳入密碼,密碼經過加密後,先判斷賬戶集合中是否已經存在此密碼,以防止密碼重複,這是因為密碼與儲存空間一一對應,因此密碼不能重複。如果密碼重複,則通過傳入的字串指標回傳。
對於第一次註冊的密碼,將會被綁定到Touch ID上,以後使用Touch ID驗證時則相當於輸入此密碼,註冊方法的實現如下。
- (void)registerAccountWithPassword:(NSString *)password errorMessage:(NSString * __autoreleasing *)errorMessage { NSAssert(password != nil, @"password cannot be nil"); // 對密碼進行MD5+鹽的加密處理 password = [self encryptString:password]; SGAccount *account = self.accountSet.accountMap[password]; // 如果根據要註冊的密碼能取到賬戶,則說明密碼重複,回傳錯誤並返回 if (account != nil) { *errorMessage = @"Account Already Exists"; return; } account = [SGAccount new]; // 產生賬戶id NSInteger accountid = self.accountSet.accountMap.allKeys.count + 1; account.accountId = accountid; account.password = password; // 存入到集合中 self.accountSet.accountMap[password] = account; if (accountid == 1) { // 如果是第一次註冊,則將其綁定到Touch ID驗證對應的密碼上 self.accountSet.touchIDPassword = password; } // 將記憶體資料同步到硬碟 [self saveAccountSet];}
加密方法的實現如下。
- (NSString *)encryptString:(NSString *)string { return [[[[NSString stringWithFormat:@"allowsad12345%@62232",string] MD5] MD5] MD5];}
MD5方法通過分類的形式添加到NSString上,實現如下。
#import "NSString+MD5.h"#import <CommonCrypto/CommonDigest.h>@implementation NSString (MD5)- (NSString *)MD5 { const char *cStr = [self UTF8String]; unsigned char digest[CC_MD5_DIGEST_LENGTH]; CC_MD5(cStr, (CC_LONG)strlen(cStr), digest); NSMutableString *result = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2]; for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++) { [result appendFormat:@"%02x",digest[i]]; } return result;}@end
將資料寫入到硬碟的方法實現如下。
- (void)saveAccountSet { [NSKeyedArchiver archiveRootObject:self.accountSet toFile:self.accountPath];}
登入驗證的實現
通過密碼驗證的方式,先將密碼加密,再與集合中的密碼比對,找到匹配的則驗證成功,實現如下。
- (SGAccount *)getAccountByPwd:(NSString *)pwd { pwd = [self encryptString:pwd]; return self.accountSet.accountMap[pwd];}
通過Touch ID驗證的方式,需要在Touch ID驗證成功後調用,使用Touch ID對應的密碼進行驗證,實現如下。
- (SGAccount *)getTouchIDAccount { NSString *pwd = self.accountSet.touchIDPassword; return self.accountSet.accountMap[pwd];}
視窗根控制器選擇的實現
如果已經有了賬戶,則返回導航控制器包裹的驗證控制器SGWelcomeViewController,如果沒有註冊過賬戶,則先初始化一個導航控制器包裹的SGWelcomeViewController,並且向視圖棧中push一個註冊控制器SGRegisterViewController,之所以這麼做,是為了保證註冊完成後能夠返回到驗證控制器,並與從驗證頁面進入的註冊保持相同的邏輯,具體實現如下。
- (UIViewController *)getRootViewController { if ([self hasAccount]) { return [[UINavigationController alloc] initWithRootViewController:[SGWelcomeViewController new]]; } SGWelcomeViewController *welcomeVc = [SGWelcomeViewController new]; SGRegisterViewController *registerVc = [SGRegisterViewController new]; UINavigationController *nav = [UINavigationController new]; nav.viewControllers = @[welcomeVc, registerVc]; return nav;}
總結
本文主要介紹了與註冊與登入驗證有關的資料類和管理類的介面與實現過程,在後面的註冊與登入驗證視圖設計中,只需要使用工具類即可。歡迎關注項目後續,項目的在本文的開頭可以找到。
iOS開源加密相簿Agony的實現(一)