Keychain的使用:KeychainTouchIDUsingTouchIDwithKeychainandLocalAuthentication

苹果官方文档地址


keychain保护

/* 设置keychain项保护等级 */

NSDictionary *query = @{(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,

(__bridge id)kSecAttrGeneric:@"MyItem",

(__bridge id)kSecAttrAccount:@"username",

(__bridge id)kSecValueData:@"password",

(__bridge id)kSecAttrService:[NSBundle mainBundle].bundleIdentifier,

(__bridge id)kSecAttrLabel:@"",

(__bridge id)kSecAttrDescription:@"",

(__bridge id)kSecAttrAccessible:(__bridge id)kSecAttrAccessibleWhenUnlocked};

OSStatus result = SecItemAdd((__bridge CFDictionaryRef)(query), NULL);

//keychain项保护等级列表

kSecAttrAccessibleWhenUnlocked                          //keychain项受到保护,只有在设备未被锁定时才可以访问

kSecAttrAccessibleAfterFirstUnlock                      //keychain项受到保护,直到设备启动并且用户第一次输入密码

kSecAttrAccessibleAlways                                //keychain未受保护,任何时候都可以访问 (Default)

kSecAttrAccessibleWhenUnlockedThisDeviceOnly            //keychain项受到保护,只有在设备未被锁定时才可以访问,而且不可以转移到其他设备

kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly        //keychain项受到保护,直到设备启动并且用户第一次输入密码,而且不可以转移到其他设备

kSecAttrAccessibleAlwaysThisDeviceOnly                  //keychain未受保护,任何时候都可以访问,但是不能转移到其他设备

http://www.cnblogs.com/ios8/p/ios-data-protect.html


iOS钥匙串KeyChain相关参数的说明

keychain(二)

//以上为参考文章

KeychainTouchID官方DEMO


增删改查的时候:kSecAttrService:的值保持一致

(id)kSecValueData: 内容(要保存的信息)

/*

Copyright (C) 2016 Apple Inc. All Rights Reserved.

See LICENSE.txt for this sample’s licensing information

Abstract:

Keychain with Touch ID demo implementation.

*/

#import "AAPLKeychainTestsViewController.h"

#import "AAPLTest.h"

@import Security;

@import LocalAuthentication;

@implementation AAPLKeychainTestsViewController

- (void)viewDidLoad {

[super viewDidLoad];

// prepare the actions which can be tested in this class

self.tests = @[

[[AAPLTest alloc] initWithName:@"Add item" details:@"Using SecItemAdd()" selector:@selector(addItemAsync)],

[[AAPLTest alloc] initWithName:@"Add item (TouchID only)" details:@"Using SecItemAdd()" selector:@selector(addTouchIDItemAsync)],

[[AAPLTest alloc] initWithName:@"Add item (TouchID and password)" details:@"Using SecItemAdd()" selector:@selector(addPwdItem)],

[[AAPLTest alloc] initWithName:@"Query for item" details:@"Using SecItemCopyMatching()" selector:@selector(copyMatchingAsync)],

[[AAPLTest alloc] initWithName:@"Update item" details:@"Using SecItemUpdate()" selector:@selector(updateItemAsync)],

[[AAPLTest alloc] initWithName:@"Delete item" details:@"Using SecItemDelete()" selector:@selector(deleteItemAsync)],

[[AAPLTest alloc] initWithName:@"Add protected key" details:@"Using SecKeyCreateRandomKey()" selector:@selector(generateKeyAsync)],

[[AAPLTest alloc] initWithName:@"Use protected key" details:@"Using SecKeyCreateSignature()" selector:@selector(useKeyAsync)],

[[AAPLTest alloc] initWithName:@"Delete protected key" details:@"Using SecItemDelete()" selector:@selector(deleteKeyAsync)]

];

}

- (void)viewWillAppear:(BOOL)animated {

[super viewWillAppear:animated];

[self.textView scrollRangeToVisible:NSMakeRange(self.textView.text.length, 0)];

}

- (void)viewDidLayoutSubviews {

// Set the proper size for the table view based on its content.

CGFloat height = MIN(self.view.bounds.size.height, self.tableView.contentSize.height);

self.dynamicViewHeight.constant = height;

[self.view layoutIfNeeded];

}

#pragma mark - Tests

//查

- (void)copyMatchingAsync {

NSDictionary *query = @{

(id)kSecClass: (id)kSecClassGenericPassword,

(id)kSecAttrService: @"SampleService",

(id)kSecReturnData: @YES,

(id)kSecUseOperationPrompt: @"Authenticate to access service password",

};

dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

CFTypeRef dataTypeRef = NULL;

NSString *message;

OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)(query), &dataTypeRef);

if (status == errSecSuccess) {

NSData *resultData = (__bridge_transfer NSData *)dataTypeRef;

NSString *result = [[NSString alloc] initWithData:resultData encoding:NSUTF8StringEncoding];

message = [NSString stringWithFormat:@"Result: %@\n", result];

}

else {

message = [NSString stringWithFormat:@"SecItemCopyMatching status: %@", [self keychainErrorToString:status]];

}

[self printMessage:message inTextView:self.textView];

});

}

//更新

- (void)updateItemAsync {

NSDictionary *query = @{

(id)kSecClass: (id)kSecClassGenericPassword,

(id)kSecAttrService: @"SampleService",

(id)kSecUseOperationPrompt: @"Authenticate to update your password"

};

NSData *updatedSecretPasswordTextData = [@"UPDATED_SECRET_PASSWORD_TEXT" dataUsingEncoding:NSUTF8StringEncoding];

NSDictionary *changes = @{

(id)kSecValueData: updatedSecretPasswordTextData

};

dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

OSStatus status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)changes);

NSString *errorString = [self keychainErrorToString:status];

NSString *message = [NSString stringWithFormat:@"SecItemUpdate status: %@", errorString];

[super printMessage:message inTextView:self.textView];

});

}

//增加

- (void)addItemAsync {

CFErrorRef error = NULL;

// Should be the secret invalidated when passcode is removed? If not then use kSecAttrAccessibleWhenUnlocked

SecAccessControlRef sacObject = SecAccessControlCreateWithFlags(kCFAllocatorDefault,

kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,

kSecAccessControlUserPresence, &error);

if (sacObject == NULL || error != NULL) {

NSString *errorString = [NSString stringWithFormat:@"SecItemAdd can't create sacObject: %@", error];

self.textView.text = [self.textView.text stringByAppendingString:errorString];

return;

}

// we want the operation to fail if there is an item which needs authentication so we will use

// kSecUseNoAuthenticationUI //(__bridge id)kSecAttrSynchronizable : @YES,

NSDictionary *attributes = @{

(id)kSecClass: (id)kSecClassGenericPassword,

(id)kSecAttrService: @"SampleService",

(id)kSecValueData: [@"password-Or-account" dataUsingEncoding:NSUTF8StringEncoding],

(id)kSecUseAuthenticationUI: (id)kSecUseAuthenticationUIAllow,

(id)kSecAttrAccessControl: (__bridge_transfer id)sacObject

};

dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

OSStatus status =  SecItemAdd((__bridge CFDictionaryRef)attributes, nil);

NSString *errorString = [self keychainErrorToString:status];

NSString *message = [NSString stringWithFormat:@"SecItemAdd status: %@", errorString];

[self printMessage:message inTextView:self.textView];

});

}

- (void)addTouchIDItemAsync {

CFErrorRef error = NULL;

// Should be the secret invalidated when passcode is removed? If not then use kSecAttrAccessibleWhenUnlocked

SecAccessControlRef sacObject = SecAccessControlCreateWithFlags(kCFAllocatorDefault,

kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,

kSecAccessControlTouchIDAny, &error);

if (sacObject == NULL || error != NULL) {

NSString *errorString = [NSString stringWithFormat:@"SecItemAdd can't create sacObject: %@", error];

self.textView.text = [self.textView.text stringByAppendingString:errorString];

return;

}

/*

We want the operation to fail if there is an item which needs authentication so we will use

`kSecUseNoAuthenticationUI`.

*/

NSData *secretPasswordTextData = [@"SECRET_PASSWORD_TEXT" dataUsingEncoding:NSUTF8StringEncoding];

NSDictionary *attributes = @{

(id)kSecClass: (id)kSecClassGenericPassword,

(id)kSecAttrService: @"SampleService",

(id)kSecValueData: secretPasswordTextData,

(id)kSecUseAuthenticationUI: (id)kSecUseAuthenticationUIAllow,

(id)kSecAttrAccessControl: (__bridge_transfer id)sacObject

};

dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

OSStatus status =  SecItemAdd((__bridge CFDictionaryRef)attributes, nil);

NSString *message = [NSString stringWithFormat:@"SecItemAdd status: %@", [self keychainErrorToString:status]];

[self printMessage:message inTextView:self.textView];

});

}

- (void)addPwdItem {

CFErrorRef error = NULL;

// Should be the secret invalidated when passcode is removed? If not then use kSecAttrAccessibleWhenUnlocke.

SecAccessControlRef sacObject = SecAccessControlCreateWithFlags(kCFAllocatorDefault,

kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,

kSecAccessControlTouchIDAny | kSecAccessControlApplicationPassword, &error);

if (sacObject == NULL || error != NULL) {

NSString *errorString = [NSString stringWithFormat:@"SecItemAdd can't create sacObject: %@", error];

self.textView.text = [self.textView.text stringByAppendingString:errorString];

return;

}

LAContext *context = [[LAContext alloc] init];

[context evaluateAccessControl:sacObject operation:LAAccessControlOperationCreateItem localizedReason:@"Create Item" reply:^(BOOL success, NSError * error) {

if (success) {

/*

We want the operation to fail if there is an item which needs authentication so we will use

`kSecUseNoAuthenticationUI`.

*/

NSData *secretPasswordTextData = [@"SECRET_PASSWORD_TEXT" dataUsingEncoding:NSUTF8StringEncoding];

NSDictionary *attributes = @{

(id)kSecClass: (id)kSecClassGenericPassword,

(id)kSecAttrService: @"SampleService",

(id)kSecValueData: secretPasswordTextData,

(id)kSecUseAuthenticationUI: (id)kSecUseAuthenticationUIAllow,

(id)kSecAttrAccessControl: (__bridge_transfer id)sacObject,

(id)kSecUseAuthenticationContext: context

};

OSStatus status = SecItemAdd((__bridge CFDictionaryRef)attributes, nil);

NSString *error = [self keychainErrorToString:status];

NSString *message = [NSString stringWithFormat:@"SecItemAdd status: %@", error];

[self printMessage:message inTextView:self.textView];

}

else {

[self printMessage:error.description inTextView:self.textView];

CFRelease(sacObject);

}

}];

}

- (void)deleteItemAsync {

NSDictionary *query = @{

(id)kSecClass: (id)kSecClassGenericPassword,

(id)kSecAttrService: @"SampleService"

};

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);

NSString *errorString = [self keychainErrorToString:status];

NSString *message = [NSString stringWithFormat:@"SecItemDelete status: %@", errorString];

[super printMessage:message inTextView:self.textView];

});

}

- (void)generateKeyAsync {

CFErrorRef error = NULL;

SecAccessControlRef sacObject;

// Should be the secret invalidated when passcode is removed? If not then use `kSecAttrAccessibleWhenUnlocked`.

sacObject = SecAccessControlCreateWithFlags(kCFAllocatorDefault,

kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,

kSecAccessControlTouchIDAny | kSecAccessControlPrivateKeyUsage, &error);

// Create parameters dictionary for key generation.

NSDictionary *parameters = @{

(id)kSecAttrTokenID: (id)kSecAttrTokenIDSecureEnclave,

(id)kSecAttrKeyType: (id)kSecAttrKeyTypeECSECPrimeRandom,

(id)kSecAttrKeySizeInBits: @256,

(id)kSecAttrLabel: @"my-se-key",

(id)kSecPrivateKeyAttrs: @{

(id)kSecAttrAccessControl: (__bridge_transfer id)sacObject,

(id)kSecAttrIsPermanent: @YES,

}

};

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

// Generate key pair.

NSError *gen_error = nil;

id privateKey = CFBridgingRelease(SecKeyCreateRandomKey((__bridge CFDictionaryRef)parameters, (void *)&gen_error));

if(privateKey != nil) {

// use the private key in your code

[self printMessage:@"Key generation success" inTextView:self.textView];

}

else {

NSString *message = [NSString stringWithFormat:@"Key generation error: %@", gen_error];

[self printMessage:message inTextView:self.textView];

}

});

}

- (void)useKeyAsync {

// Query private key object from the keychain.

NSDictionary *params = @{

(id)kSecClass: (id)kSecClassKey,

(id)kSecAttrKeyType: (id)kSecAttrKeyTypeECSECPrimeRandom,

(id)kSecAttrKeySizeInBits: @256,

(id)kSecAttrLabel: @"my-se-key",

(id)kSecReturnRef: @YES,

(id)kSecUseOperationPrompt: @"Authenticate to sign data"

};

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

// Retrieve the key from the keychain.  No authentication is needed at this point.

SecKeyRef privateKey;

OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)params, (CFTypeRef *)&privateKey);

if (status == errSecSuccess) {

NSError *error;

NSData *dataToSign = [@"message" dataUsingEncoding:NSUTF8StringEncoding];

NSData *signature = CFBridgingRelease(SecKeyCreateSignature(privateKey, kSecKeyAlgorithmECDSASignatureMessageX962SHA256, (CFDataRef)dataToSign, (void *)&error));

NSString *message = [NSString stringWithFormat:@"Key used: %@", error];

[self printMessage:message inTextView:self.textView];

if (status == errSecSuccess) {

// In your own code, here is where you'd continue with the signature of the digest.

id publicKey = CFBridgingRelease(SecKeyCopyPublicKey((SecKeyRef)privateKey));

Boolean verified = SecKeyVerifySignature((SecKeyRef)publicKey, kSecKeyAlgorithmECDSASignatureMessageX962SHA256, (CFDataRef)dataToSign, (CFDataRef)signature, (void *)&error);

message = [NSString stringWithFormat:@"signature:%@ verified:%d error:%@", signature, verified, error];

[self printMessage:message inTextView:self.textView];

}

CFRelease(privateKey);

}

else {

NSString *message = [NSString stringWithFormat:@"Key not found: %@",[self keychainErrorToString:status]];

[self printMessage:message inTextView:self.textView];

}

});

}

- (void)deleteKeyAsync {

NSDictionary *query = @{

(id)kSecAttrTokenID: (id)kSecAttrTokenIDSecureEnclave,

(id)kSecClass: (id)kSecClassKey,

(id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate,

(id)kSecAttrLabel: @"my-se-key",

(id)kSecReturnRef: @YES,

};

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);

NSString *message = [NSString stringWithFormat:@"SecItemDelete status: %@", [self keychainErrorToString:status]];

[self printMessage:message inTextView:self.textView];

});

}

#pragma mark - Tools

- (NSString *)keychainErrorToString:(OSStatus)error {

NSString *message = [NSString stringWithFormat:@"%ld", (long)error];

switch (error) {

case errSecSuccess:

message = @"success";

break;

case errSecDuplicateItem:

message = @"error item already exists";

break;

case errSecItemNotFound :

message = @"error item not found";

break;

case errSecAuthFailed:

message = @"error item authentication failed";

break;

default:

break;

}

return message;

}

@end

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容