iOS 앱 개발 과정에서 로컬(모바일 기기)에 특정 정보를 저장할 때
간단하게는 유저디폴트(UserDefaults)부터 코어데이터, Realm, SQLite 등 여러 저장 방식을 사용할 수 있습니다.
하지만 정말 중요한 사용자 정보들(예를 들어 패스워드, 프라이빗 키, 인증서 등)을
아무 곳에나 저장하는 것은 아주 위험하다고 할 수 있겠습니다!
민감한 정보를 안전하게 저장하기 위해 애플은 Keychain Services를 제공합니다.



여러분들은 사용자의 민감 정보를 암호화 해서 키체인에 저장하고, 이후에 이를 다시 복호화 해서 사용할 수 있습니다!
결국 중요한 것은 사용 방법이겠지요?
키체인을 생성(Create)하고, 읽고(Read), 지우는(Delete) 방법을 한 번 알아봅시다!
바로 전체 코드를 보여드리겠습니다!
import UIKit | |
class ViewController: UIViewController { | |
let account = UIDevice.current.identifierForVendor?.uuidString ?? "" // 기기의 UUID | |
let service = Bundle.main.bundleIdentifier ?? "" // 앱의 번들아이덴티파이어 | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
print("viewDidLoad - 기존에 있던 패스워드 지우기") | |
print("어카운트: \(account), 서비스: \(service)") | |
deletePassword() | |
} | |
override func viewWillAppear(_ animated: Bool) { | |
super.viewWillAppear(animated) | |
print("viewDidLoad - 새로 패스워드를 저장하기") | |
savePassword() | |
} | |
override func viewDidAppear(_ animated: Bool) { | |
super.viewDidAppear(animated) | |
print("viewWillAppear - 새로 저장한 패스워드 불러오기") | |
getPassword() | |
} | |
func savePassword() { | |
do { | |
try KeychainManager.save( | |
service: service, | |
account: account, | |
password: "내가 저장하고 싶은 패스워드".data(using: .utf8) ?? Data() // 여기에 여러분이 저장하고자 하는 중요 정보를 넣으시면 됩니다 | |
) | |
} catch { | |
print(error) | |
} | |
} | |
func getPassword() { | |
guard let data = KeychainManager.get( | |
service: service, | |
account: account | |
) else { | |
print("getPassword() - 불러올 수 있는 패스워드가 없습니다") | |
return | |
} | |
let password = String(decoding: data, as: UTF8.self) | |
print("getPassword() - 불러온 패스워드: \(password)") | |
} | |
func deletePassword() { | |
guard let data = KeychainManager.get( | |
service: service, | |
account: account | |
) else { | |
print("deletePassword() - 불러올 수 있는 패스워드가 없습니다") | |
return | |
} | |
KeychainManager.delete(service: service, account: account, password: data) | |
} | |
} | |
class KeychainManager { | |
enum KeychainError: Error { | |
case duplicateEntry | |
case unknown(OSStatus) | |
} | |
static func save(service: String, account: String, password: Data) throws { | |
let query: [String: AnyObject] = [ | |
kSecClass as String: kSecClassGenericPassword, // 키체인 아이템 클래스 타입 | |
kSecAttrService as String: service as AnyObject, // 서비스 아이디 -> 앱 번들 아이디 | |
kSecAttrAccount as String: account as AnyObject, // 저장할 아이템의 계정 이름 | |
kSecValueData as String: password as AnyObject // 저장할 아이템의 데이터 | |
] | |
let status = SecItemAdd(query as CFDictionary, nil) // 키체인에 하나 이상의 항목을 추가할 때 사용 | |
guard status != errSecDuplicateItem else { | |
throw KeychainError.duplicateEntry | |
} | |
guard status == errSecSuccess else { | |
throw KeychainError.unknown(status) | |
} | |
print("save() - status: \(status)") | |
} | |
static func get(service: String, account: String) -> Data? { | |
let query: [String: AnyObject] = [ | |
kSecClass as String: kSecClassGenericPassword, | |
kSecAttrService as String: service as AnyObject, | |
kSecAttrAccount as String: account as AnyObject, | |
kSecReturnData as String: kCFBooleanTrue, | |
kSecMatchLimit as String: kSecMatchLimitOne | |
] | |
var result: AnyObject? | |
// 검색 쿼리와 일치하는 키체인 항목을 하나 이상 반환하는 기능, 특정 키 체인 항목의 속성을 복사할 수 있음 | |
let status = SecItemCopyMatching( | |
query as CFDictionary, | |
&result | |
) | |
print("get() - status: \(status)") | |
return result as? Data | |
} | |
static func delete(service: String, account: String, password: Data) { | |
let query: [String: AnyObject] = [ | |
kSecClass as String: kSecClassGenericPassword, // 키체인 아이템 클래스 타입 | |
kSecAttrService as String: service as AnyObject, // 서비스 아이디 -> 앱 번들 아이디 | |
kSecAttrAccount as String: account as AnyObject, // 저장할 아이템의 계정 이름 | |
kSecValueData as String: password as AnyObject // 저장할 아이템의 데이터 | |
] | |
let passwordToDelete = String(decoding: password, as: UTF8.self) | |
print("delete() - 삭제할 패스워드 - \(passwordToDelete)") | |
let status = SecItemDelete(query as CFDictionary) | |
print("delete() - status: \(status)") | |
} | |
} |
전체 GitHub Code는 여기에서 다운로드 받으시면 됩니다!
https://github.com/TDCIAN/KeychainExample
GitHub - TDCIAN/KeychainExample: based on: https://www.youtube.com/watch?v=cQjgBIJtMbw&list=RDCMUCnksRRifsSCGUZpQukUKAyg&start_r
based on: https://www.youtube.com/watch?v=cQjgBIJtMbw&list=RDCMUCnksRRifsSCGUZpQukUKAyg&start_radio=1&rv=cQjgBIJtMbw&t=1053 - GitHub - TDCIAN/KeychainExample: based on: https://www....
github.com
위 코드는 참고 자료에 있는 여러 내용들을 종합해서 제가 별도로 만든 내용입니다.
가장 기초적인 영상 설명을 원하신다면 이 링크를 클릭해주세요!
https://www.youtube.com/watch?v=cQjgBIJtMbw%20
키체인의 특성상 유저디폴트와 다르게 여러분이 앱을 한 번 삭제 하더라도
저장된 내용(정확하게는 키체인에서 kSecAttrService와 kSecAttrAccount로 조회했을 때 일치하는 내용)은 지워지지 않습니다!
* 혹시라도 잘못된 부분을 발견하셨거나, 조언하고 싶으신 내용이 있으시다면 댓글로 남겨주세요!
참고 자료
- https://www.youtube.com/watch?v=cQjgBIJtMbw
- https://developer.apple.com/documentation/security/keychain_services
'iOS > Common' 카테고리의 다른 글
[Swift] lazy 왜 쓰나요? (0) | 2022.05.13 |
---|---|
[Code Snippet] private lazy var 쓰다가 팔 부러질 것 같을 때 보는 글 (0) | 2022.04.29 |
Test Driven Development는 무엇이며, 왜 하는가? (0) | 2022.04.14 |
앱 스토어 커넥트에서 DSYM 파일 다운로드가 안 되는 문제 해결하기 (0) | 2022.03.31 |
[Swift] Let's use SwiftLint (feat. swiftlint exclude pods not working) (0) | 2022.03.23 |