[iOS] Keychain(키체인) 알아보기
데이터를 저장하는 방법에는 UserDefaults, CoreData, Keychain... 등 여러가지 방법이 있다.
그러나 데이터의 종류가 다양하기 때문에 각각의 방법들의 쓰임새가 다른데 많고 많은 방법 중 키체인 방식에 대해 알아보고자 한다.
Keychain(키체인)이란?
https://developer.apple.com/documentation/security/keychain_services
Keychain Services | Apple Developer Documentation
Securely store small chunks of data on behalf of the user.
developer.apple.com
애플 공식 문서에 따르면 안전하게 저장해야하는 작은 데이터를 저장할 때 사용한다고 한다.
위 그림처럼 비밀번호, 암호키, 인증키, 메모 등의 정보를 암호화하여 저장할 수 있다.
처음에 든 생각은 키체인이 안전하게 데이터를 관리한다고 하는데 어떻게 안전하게 관리하는지 작은 데이터를 저장할때는 UserDefaults를 사용한다고 했는데 두개가 무슨 차이점이 있는건지 궁금했다.
아무래도 UserDefaults와 Keychain등 애플에서 만든것이기 때문에 차이점은 있을테니 하나씩 알아보자.
UserDefaults vs Keychain
UserDefaults와 어떤 차이가 있을까? 왜 키체인을 사용할까?
먼저 키체인과 비교하기 전 UserDefaults를 알아보면,
UserDefaults
- plist파일에 데이터가 저장되어 유저가 탐색기를 통해 접근이 가능 -> (보안이 중요하지 않은 데이터만 저장 권장)
- 앱 삭제 시 데이터가 함께 사라짐 -> (SandBox 내부 영역에 데이터가 저장)
- 앱이 시작될 때 plist파일이 로드되어 싱글톤 형태로 접근 가능 -> (너무 데이터가 거대하면 로드 시 앱 성능 저하)
- AppGroup을 통해 App과 Extension간 UserDefaults 데이터 공유가 가능
이것저것 특징이 많긴 했지만 중요하다고 생각되는 부분 몇개만 가져왔다.
반면 키체인의 특징은 다음과 같다.
Keychain
- 앱을 삭제해도 데이터가 남아 있음 -> (SandBox 밖에 데이터 저장)
- 디바이스가 Lock 시 키체인도 Lock, 디바이스가 unLock시 키체인도 unLock 됨
- Secure Enclave라는 암호화 기능을 사용하는 하드웨어에서 키체인 데이터를 관리함
- Keychain Sharing을 통해 동일한 개발자에 속한 서로 다른 앱 간 키체인 공유가 가능함
Keychain item
- 데이터를 Item으로 패키징하며, 저장하려는 데이터 뿐만 아니라 Attributes도 함께 저장됨 -> 이 Attribute를 통해 Data에 접근한다.
1. Involve the User When Needed (오른쪽 그림)
앱에 권한이 처음 필요할 때는 키체인 암호가 저장 안됨 -> 사용자에게 권한 요청 -> 사용자 권한 허용 -> 앱은 권한 저장(SecItemAdd(_:_:)) -> 추후 키체인에서 권한 검색
2. Avoid Bothering the User in the Common Case
키체인에서 비밀번호 검색(SecItemCopyMatching(_:_:)) -> 앱에서 인증 -> 사용자 개입없이 진행 가능
3. Handle Changes Gracefully
사용자가 앱 영역 밖에서 비밀번호 변경 ->새로운 비밀번호를 받아 기존 저장 값 수정 (SecItemUpdate) / 키체인에서 비밀번호 완전 삭제(SecItemDelete)
Secure Enclave
위 사진 파란색 영역이 Secure Enclave라고 설명하고 있다. 해당 영역은 메인 프로세서와 격리되어 추가적인 보안 계층을 제공하여, 커널이 손상된 경우라고 해도 사용자의 데이터를 안전하게 보관할 수 있도록 설계되어 있다.
이 Secure Enclave라는 영역에 Touch id, Face id, Security Framework, Cryptokit과 같은 것들이 저장된다.
Secure Enclave는 import, export를 하지 않기 때문에 다른곳에서 생성한 키를 저장하고 빼낼 수 없다. -> 키를 생성한 디바이스에서만 암호화가 가능하다.
KSecClass
이 아이템이 어떤 종류의 정보인지 나타난다. 종류는 아래와 같다.
@available(iOS 2.0, *)
public let kSecClassInternetPassword: CFString
@available(iOS 2.0, *)
public let kSecClassGenericPassword: CFString
@available(iOS 2.0, *)
public let kSecClassCertificate: CFString
@available(iOS 2.0, *)
public let kSecClassKey: CFString
@available(iOS 2.0, *)
public let kSecClassIdentity: CFString
Predefined item class constants used to get or set values in a dictionary.
The kSecClass constant is the key and its value is one of the constants defined here.
-> 아래 목록 중 적절한 것을 골라 사용하면 된다!
1. kSecClassInternetPassword
- 인터넷 password item
2. kSecClassGenericPassword
- 일반 password item
3. kSecClassCertificate
- 인증서 item
4. kSecClassKey
- key item
5. kSecClassIdentity
- id item
그리고.. 이 5가지 Class안에 많은 attributes가 있는데 너무 많아서 공식문서 참고하는게 좋겠다!! (https://developer.apple.com/documentation/security/keychain_services/keychain_items/item_attribute_keys_and_values)
그리고 마지막으로 키체인 CRUD 메소드를 알아보쟈..~
KeyChain Method
1. Create
var account = "" // 고유한 키
func create(_ valueData: String) {
let data = valueData.data(using: String.Encoding.utf8)! // item Class
let query: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
kSecAttrAccount: account, // 계정 ID 정보
kSecValueData: data] // 저장하려는 데이터 (비밀번호)
let status = SecItemAdd(query as CFDictionary, nil)
switch status {
case errSecSuccess:
print("등록 성공")
case errSecDuplicateItem: // 중복
update(valueData)
default:
print("등록 실패")
}
}
SecItemAdd(_:_:) 를 사용하면 키체인 등록이 가능하다.
2. read
var account = "" // 고유한 키
func read() -> Any? {
let query: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
kSecAttrAccount: account,
kSecReturnAttributes: true,
kSecReturnData: true]
var item: CFTypeRef?
if SecItemCopyMatching(query as CFDictionary, &item) != errSecSuccess { return nil }
guard let existingItem = item as? [String: Any],
let data = existingItem[kSecValueData as String] as? Data,
let valueData = String(data: data, encoding: .utf8) else { return nil }
return valueData
}
SecItemCopyMatching(_:_:)를 사용하면 키체인 조회가 가능하다.
3. update
func update(_ valueData: Any) {
let previousQuery: [CFString: Any] = [kSecClass: kSecClassGenericPassword, kSecAttrAccount: account]
let updateQuery: [CFString: Any] = [kSecValueData: valueData]
let status = SecItemUpdate(previousQuery as CFDictionary, updateQuery as CFDictionary)
switch status {
case errSecSuccess:
print("수정 성공")
default:
print("수정 실패")
}
}
SecItemUpdate(_:_:)를 사용하면 키체인 수정이 가능하다.
4. delete
func delete(key: String) {
let deleteQuery: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
kSecAttrAccount: key]
let status = SecItemDelete(deleteQuery as CFDictionary)
switch status {
case errSecSuccess:
print("삭제 성공")
default:
print("삭제 실패")
}
SecItemDelete(_:)를 사용하면 키체인 삭제가 가능하다.
참고