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閱讀 227,533評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,055評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,365評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,561評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,346評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,889評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,118評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,637評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,558評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,739評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,980評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,619評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,347評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,702評論 2 370

推薦閱讀更多精彩內容

  • 2017.12.27 發現一種特別舒服的關系,并不總是你一言我一語的秒回,有時候愿意把我現在看到的所有東西一股腦兒...
    雨情清閱讀 99評論 0 0
  • 今早五點醒來想到這幾天自己的狀態,決定給自己做了一個腦身心塔羅個案。呈現出來的是腦和身體心分離了,得整合一下。所以...
    閔靜閱讀 313評論 0 1
  • 誒,還混了一篇17年的法醫秦明
    想要一杯果西汁閱讀 146評論 0 0
  • 做淘寶真的要刷單么?那些所謂的七天螺旋,如果你一個新開的店,沒權重沒流量,不刷單哪來的螺旋給你? 淘寶大環境下,小...
    我是盼盼呢閱讀 9,747評論 1 16