TouchID 、FaceID 与KeyChain介绍

LAPublicDefines.h

首先是LAPublicDefines.h,从名字上来看是公共宏定义类,里面包含了许多定义好的宏,这些宏会在LAContext.h中用到。

#ifndef LocalAuthentication_LAPublicDefines_h
#define LocalAuthentication_LAPublicDefines_h

// Policies
#define kLAPolicyDeviceOwnerAuthenticationWithBiometrics    1
#define kLAPolicyDeviceOwnerAuthentication                  2

// Options
#define kLAOptionUserFallback                               1
#define kLAOptionAuthenticationReason                       2

// Credential types
#define kLACredentialTypeApplicationPassword                0

// Error codes
#define kLAErrorAuthenticationFailed                       -1
#define kLAErrorUserCancel                                 -2
#define kLAErrorUserFallback                               -3
#define kLAErrorSystemCancel                               -4
#define kLAErrorPasscodeNotSet                             -5
#define kLAErrorTouchIDNotAvailable                        -6
#define kLAErrorTouchIDNotEnrolled                         -7
#define kLAErrorTouchIDLockout                             -8
#define kLAErrorAppCancel                                  -9
#define kLAErrorInvalidContext                            -10
#define kLAErrorNotInteractive                          -1004

#define kLAErrorBiometryNotAvailable                        kLAErrorTouchIDNotAvailable
#define kLAErrorBiometryNotEnrolled                         kLAErrorTouchIDNotEnrolled
#define kLAErrorBiometryLockout                             kLAErrorTouchIDLockout

// Error domain
#define kLAErrorDomain        "com.apple.LocalAuthentication"

#endif

LAContext.h API

#import <Foundation/Foundation.h>
#import <LocalAuthentication/LAPublicDefines.h>

NS_ASSUME_NONNULL_BEGIN

typedef NS_ENUM(NSInteger, LAPolicy) {
    /*指纹(人脸)识别。验证弹框有两个按钮,第一个是取消按钮,第二个按钮可以自定义标题名称(输入密码)。
     只有在第一次指纹验证失败后才会出现第二个按钮,这种方式下的第二个按钮功能需要自己定义。前三次指纹验证失败,指纹验证框不再弹出。
     再次重新进入验证,还有两次验证机会,如果还是验证失败,TOUCH ID 被锁住不再继续弹出指纹验证框。以后的每次验证都将会弹出设备密码输入框直至输入正确的设备密码才能重新使用指纹(人脸)识别*/
    LAPolicyDeviceOwnerAuthenticationWithBiometrics NS_ENUM_AVAILABLE(10_12_2, 8_0) __WATCHOS_AVAILABLE(3.0) __TVOS_AVAILABLE(10.0) = kLAPolicyDeviceOwnerAuthenticationWithBiometrics,
    /*指纹(人脸)识别或系统密码验证。
    如果Touch ID (Face ID)可用,且已经录入指纹(人脸),则优先调用指纹(人脸)验证。其次是调用系统密码验证,如果没有开启设备密码,则不可以使用这种验证方式。
    指纹(人脸)识别验证失败三次将弹出设备密码输入框,如果不进行密码输入,再次进来还可以有两次机会验证指纹(人脸),如果都失败则Touch ID(Face ID)被锁住,以后每次进来验证都是调用系统的设备密码直至输入正确的设备密码才能重新使用指纹(人脸)识别*/
    LAPolicyDeviceOwnerAuthentication NS_ENUM_AVAILABLE(10_11, 9_0) = kLAPolicyDeviceOwnerAuthentication
} NS_ENUM_AVAILABLE(10_10, 8_0) __WATCHOS_AVAILABLE(3.0) __TVOS_AVAILABLE(10.0);

///复用设备解锁授权最大时间常量
extern const NSTimeInterval LATouchIDAuthenticationMaximumAllowableReuseDuration API_AVAILABLE(macos(10.12), ios(9.0)) API_UNAVAILABLE(watchos, tvos);

NS_CLASS_AVAILABLE(10_10, 8_0) __WATCHOS_AVAILABLE(3.0) __TVOS_AVAILABLE(10.0)
@interface LAContext : NSObject
///检查当前设备是否可用TouchID/FaceID
- (BOOL)canEvaluatePolicy:(LAPolicy)policy error:(NSError * __autoreleasing *)error __attribute__((swift_error(none)));
///验证TouchID/FaceID方法
- (void)evaluatePolicy:(LAPolicy)policy
       localizedReason:(NSString *)localizedReason
                 reply:(void(^)(BOOL success, NSError * __nullable error))reply;
///用来废止这个context
- (void)invalidate NS_AVAILABLE(10_11, 9_0);

typedef NS_ENUM(NSInteger, LACredentialType) {
    LACredentialTypeApplicationPassword __TVOS_AVAILABLE(11.0) = kLACredentialTypeApplicationPassword,
} NS_ENUM_AVAILABLE(10_11, 9_0) __WATCHOS_AVAILABLE(3.0) __TVOS_AVAILABLE(10.0);
///设置解锁额外加密凭证
- (BOOL)setCredential:(nullable NSData *)credential
                 type:(LACredentialType)type NS_AVAILABLE(10_11, 9_0) __WATCHOS_AVAILABLE(3.0) __TVOS_AVAILABLE(11.0);
///判断加密凭证是否设置成功
- (BOOL)isCredentialSet:(LACredentialType)type NS_AVAILABLE(10_11, 9_0) __WATCHOS_AVAILABLE(3.0) __TVOS_AVAILABLE(11.0);

typedef NS_ENUM(NSInteger, LAAccessControlOperation) {
    LAAccessControlOperationCreateItem,        // 访问控制用于创建新的item
    LAAccessControlOperationUseItem,           // 访问控制用于使用已存在的item
    LAAccessControlOperationCreateKey,         // 访问控制用于创建新的密钥
    LAAccessControlOperationUseKeySign,        // 访问控制用于使用已存在的密钥签名
    LAAccessControlOperationUseKeyDecrypt NS_ENUM_AVAILABLE(10_12, 10_0),     // 访问控制用于使用已存在的密钥解密
    LAAccessControlOperationUseKeyKeyExchange NS_ENUM_AVAILABLE(10_12, 10_0), // 访问控制用于密钥交换
} NS_ENUM_AVAILABLE(10_11, 9_0) __WATCHOS_AVAILABLE(3.0) __TVOS_AVAILABLE(10.0);
//更加灵活的安全访问控制
- (void)evaluateAccessControl:(SecAccessControlRef)accessControl
                    operation:(LAAccessControlOperation)operation
              localizedReason:(NSString *)localizedReason
                        reply:(void(^)(BOOL success, NSError * __nullable error))reply
NS_AVAILABLE(10_11, 9_0) __WATCHOS_AVAILABLE(3.0) __TVOS_UNAVAILABLE;

//设置验证TouchID时弹出Alert的输入密码按钮的标题
@property (nonatomic, nullable, copy) NSString *localizedFallbackTitle;
//设置验证TouchID时弹出Alert的取消按钮的标题
@property (nonatomic, nullable, copy) NSString *localizedCancelTitle NS_AVAILABLE(10_12, 10_0);
//最大指纹尝试错误次数 (有效iOS8.3 - iOS9.0) 
@property (nonatomic, nullable) NSNumber *maxBiometryFailures NS_DEPRECATED_IOS(8_3, 9_0) __WATCHOS_UNAVAILABLE __TVOS_UNAVAILABLE;

@property (nonatomic, nullable, readonly) NSData *evaluatedPolicyDomainState NS_AVAILABLE(10_11, 9_0) __WATCHOS_UNAVAILABLE __TVOS_UNAVAILABLE;
/*该属性表示从设备解锁后多长时间内不需要重新验证的时间,该属性默认值为0,表示不采用设备解锁来授权应用。该属性允许最大的设置时长为5分钟(注:属性值为300,因为是以秒为单位,也可以使用*/
@property (nonatomic) NSTimeInterval touchIDAuthenticationAllowableReuseDuration NS_AVAILABLE(10_12, 9_0) __WATCHOS_UNAVAILABLE __TVOS_UNAVAILABLE;
//
@property (nonatomic, copy) NSString *localizedReason API_AVAILABLE(macos(10.13), ios(11.0)) API_UNAVAILABLE(watchos, tvos);
//允许在非交互模式下进行身份认证。这个是iOS 11新增功能,可以用来解决后台运行时授权处理
@property (nonatomic) BOOL interactionNotAllowed API_AVAILABLE(macos(10.13), ios(11.0)) API_UNAVAILABLE(watchos, tvos);

typedef NS_ENUM(NSInteger, LABiometryType) {
    //表示设备不支持生物识别技术
    LABiometryTypeNone API_AVAILABLE(macos(10.13.2), ios(11.2)),
    LABiometryNone API_DEPRECATED_WITH_REPLACEMENT("LABiometryTypeNone", macos(10.13, 10.13.2), ios(11.0, 11.2)) = LABiometryTypeNone,
    //表示当前设备支持指纹识别
    LABiometryTypeTouchID,  
    //表示当前设备支持人脸识别
    LABiometryTypeFaceID API_UNAVAILABLE(macos),
} API_AVAILABLE(macos(10.13.2), ios(11.0)) API_UNAVAILABLE(watchos, tvos);

@property (nonatomic, readonly) LABiometryType biometryType API_AVAILABLE(macos(10.13.2), ios(11.0)) API_UNAVAILABLE(watchos, tvos);

@end

NS_ASSUME_NONNULL_END

LAError.h API

包括一个枚举,里面写的是错误的类型,其实就是把上面的kLAError宏写进这个枚举了

#import <Foundation/Foundation.h>
#import <LocalAuthentication/LAPublicDefines.h>

typedef NS_ENUM(NSInteger, LAError)
{   ///身份验证失败
    LAErrorAuthenticationFailed = kLAErrorAuthenticationFailed,
    ///用户在认证时点击取消
    LAErrorUserCancel = kLAErrorUserCancel,
    ///用户点击输入密码取消指纹验证
    LAErrorUserFallback = kLAErrorUserFallback,
    ///身份认证被系统取消(按下[Home键]或电源键)
    LAErrorSystemCancel = kLAErrorSystemCancel,
    ///用户没有设置TouchID
    LAErrorPasscodeNotSet = kLAErrorPasscodeNotSet,
    ///设备不支持TouchID
    LAErrorTouchIDNotAvailable NS_ENUM_DEPRECATED(10_10, 10_13, 8_0, 11_0, "use LAErrorBiometryNotAvailable") = kLAErrorTouchIDNotAvailable,
    ///用户没有设置手指指纹
    LAErrorTouchIDNotEnrolled NS_ENUM_DEPRECATED(10_10, 10_13, 8_0, 11_0, "use LAErrorBiometryNotEnrolled") = kLAErrorTouchIDNotEnrolled,
    ///连续五次密码错误,FaceID被锁定
    LAErrorTouchIDLockout NS_ENUM_DEPRECATED(10_11, 10_13, 9_0, 11_0, "use LAErrorBiometryLockout")
    __WATCHOS_DEPRECATED(3.0, 4.0, "use LAErrorBiometryLockout") __TVOS_DEPRECATED(10.0, 11.0, "use LAErrorBiometryLockout") = kLAErrorTouchIDLockout,
    ///用户不能控制情况下App被挂起 / 在验证中被其他app中断
    LAErrorAppCancel NS_ENUM_AVAILABLE(10_11, 9_0) = kLAErrorAppCancel,
    ///请求验证出错
    LAErrorInvalidContext NS_ENUM_AVAILABLE(10_11, 9_0) = kLAErrorInvalidContext,
    ///
    LAErrorBiometryNotAvailable NS_ENUM_AVAILABLE(10_13, 11_0) __WATCHOS_AVAILABLE(4.0) __TVOS_AVAILABLE(11.0) = kLAErrorBiometryNotAvailable,
    ///
    LAErrorBiometryNotEnrolled NS_ENUM_AVAILABLE(10_13, 11_0) __WATCHOS_AVAILABLE(4.0) __TVOS_AVAILABLE(11.0) = kLAErrorBiometryNotEnrolled,
    ///
    LAErrorBiometryLockout NS_ENUM_AVAILABLE(10_13, 11_0) __WATCHOS_AVAILABLE(4.0) __TVOS_AVAILABLE(11.0) = kLAErrorBiometryLockout,
    ///
    LAErrorNotInteractive API_AVAILABLE(macos(10.10), ios(8.0), watchos(3.0), tvos(10.0)) = kLAErrorNotInteractive,
} NS_ENUM_AVAILABLE(10_10, 8_0) __WATCHOS_AVAILABLE(3.0) __TVOS_AVAILABLE(10.0);

/// LocalAuthentication error domain.
extern NSString *const __nonnull LAErrorDomain
API_AVAILABLE(macos(10.11), ios(8.3), watchos(3.0), tvos(10.0));

FaceID应用

LAContext *context = [[LAContext alloc] init];
context.touchIDAuthenticationAllowableReuseDuration = LATouchIDAuthenticationMaximumAllowableReuseDuration;
NSError *error = nil;
BOOL isSupportFaceID = [context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error];
if (isSupportFaceID) {
    NSLog(@"FaceID_IS_AVAILABLE");
    [context evaluatePolicy:LAPolicyDeviceOwnerAuthentication localizedReason:@"请验证指纹" reply:
     ^(BOOL success, NSError *authenticationError) {
         if (success) {
             NSLog(@"FaceID_unlock_Success");
         }
         else {
             NSLog(@"FaceID_unlock_Failure");
         }
     }];
}
else {
    NSLog(@"FaceID_IS_UNAVAILABLE");
}

复用设备解锁授权

如果你的应用想要在设备使用Touch ID / Face ID解锁后一段时间内自己的App也不需要重新弹出解锁界面,就可以使用LAContexttouchIDAuthenticationAllowableReuseDuration属性,该属性表示从设备解锁后多长时间内不需要重新验证的时间,该属性默认值为0,表示不采用设备解锁来授权应用。该属性允许最大的设置时长为5分钟(注:属性值为300,因为是以秒为单位,也可以使用LATouchIDAuthenticationMaximumAllowableReuseDuration常量值)

控制Keychain(钥匙串)访问权限

在iOS 9之前我们写入到Keychain的数据,在设备解锁后就能够对keychain中的数据进行访问,其实这样是不够安全的,特别是在你的App使用第三方SDK的情况底下,很有可能就会去窃取App中的keychain数据。那么,在iOS 9之后,系统加入了一项新的功能,就是允许应用来控制Keychain的数据访问。它的实现过程是在写入数据时添加一个应用级别的访问密码,后续要访问这个数据除了要设备解锁,还需要有正确的密码才能够访问keychain中的数据。而这功能正好是集成到了LocalAuthentication这个框架中,下面我们来探索一下这个功能的用法。

实现keychain的控制访问,依然还是要依靠LAContext来实现,我们可以看到在iOS 9之后,这个类型新增了一个方法setCredential:type:。这个方法的作用就是把访问Keychain的密码设置到LAContext对象中,配合LACredentialTypeApplicationPassword这个类型就能够实现控制访问了。
下面先来看一下实现的示例代码:

OSStatus status = -1;
CFErrorRef error = NULL;
SecAccessControlRef sacr = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleAfterFirstUnlock, kSecAccessControlApplicationPassword, &error);
    
if (sacr) {
    NSString *dataValue = @"要写入的数据";    //要写入的数据
    NSString *password = @"123456";          //访问密码
    NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
        
    LAContext *context = [[LAContext alloc] init];
    [context setCredential:passwordData type:LACredentialTypeApplicationPassword];
        
    NSMutableDictionary *saveDictionary = [[NSMutableDictionary alloc] init];
    [saveDictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
    [saveDictionary setObject:@"testService" forKey:(__bridge id)kSecAttrService];
    [saveDictionary setObject:@"testAccount" forKey:(__bridge id)kSecAttrAccount];
    [saveDictionary setObject:[dataValue dataUsingEncoding:NSUTF8StringEncoding] forKey:(__bridge id)kSecValueData];
    [saveDictionary setObject:(__bridge id)sacr forKey:(__bridge id)kSecAttrAccessControl];
    [saveDictionary setObject:context forKey:(__bridge id)kSecUseAuthenticationContext];
        
    status = SecItemAdd((__bridge CFDictionaryRef)saveDictionary, nil);
    if (status == errSecSuccess) {
        NSLog(@"存储成功");
    }
    CFRelease(sacr);
}
OSStatus status = -1;
CFErrorRef error = NULL;
SecAccessControlRef sacr = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
                                                              kSecAttrAccessibleAfterFirstUnlock,
                                                              kSecAccessControlApplicationPassword, &error);
if (sacr) {
    NSString *password = @"123456"; //访问密码
    LAContext *context = [[LAContext alloc] init];
    NSData *appPassword = [password dataUsingEncoding:NSUTF8StringEncoding];
    [context setCredential:appPassword type:LACredentialTypeApplicationPassword];
        
    NSMutableDictionary *loadDictionary = [[NSMutableDictionary alloc] init];
    [loadDictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
    [loadDictionary setObject:@"testService" forKey:(__bridge id)kSecAttrService];
    [loadDictionary setObject:@"testAccount" forKey:(__bridge id)kSecAttrAccount];
    [loadDictionary setObject:@(YES) forKey:(__bridge id)kSecReturnData];
    [loadDictionary setObject:(NSString *)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
    [loadDictionary setObject:(__bridge id)sacr forKey:(__bridge id)kSecAttrAccessControl];
    [loadDictionary setObject:context forKey:(__bridge id)kSecUseAuthenticationContext];

    CFDataRef data = NULL;
    CFDataRef passwordData = (__bridge CFDataRef)(appPassword);
    status = SecItemCopyMatching((CFDictionaryRef)loadDictionary, (CFTypeRef *)&data);
    if (status == errSecSuccess) {
       if ( 0 < CFDataGetLength(data)) {
            CFStringRef string = CFStringCreateWithBytes(kCFAllocatorDefault, CFDataGetBytePtr(passwordData), CFDataGetLength(passwordData), kCFStringEncodingUTF8, FALSE);
            if (string) {
                NSString *dataString = [[NSString alloc] initWithData:(__bridge NSData *)data encoding:NSUTF8StringEncoding];
                NSLog(@"data = %@", dataString);
                CFRelease(string);
            }
        }
    }
    else {
        NSLog(@"密码错误,无法访问");
    }
    if (data != NULL) {
        CFRelease(data);
    }
    CFRelease(sacr);
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  • 2017.12.27 发现一种特别舒服的关系,并不总是你一言我一语的秒回,有时候愿意把我现在看到的所有东西一股脑儿...
    雨情清阅读 93评论 0 0
  • 今早五点醒来想到这几天自己的状态,决定给自己做了一个脑身心塔罗个案。呈现出来的是脑和身体心分离了,得整合一下。所以...
    闵静阅读 307评论 0 1
  • 诶,还混了一篇17年的法医秦明
    想要一杯果西汁阅读 138评论 0 0
  • 做淘宝真的要刷单么?那些所谓的七天螺旋,如果你一个新开的店,没权重没流量,不刷单哪来的螺旋给你? 淘宝大环境下,小...
    我是盼盼呢阅读 9,711评论 1 16