수색…
통사론
- kSecClassGenericPassword // 비 인터넷 암호를 나타내는 값 키
- kSecClassInternetPassword // 인터넷 비밀번호를 나타내는 값 키
- kSecClassCertificate // 인증서를 나타내는 값 키
- kSecClassCertificate // 키를 나타내는 값 키
- kSecClassIdentity // 신원을 나타내는 값 키 (인증서 + 키)
비고
iOS는 암호, 암호화 키, 인증서 및 ID와 같은 개인 정보를 키 체인이라는 안전한 저장 영역에 저장합니다. 이 저장 영역은 응용 프로그램 프로세서 내부에 내장 된 보안 엔클로 지 (Enclave)라는 코 프로세서에 의해 완벽하게 관리됩니다. 키 체인은 iOS에서 샌드 박싱되어 있기 때문에 키 체인 항목은 처음부터 키 체인 항목을 가져온 응용 프로그램에서만 검색 할 수 있습니다.
어떤 경우에는 오류를 피하기 위해 Xcode 기능에서 키 체인 공유를 켜야합니다.
키 체인과 상호 작용하기 위해 키 체인 서비스라는 ac 프레임 워크를 사용합니다. 자세한 내용은 Apple의 키 체인 서비스 프로그래밍 안내서를 참조하십시오 .
Keychain Services는 Foundation
수준에 미치지 못하기 때문에 CoreFoundation
유형을 사용하는 것으로 제한됩니다. 결과적으로, 대부분의 객체는 CFString
키로, 다양한 CoreFoundation
유형을 값으로 갖는 CFDictionary
로 내부적으로 표현됩니다.
키 체인 서비스는 Security
프레임 워크의 일부로 포함되지만 백엔드에 몇 가지 도우미 기능이 포함되어 있으므로 가져 오기 Foundation
은 일반적으로 좋은 옵션입니다.
또한 키 체인 서비스를 직접 처리하지 않으려는 경우 Apple은 Keychain Services를 사용하는 신속한 유형을 제공하는 Generic Keychain Swift 샘플 프로젝트를 제공합니다.
키 체인에 암호 추가
모든 키 체인 항목은 가장 자주 CFDictionary
로 표시됩니다. 그러나 Objective-C에서 NSDictionary
를 사용하고 브리징을 활용하거나 Swift에서 Dictionary
사용하여 명시 적으로 CFDictionary
캐스트 할 수 있습니다.
다음 사전을 사용하여 비밀번호를 구성 할 수 있습니다.
빠른
var dict = [String : AnyObject]()
먼저 키 체인에서 암호임을 알 수있는 키 / 값 쌍이 필요합니다. 우리의 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
는 우리가 생성 한 사전뿐만 아니라 결과를 원하는 곳의 포인터를 가져옵니다. 이 함수는 성공 또는 오류 코드를 나타내는 OSStatus
를 반환합니다. 결과 코드는 여기 에 설명되어 있습니다 .
키 체인에서 암호 찾기
쿼리를 생성하려면 CFDictionary
로 표현해야합니다. Objective-C에서 NSDictionary
를 사용하거나 Swift에서 Dictionary
를 사용하여 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
객체로 반환하도록 요청할 것입니다.
빠른
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
. 쿼리 사전과 일치하는 항목은 영구적으로 삭제되므로 단일 항목 만 삭제하려는 경우 검색어와 관련이 있는지 확인하십시오. 항상 그렇듯이 Objective-C에서 NSDictionary
를 사용할 수도 있고 Swift에서 Dictionary
사용하여 CFDictionary
로 캐스팅 할 수도 있습니다.
이 컨텍스트에서 쿼리 사전은 항목이 무엇인지 설명하는 클래스 키와 항목에 대한 정보를 설명하는 속성을 독점적으로 포함합니다. 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
. 결과 코드는 여기 에 설명되어 있습니다 .
키 체인 하나의 파일을 사용하여 작업 추가, 업데이트, 제거 및 찾기.
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
키 체인.
#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 (또는 폴백이 허용되는 경우 패스 코드)로 인증 된 후에 만 키 체인에서 항목을 가져올 수 있습니다. 앱 인증이 성공했는지 여부 만 알리는 경우 전체 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")
}