-
[iOS] KeyChain이란~? Swift코드를 통해 살펴보기STUDYING/iOS 2021. 1. 6. 19:18728x90
iOS 앱 개발 프로젝트를 할 때 민감한 정보를 어디에 저장해야할지 고민이됐다.
UserDefaults보다 보안이 뛰어난 키체인을 사용해보자!UserDefaults
UserDefaults
에도 데이터를 쉽게 저장할 수 있지만, 단순히 .info 파일에 키-값 쌍을 텍스트 형태로 저장하기 때문에 OS를 탈옥하면 내용물을 볼 수 있다는 문제가 있다.보안이 필요한 데이터에는 적합하지 않다.
KeyChain
이를 방지하기 위해 암호나 API Token과 같은 민감한 정보는
KeyChain
에 저장하는 것이 좋다.KeyChain이란~~?
Apple이 제공하는 보안 프레임워크
Keychain은 디바이스 안에 암호화된 데이터 저장 공간을 의미한다. 사용자는 암호화된 공간에 데이터를 안전하게 보관할 수 있다.
무엇을 저장할까?
- 로그인 및 암호 (해시)
- 결제 데이터
- 알고리즘 암호화를위한 키
등등... 단순한 구조의 비밀을 유지하고 싶은 것들을 저장
KeyChain 특징
- 사용자가 직접 제거하지 않는 이상, 앱을 제거하고 설치해도 데이터는 남아있다.
- 디바이스를 Lock하면 KeyChain도 잠기고, 디바이스를 UnLock하면 KeyChain역시 풀린다.
KeyChain
이 잠긴 상태에서는 Item들에 접근할수도, 복호화 할 수도 없다.KeyChain
이 풀린 상태에서도 해당 Item을 생성하고 저장한 어플리케이션에서만 접근이 가능하다.
- 같은 개발자가 개발한 여러 앱에서 키체인 정보를 공유할 수 있다.
KeyChain Service API
KeyChain service API로 민감한 데이터를 암호화 - 복호화 하며 재사용 하는 행위를 보다 쉽고 안전하게 사용할 수 있게한다.
KeyChain Items
키체인에 저장된 Data 단위. 키체인은 하나 이상의 Keychain item을 가질 수 있다.
KeyChain Item Class
저장할 Data의 종류.
- kSecClassInternetPassword
- kSecClassCertificate
- kSecClassGenericPassword
- kSecClassIdentity
- kSecClassKey
대표적으로 인터넷용 아이디/패스워드르 저장할 때 사용하는 kSecClassInernetPassword,
인증서를 저장할 때 사용하는 kSecClassCertificate,
일반 비밀번호를 저장할 때 사용하는 kSecClassGenericPassword등이 있다.
Attributes
keychain item을 설명하는 특성. item class에 따라 설정할 수 있는 attributes가 달라진다.
애플 문서를 보면 아래와 같이 각 아이템 클래스별로 다양한 어트리뷰트가 존재한다.
구현해보기
Token구조체 배열을 KeyChain으로 저장하자!
일반 비밀번호를 저장할 때 사용하는 kSecClassGenericPassword 라는 Keychain Item Class 를 사용하여 TOTP토큰배열을 저장하는 과정에 대해 살펴보도록 하겠다.
KeyChain Query 작성하기
---------------------------------------------------------------- kSecClass: 키체인 아이템 클래스 타입, kSecAttrService: 서비스 아이디, // 앱 번들 아이디 kSecAttrAccount: 저장할 아이템의 계정 이름, kSecAttrGeneric: 저장할 아이템의 데이터 ---------------------------------------------------------------- private let account = "TokenService" private let service = Bundle.main.bundleIdentifier let query: [CFString: Any] = [kSecClass: kSecClassGenericPassword, kSecAttrService: service, kSecAttrAccount: account, kSecAttrGeneric: data]
API 메소드
Create
SecItemAdd(_:_:)
- 키체인에 하나 이상의 항목을 추가할 때 사용
func createTokens(_ token: [Token]) -> Bool { guard let data = try? JSONEncoder().encode(token), let service = self.service else { return false } let query: [CFString: Any] = [kSecClass: kSecClassGenericPassword, kSecAttrService: service, kSecAttrAccount: account, kSecAttrGeneric: data] return SecItemAdd(query as CFDictionary, nil) == errSecSuccess }
Read
SecItemCopyMatching(_:_:)
- 검색 쿼리와 일치하는 키체인 항목을 하나 이상 반환하는 기능. 또한, 특정 키 체인 항목의 속성을 복사할 수 있다.
func readTokens() -> [Token]? { guard let service = self.service else { return nil } let query: [CFString: Any] = [kSecClass: kSecClassGenericPassword, kSecAttrService: service, kSecAttrAccount: account, kSecMatchLimit: kSecMatchLimitOne, 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[kSecAttrGeneric as String] as? Data, let tokens = try? JSONDecoder().decode([TOTPToken].self, from: data) else { return nil } return tokens }
Update
SecItemUpdate(_:_:)
- 검색 쿼리와 일치하는 항목을 수정할 수 있는 기능
func updateTokens(_ token: [Token]) -> Bool { guard let query = self.query, let data = try? JSONEncoder().encode(token) else { return false } let attributes: [CFString: Any] = [kSecAttrAccount: account, kSecAttrGeneric: data] return SecItemUpdate(query as CFDictionary, attributes as CFDictionary) == errSecSuccess }
Delete
SecItemDelete(_:)
- 검색 쿼리와 일치하는 항목을 제거하는 기능
func deleteTokens() -> Bool { guard let query = self.query else { return false } return SecItemDelete(query as CFDictionary) == errSecSuccess }
참고 블로그
'STUDYING > iOS' 카테고리의 다른 글
[iOS] Could not insert new outlet connection 오류 해결하기 (0) 2021.01.07 [iOS] Codable에 관하여 (0) 2018.11.28 [iOS] App Transport Security 해제하여 HTTP프로토콜 연결하기 (0) 2018.11.24 [iOS] Unwind에 관하여 (0) 2018.10.10