수색…


통사론

  • 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에서 명시 적으로 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 는 우리가 생성 한 사전뿐만 아니라 결과를 원하는 곳의 포인터를 가져옵니다. 이 함수는 성공 또는 오류 코드를 나타내는 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))
}

사용자가 승인을 거부하거나 취소했거나 승인에 실패하면 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