サーチ…


構文

  • 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で明示的に任意のCFStringStringキャストしなければならないことに注意してください。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))
}

ユーザーが承認を拒否した場合、キャンセルした場合、または承認に失敗した場合は、 statuserrなることに注意してください。

迅速

if status == noErr {
    let password = String(data: queryResult as! Data, encoding: .utf8)!
    print("Password: \(password)")
} else {
    print("Authorization not passed")
}


Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow