简介

虽然目前市面上有一些不错的加密相册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

为了进行归档存储,需要实现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的实现(一)的更多相关文章

  1. iOS开源加密相册Agony的实现(三)

    简介 虽然目前市面上有一些不错的加密相册App,但不是内置广告,就是对上传的张数有所限制.本文介绍了一个加密相册的制作过程,该加密相册将包括多密码(输入不同的密码即可访问不同的空间,可掩人耳目).Wi ...

  2. iOS开源加密相册Agony的实现(二)

    简介 虽然目前市面上有一些不错的加密相册App,但不是内置广告,就是对上传的张数有所限制.本文介绍了一个加密相册的制作过程,该加密相册将包括多密码(输入不同的密码即可访问不同的空间,可掩人耳目).Wi ...

  3. iOS开源加密相册Agony的实现(七)

    简介 虽然目前市面上有一些不错的加密相册App,但不是内置广告,就是对上传的张数有所限制.本文介绍了一个加密相册的制作过程,该加密相册将包括多密码(输入不同的密码即可访问不同的空间,可掩人耳目).Wi ...

  4. iOS开源加密相册Agony的实现(六)

    简介 虽然目前市面上有一些不错的加密相册App,但不是内置广告,就是对上传的张数有所限制.本文介绍了一个加密相册的制作过程,该加密相册将包括多密码(输入不同的密码即可访问不同的空间,可掩人耳目).Wi ...

  5. iOS开源加密相册Agony的实现(五)

    简介 虽然目前市面上有一些不错的加密相册App,但不是内置广告,就是对上传的张数有所限制.本文介绍了一个加密相册的制作过程,该加密相册将包括多密码(输入不同的密码即可访问不同的空间,可掩人耳目).Wi ...

  6. iOS开源加密相册Agony的实现(四)

    简介 虽然目前市面上有一些不错的加密相册App,但不是内置广告,就是对上传的张数有所限制.本文介绍了一个加密相册的制作过程,该加密相册将包括多密码(输入不同的密码即可访问不同的空间,可掩人耳目).Wi ...

  7. iOS开源照片浏览器框架SGPhotoBrowser的设计与实现

    简介 近日在制作一个开源加密相册时附带着设计了一个照片浏览器,在进一步优化后发布到了GitHub供大家使用,该框架虽然没有MWPhotoBrowser那么强大,但是使用起来更为方便,操作更符合常规相册 ...

  8. iOS -- 开源项目和库

    TimLiu-iOS 目录 UI 下拉刷新 模糊效果 AutoLayout 富文本 图表 表相关与Tabbar 隐藏与显示 HUD与Toast 对话框 其他UI 动画 侧滑与右滑返回手势 gif动画 ...

  9. iOS开源项目周报1222

    由OpenDigg 出品的iOS开源项目周报第二期来啦.我们的iOS开源周报集合了OpenDigg一周来新收录的优质的iOS开发方面的开源项目,方便iOS开发人员便捷的找到自己需要的项目工具等. io ...

随机推荐

  1. ansible+packer+terraform在aws上布署web服务器

    各工具所扮演的角色 ansible: 配合packer生成安装有apache的基础镜像 packer: 生成amazon AMI terraform: 以packer生成的镜像为基础,布署web服务器 ...

  2. Html5 监听拦截Android返回键方法详解

    浏览器窗口有一个history对象,用来保存浏览历史. 如果当前窗口先后访问了三个网址,那么history对象就包括三项,history.length属性等于3. history对象提供了一系列方法, ...

  3. 遗传算法详解(LINGO及MatlabGA工具箱求解实现)

    遗传算法 1.前言 遗传算法是一种基于生物界自然群体遗传进化机制的自适应全局优化概率搜索算法.它与传统算法不同,不依赖梯度信息,而是通过模拟自然进化过程来搜索最优解. 例子:兔子的遗传进化 有人说,现 ...

  4. Java之GC

    Java之GC GC:GC 是JVM的垃圾回收器.与C/C++不同,java程序员无需考虑太多内存分配的位置,更不用考虑内存释放的机制,java对象内存的申请和释放都有JVM托管.JVM的内存释放机制 ...

  5. drupal 8 查看数据库用户名密码

    一.查看网站数据库名.密码 在已安装网站下 例如:我安装了一个d8radix的网站,此网站/home/jx/www/d8radix/sites/default/setting.php文件里面 数据库名 ...

  6. 各种电脑进入BIOS快捷键

    组装机主板 品牌笔记本 品牌台式机 主板品牌 启动按键 笔记本品牌 启动按键 台式机品牌 启动按键 华硕主板 F8 联想笔记本 F12 联想台式机 F12 技嘉主板 F12 宏基笔记本 F12 惠普台 ...

  7. [CTSC 1999]拯救大兵瑞恩&[网络流24题]孤岛营救问题

    Description $1944$ 年,特种兵麦克接到国防部的命令,要求立即赶赴太平洋上的一个孤岛,营救被敌军俘虏的大兵瑞恩.瑞恩被关押在一个迷宫里,迷宫地形复杂,但幸好麦克得到了迷宫的地形图.迷宫 ...

  8. [Luogu 2090]数字对

    Description 对于一个数字对(a, b),我们可以通过一次操作将其变为新数字对(a+b, b)或(a, a+b). 给定一正整数n,问最少需要多少次操作可将数字对(1, 1)变为一个数字对, ...

  9. codeforces868D Huge Strings

    You are given n strings s1, s2, ..., sn consisting of characters 0 and 1. m operations are performed ...

  10. 计蒜客NOIP模拟赛(2) D2T3 银河战舰

    [问题描述]    瑞奥和玛德利德是非常好的朋友.瑞奥平时的爱好是吹牛,玛德利德的爱好是戳穿瑞奥吹的牛.    这天瑞奥和玛德利德来到了宇宙空间站,瑞奥向玛德利德炫耀这个空间站里所有的银河战舰都是自己 ...