サーチ…
構文
- kSecClassGenericPassword //非インターネットパスワードを表す値キー
- kSecClassInternetPassword //インターネットパスワードを表す値のキー
- kSecClassCertificate //証明書を表す値キー
- kSecClassCertificate //キーを表す値キー
- kSecClassIdentity //アイデンティティを表す値キー。証明書とキーです。
備考
iOSは、パスワード、暗号化キー、証明書、IDなどの個人情報をキーチェーンと呼ばれる安全な記憶域に保存します。この記憶領域は、セキュア・エンクレーブと呼ばれるコプロセッサーによって完全に管理されます。セキュア・エンクレーブは、アプリケーション・プロセッサーの内部に組み込まれています。キーチェーンはiOS上でサンドボックス化されているため、キーチェーンアイテムは最初にそこに置くアプリケーションによってのみ取り出されます。
場合によっては、エラーを避けるためにXcode機能でキーチェーンの共有を有効にする必要があります。
キーチェーンと対話するために、キーチェーンサービスと呼ばれるacフレームワークを使用します。詳細については、 AppleのKeychain Services Programming Guideを参照してください。
Keychain ServicesはFoundation
レベルを下回っているため、 CoreFoundation
タイプの使用に限定されています。その結果、ほとんどのオブジェクトはCFString
キーとして保持し、さまざまなCoreFoundation
型をその値として保持するCFDictionary
として内部的に表現されます。
Keychain ServicesはSecurity
フレームワークの一部として組み込まれていますが、バックエンドにいくつかのヘルパ関数が含まれているため、 Foundation
インポートするのが一般的です。
さらに、Keychain Servicesを直接処理したくない場合、Appleはキーチェーンサービスを使用するSwiftタイプを提供するGeneric Keychain Swiftサンプルプロジェクトを提供しています。
キーチェーンへのパスワードの追加
すべてのキーチェーン項目は、最も頻繁にCFDictionary
として表されます。ただし、単にObjective-CでNSDictionary
を使用してブリッジングを利用することも、SwiftではDictionary
を使用して明示的にCFDictionary
にキャストすることもできます。
あなたは次の辞書でパスワードを作ることができます:
迅速
var dict = [String : AnyObject]()
まず、Keychainにこれがパスワードであることを知らせるキーと値のペアが必要です。私たちのdictキーはString
なので、Swift 3で明示的に任意のCFString
をString
キャストしなければならないことに注意してください。CFStringはHashableではないためSwift Dictionaryのキーとして使用できません。
迅速
dict[kSecClass as String] = kSecClassGenericPassword
次に、私たちのパスワードには、そのパスワードを記述し、後で見つけるのに役立つ一連の属性が含まれている場合があります。 以下は、一般的なパスワードの属性一覧です 。
迅速
// The password will only be accessible when the device is unlocked
dict[kSecAttrAccessible as String] = kSecAttrAccessibleWhenUnlocked
// Label may help you find it later
dict[kSecAttrLabel as String] = "com.me.myapp.myaccountpassword" as CFString
// Username
dict[kSecAttrAccount as String] = "My Name" as CFString
// Service name
dict[kSecAttrService as String] = "MyService" as CFString
最後に、実際の私的データが必要です。あまりにも長い間、これをメモリ内に保持しないようにしてください。これはCFData
なければなりません。
迅速
dict[kSecValueData as String] = "my_password!!".data(using: .utf8) as! CFData
最後に、Keychain Services add関数は、新しく構築されたキーチェーン項目をどのように戻すべきかを知りたいと考えています。メモリに非常に長いデータを保持してはならないので、次のように属性を返すだけです。
迅速
dict[kSecReturnAttributes as String] = kCFBooleanTrue
今私達はアイテムを作りました。それを追加しましょう:
迅速
var result: AnyObject?
let status = withUnsafeMutablePointer(to: &result) {
SecItemAdd(dict as CFDictionary, UnsafeMutablePointer($0))
}
let newAttributes = result as! Dictionary<String, AnyObject>
これは新しい属性をresult
中に置きます。 SecItemAdd
は、作成した辞書と、結果をどこにSecItemAdd
を示すポインタをSecItemAdd
ます。この関数は、成功またはエラーコードを示すOSStatus
を返します。 ここで結果コードについて説明します 。
キーチェーンでのパスワードの検索
クエリを作成するには、そのクエリをCFDictionary
として表現する必要があります。 Objective-CではNSDictionary
、SwiftではDictionary
を使用してCFDictionary
キャストすることもCFDictionary
ます。
クラスキーが必要です:
迅速
var dict = [String : AnyObject]()
dict[kSecClass as String] = kSecClassGenericPassword
次に、属性を指定して検索範囲を絞り込むことができます。
迅速
// Label
dict[kSecAttrLabel as String] = "com.me.myapp.myaccountpassword" as CFString
// Username
dict[kSecAttrAccount as String] = "My Name" as CFString
// Service name
dict[kSecAttrService as String] = "MyService" as CFString
ここで説明する特別な検索修飾キーを指定することもできます。
最後に、どのようにデータを返すのかを述べる必要があります。以下では、プライベートパスワード自体をCFData
オブジェクトとして返すようにCFData
ます。
迅速
dict[kSecReturnData as String] = kCFBooleanTrue
さあ、検索しましょう:
迅速
var queryResult: AnyObject?
let status = withUnsafeMutablePointer(to: &queryResult) {
SecItemCopyMatching(dict as CFDictionary, UnsafeMutablePointer($0))
}
// Don't keep this in memory for long!!
let password = String(data: queryResult as! Data, encoding: .utf8)!
ここでは、 SecItemCopyMatching
はクエリ辞書と、結果をどこに行きたいかを示すポインタを取り込みます。結果コードを含むOSStatus
を返します。 ここに可能性があります。
キーチェーンでのパスワードの更新
いつものように、私たちは最初に更新したいアイテムを表すためにCFDictionary
が必要です。これには、古いプライベートデータを含め、アイテムのすべての古い値が含まれている必要があります。次に、変更したい属性やデータそのもののCFDictionary
を取得します。
だから最初に、クラスキーと属性のリストを作成しましょう。これらの属性は検索範囲を絞り込むことができますが、変更する場合は属性や古い値を含める必要があります。
迅速
var dict = [String : AnyObject]()
dict[kSecClass as String] = kSecClassGenericPassword
// Label
dict[kSecAttrLabel as String] = "com.me.myapp.myaccountpassword" as CFString
// Username
dict[kSecAttrAccount as String] = "My Name" as CFString
これで古いデータを追加する必要があります:
迅速
dict[kSecValueData as String] = "my_password!!".data(using: .utf8) as! CFData
次に、同じ属性を作成して別のパスワードを作成してみましょう。
迅速
var newDict = [String : AnyObject]()
newDict[kSecClass as String] = kSecClassGenericPassword
// Label
newDict[kSecAttrLabel as String] = "com.me.myapp.myaccountpassword" as CFString
// Username
newDict[kSecAttrAccount as String] = "My Name" as CFString
// New password
newDict[kSecValueData as String] = "new_password!!".data(using: .utf8) as! CFData
今、Keychain Servicesに渡します:
迅速
let status = SecItemUpdate(dict as CFDictionary, newDict as CFDictionary)
SecItemUpdate
はステータスコードを返します。 ここで結果を説明します 。
キーチェーンからのパスワードの削除
Keychainから項目を削除するには、削除する項目を説明する属性を持つCFDictionary
を1つだけ必要とします。クエリ辞書に一致するアイテムは完全に削除されるので、1つのアイテムを削除するだけの場合は、クエリに固有のものであることを確認してください。いつものように、私たちはObjective-CでNSDictionary
を使うことができますし、SwiftではDictionary
を使ってCFDictionary
キャストすることもできます。
この文脈では、クエリ辞書は、項目が何であるかを記述するクラスキーと、項目に関する情報を記述する属性とを排他的に含む。 kSecMatchCaseInsensitive
などの検索制限をkSecMatchCaseInsensitive
はできません。
迅速
var dict = [String : AnyObject]()
dict[kSecClass as String] = kSecClassGenericPassword
// Label
dict[kSecAttrLabel as String] = "com.me.myapp.myaccountpassword" as CFString
// Username
dict[kSecAttrAccount as String] = "My Name" as CFString
そして今、私たちは単にそれを削除することができます:
迅速
let status = SecItemDelete(dict as CFDictionary)
SecItemDelete
返しOSStatus
。 ここで結果コードについて説明します 。
キーチェーン1つのファイルを使用して操作を追加、更新、削除、検索します。
Keychain.h
#import <Foundation/Foundation.h>
typedef void (^KeychainOperationBlock)(BOOL successfulOperation, NSData *data, OSStatus status);
@interface Keychain : NSObject
-(id) initWithService:(NSString *) service_ withGroup:(NSString*)group_;
-(void)insertKey:(NSString *)key withData:(NSData *)data withCompletion:(KeychainOperationBlock)completionBlock;
-(void)updateKey:(NSString*)key withData:(NSData*) data withCompletion:(KeychainOperationBlock)completionBlock;
-(void)removeDataForKey:(NSString*)key withCompletionBlock:(KeychainOperationBlock)completionBlock;
-(void)findDataForKey:(NSString*)key withCompletionBlock:(KeychainOperationBlock)completionBlock;
@end
キーチェーンm
#import "Keychain.h"
#import <Security/Security.h>
@implementation Keychain
{
NSString * keychainService;
NSString * keychainGroup;
}
-(id) initWithService:(NSString *)service withGroup:(NSString*)group
{
self =[super init];
if(self) {
keychainService = [NSString stringWithString:service];
if(group) {
keychainGroup = [NSString stringWithString:group];
}
}
return self;
}
-(void)insertKey:(NSString *)key
withData:(NSData *)data
withCompletion:(KeychainOperationBlock)completionBlock
{
NSMutableDictionary * dict =[self prepareDict:key];
[dict setObject:data forKey:(__bridge id)kSecValueData];
[dict setObject:keychainService forKey:(id)kSecAttrService];
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)dict, NULL);
if(errSecSuccess != status) {
DLog(@"Unable add item with key =%@ error:%d",key,(int)status);
if (completionBlock) {
completionBlock(errSecSuccess == status, nil, status);
}
}
if (status == errSecDuplicateItem) {
[self updateKey:key withData:data withCompletion:^(BOOL successfulOperation, NSData *updateData, OSStatus updateStatus) {
if (completionBlock) {
completionBlock(successfulOperation, updateData, updateStatus);
}
DLog(@"Found duplication item -- updating key with data");
}];
}
}
-(void)findDataForKey:(NSString *)key
withCompletionBlock:(KeychainOperationBlock)completionBlock
{
NSMutableDictionary *dict = [self prepareDict:key];
[dict setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
[dict setObject:keychainService forKey:(id)kSecAttrService];
[dict setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
CFTypeRef result = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)dict,&result);
if( status != errSecSuccess) {
DLog(@"Unable to fetch item for key %@ with error:%d",key,(int)status);
if (completionBlock) {
completionBlock(errSecSuccess == status, nil, status);
}
} else {
if (completionBlock) {
completionBlock(errSecSuccess == status, (__bridge NSData *)result, status);
}
}
}
-(void)updateKey:(NSString *)key
withData:(NSData *)data
withCompletion:(KeychainOperationBlock)completionBlock
{
NSMutableDictionary * dictKey =[self prepareDict:key];
NSMutableDictionary * dictUpdate =[[NSMutableDictionary alloc] init];
[dictUpdate setObject:data forKey:(__bridge id)kSecValueData];
[dictUpdate setObject:keychainService forKey:(id)kSecAttrService];
OSStatus status = SecItemUpdate((__bridge CFDictionaryRef)dictKey, (__bridge CFDictionaryRef)dictUpdate);
if( status != errSecSuccess) {
DLog(@"Unable to remove item for key %@ with error:%d",key,(int)status);
}
if (completionBlock) {
completionBlock(errSecSuccess == status, nil, status);
}
}
-(void)removeDataForKey:(NSString *)key
withCompletionBlock:(KeychainOperationBlock)completionBlock {
NSMutableDictionary *dict = [self prepareDict:key];
OSStatus status = SecItemDelete((__bridge CFDictionaryRef)dict);
if( status != errSecSuccess) {
DLog(@"Unable to remove item for key %@ with error:%d",key,(int)status);
}
if (completionBlock) {
completionBlock(errSecSuccess == status, nil, status);
}
}
#pragma mark Internal methods
-(NSMutableDictionary*) prepareDict:(NSString *) key {
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
NSData *encodedKey = [key dataUsingEncoding:NSUTF8StringEncoding];
[dict setObject:encodedKey forKey:(__bridge id)kSecAttrGeneric];
[dict setObject:encodedKey forKey:(__bridge id)kSecAttrAccount];
[dict setObject:keychainService forKey:(__bridge id)kSecAttrService];
[dict setObject:(__bridge id)kSecAttrAccessibleAlwaysThisDeviceOnly forKey:(__bridge id)kSecAttrAccessible];
//This is for sharing data across apps
if(keychainGroup != nil) {
[dict setObject:keychainGroup forKey:(__bridge id)kSecAttrAccessGroup];
}
return dict;
}
@end
キーチェーンのアクセス制御(パスワードのフォールバックによるTouchID)
Keychainは特別なSecAccessControl属性を持つアイテムを保存することができます。これにより、ユーザーはTouch ID(またはフォールバックが許可されている場合はパスコード)で認証された後にのみKeychainからアイテムを取得できます。認証が成功したかどうかだけがアプリに通知され、UI全体がiOSによって管理されます。
まず、SecAccessControlオブジェクトを作成する必要があります。
迅速
let error: Unmanaged<CFError>?
guard let accessControl = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, .userPresence, &error) else {
fatalError("Something went wrong")
}
次に、kSecAttrAccessControlキー(他の例で使用していたkSecAttrAccessibleキーと相互排他的です)を使用して辞書に追加します。
迅速
var dictionary = [String : Any]()
dictionary[kSecClass as String] = kSecClassGenericPassword
dictionary[kSecAttrLabel as String] = "com.me.myapp.myaccountpassword" as CFString
dictionary[kSecAttrAccount as String] = "My Name" as CFString
dictionary[kSecValueData as String] = "new_password!!".data(using: .utf8) as! CFData
dictionary[kSecAttrAccessControl as String] = accessControl
これまでと同じように保存してください:
迅速
let lastResultCode = SecItemAdd(query as CFDictionary, nil)
格納されたデータにアクセスするには、Keychainにキーを問い合わせます。キーチェーンサービスは、ユーザーに認証ダイアログを表示し、適切な指紋が提供されたか、パスコードが一致したかに応じて、データまたはなしを返します。
必要に応じて、プロンプト文字列を指定することもできます。
迅速
var query = [String: Any]()
query[kSecClass as String] = kSecClassGenericPassword
query[kSecReturnData as String] = kCFBooleanTrue
query[kSecAttrAccount as String] = "My Name" as CFString
query[kSecAttrLabel as String] = "com.me.myapp.myaccountpassword" as CFString
query[kSecUseOperationPrompt as String] = "Please put your fingers on that button" as CFString
var queryResult: AnyObject?
let status = withUnsafeMutablePointer(to: &queryResult) {
SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))
}
ユーザーが承認を拒否した場合、キャンセルした場合、または承認に失敗した場合は、 status
がerr
なることに注意してください。
迅速
if status == noErr {
let password = String(data: queryResult as! Data, encoding: .utf8)!
print("Password: \(password)")
} else {
print("Authorization not passed")
}