Keychain에 대해서 이해하는 것이 중요!
Keychain 정의
Keychain은 비밀번호나 인증 토큰과 같은 민감한 정보를 안전하게 저장할 수 있게 도와주는 Security 프레임워크의 기능입니다.
Apple Document : https://developer.apple.com/documentation/security/keychain_services
참고로 Keychain은 앱을 삭제해도 유지되는 값이므로, 앱 설계를 하실 때 매우 신중하게 선택해서 진행해 주셔야 합니다.
Keychain 저장
- 저장할 데이터 설정을 합니다.
- kSecClass 는 다양한 종류가 있지만, 여기서는 일반적인 비밀번호 항목인 kSecClassGenericPassword 로 가정해서 사용하겠습니다.
- kSecAttrAccount : 저장할 계정 및 아이디
- kSecValueData : 비밀번호
1 2 3 4 5
let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: account, kSecValueData as String: password ]
- 이전 데이터 삭제
- 중복 저장을 피하기 위해서 이전에 똑같은 데이터를 삭제합니다.(안해도 상관 없지만, 중복 저장되면 골치 아파서 무조건 해야됨)
1
SecItemDelete(query as CFDictionary)
- 중복 저장을 피하기 위해서 이전에 똑같은 데이터를 삭제합니다.(안해도 상관 없지만, 중복 저장되면 골치 아파서 무조건 해야됨)
- 데이터 저장
- 여기서 실제 Keychain 저장을 합니다. SecItemAdd(query as CFDictionary, nil)
Keychain 조회
- 조회할 데이터 설정을 합니다.
- kSecClass : 아까 저장할 때 설정했던 kSecClassGenericPassword 를 입력해 줍시다.
- kSecAttrAccount : 저장할 계정 및 아이디
- kSecMatchLimit : 검색 쿼리가 반환할 결과의 수를 제한하는 데 사용 되는데, 우리는 한 건에 대해서만 받아올 예정이니 kSecMatchLimitOne 를 사용해 줍시다. (다른 속성도 많은데 kSecMatchLimitAll 도 쓸 수 있습니다.)
- kSecReturnData : Data로 반환 받을지 설정
1 2 3 4 5 6
let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: account.data(using: .utf8)!, kSecMatchLimit as String: kSecMatchLimitOne, kSecReturnData as String: true ]
- 받아올 변수 선언
1
var item: CFTypeRef?
- 데이터 조회
1
let status = SecItemCopyMatching(query as CFDictionary, &item)
Keychain 저장 및 조회 예시
이론보다는 역시 실습이 최고겠죠?
저 같은 경우에는 Storyboard 예시가 아닌, code based 로 간단한 예시를 만들었습니다. 따라서, 직접 사용하게 되실 때 적절히 입맛에 맞게 사용하시면 될 것 같습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// 키체인을 이용한 저장
@objc func addToKeychain() {
guard let accountText = self.accountTextField.text,
let passwordText = self.passwordTextField.text
else {
return
}
let account = accountText.data(using: .utf8)!
let password = passwordText.data(using: .utf8)!
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: account,
kSecValueData as String: password
]
// 이전 항목 삭제
SecItemDelete(query as CFDictionary)
// 새 항목 추가
let status = SecItemAdd(query as CFDictionary, nil)
if status != errSecSuccess {
if let errorMessage = SecCopyErrorMessageString(status, nil) {
print("Add to keychain failed: \(errorMessage)")
}
}
}
// 키체인을 이용한 조회
@objc func getFromKeychain(){
guard let accountText = self.accountTextField.text else {
return
}
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: accountText.data(using: .utf8)!,
kSecMatchLimit as String: kSecMatchLimitOne,
kSecReturnData as String: true
]
var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
if status == errSecSuccess,
let data = item as? Data,
let password = String(data: data, encoding: .utf8) {
self.resultLabel.text = password
} else {
if let errorMessage = SecCopyErrorMessageString(status, nil) {
print("키체인 조회 시: \(errorMessage)")
}
self.resultLabel.text = ""
}
}
github 예시 주소
https://github.com/JacksonJang/KeychainExample