일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- notifychanged
- 상단 탭바
- 스크롤 탭
- Tuist
- Side Menu
- DevelopmentRegion
- List
- Swift Package Manager
- swift #swift keychain #keychain 사용법
- swift
- url 관찰
- development language
- pod install
- GeometryReader
- ViewBuilder
- SwiftUI
- scrolling tab
- detect url
- 기존 앱
- 개발자 면접
- DataBinding
- Android
- UIViewControllerTransitioningDelegate
- convert base64
- url 추적
- transformation.map
- UIPresentationController
- base64 변환
- ios
- oberve url
- Today
- Total
버그 잡이
[iOS] Keychain Service 개념부터 사용법까지 알아보기 본문
Keychain이란?
Keychain은 iOS에서 제공하는 보안 저장소로, 사용자의 민감한 정보를 암호화하여 안전하게 저장할 수 있게 해줍니다.
이는 UserDefault나 파일 시스템 저장과 같은 다른 저장 방식보다 안전한 옵션을 제공합니다.
Keychain Item
keychain item 단위로 write / read / delete / update 합니다.
keychain item는 attribute를 통해서 key-value 값 뿐만 아니라 부가적인 데이터 저장 및 추가 옵션 설정이 가능합니다.
SecItemAdd(), SecItemDelete() 와 같은 함수을 통해서 keychain item을 저장할 수 있습니다.
(뒤에 나오는 코드를 보시면 보다 이해가 쉬울겁니다.)
Attributes
kSecAttrAccount: 저장되는 Key 값
kSecValueData: 저장되는 Value 값
kSecClass: 저장되는 데이터의 유형
- kSecClassGenericPassword: 일반적인 비밀번호
- kSecClassInternetPassword: 인터넷 서비스용 비밀번호
kSecAttrService : 서비스 이름
- 같은 이름의 account라도 다른 service 값으로 저장할 수 있
kSecMatchLimit: 몇개의 데이터를 반환할 것이지.
- kSecMatchLimitOne: 하나
- kSecMatchLimitAll: 전부
kSecReturnData: 데이터를 반환할지 말지 여부
- true일때 데이터 반환, false 는 관련 속성만 반환
kSecAttrAccessible: 키체인 데이터에 언제 접근할 수 있게 할지
- kSecAttrAccessbileWhenUnlocked(default): 디바이스 잠금해제 후 사용가능
- kSecAttrAccessibleAfterFirstUnlock: 사용자가 한번 잠금해제 하면 그 이후 접근 가능
- 백그라운드에서 잠금 해제 하지 않고 키체인 아이템에 접근하려고 하면 접근이 안 될 수 있다.
kSecAttrSynchronizable: cloud 연동 여부
(추가 속성)
사용법
keychain 을 활용하여 어떻게 데이터를 저장하고 관리하는지 살펴보겠습니다.
(아래 코드는 제가 예전에 한 블로그에서 본 내용으로 재작성한건데 출처를 찾지 못하겠네요 ㅠ)
enum KeychainError: Error {
case itemNotFound
case duplicateItem
case invalidItemFormat
case unknown(OSStatus)
}
enum KeychainAccount: String {
case password = "tistory_password"
}
class KeychainManager {
static let service = Bundle.main.bundleIdentifier
// MARK: - Save
static func save(account: String, value: String, isForce: Bool = false) throws {
try save(account: account, value: value.data(using: .utf8)!, isForce: isForce)
}
static func save(account: String, value: Data, isForce: Bool = false) throws {
let query: [String: AnyObject] = [
kSecAttrService as String: service as AnyObject,
kSecAttrAccount as String: account as AnyObject,
kSecClass as String: kSecClassGenericPassword,
kSecValueData as String: value as AnyObject,
]
let status = SecItemAdd(query as CFDictionary, nil)
if status == errSecDuplicateItem {
if isForce {
try update(account: account, value: value)
return
} else {
throw KeychainError.duplicateItem
}
}
guard status == errSecSuccess else {
throw KeychainError.unknown(status)
}
}
// MARK: - Update
static func update(account: String, value: String) throws {
try update(account: account, value: value.data(using: .utf8)!)
}
static func update(account: String, value: Data) throws {
let query: [String: AnyObject] = [
kSecAttrService as String: service as AnyObject,
kSecAttrAccount as String: account as AnyObject,
kSecClass as String: kSecClassGenericPassword,
kSecValueData as String: value as AnyObject,
]
let attributes: [String: AnyObject] = [
kSecValueData as String: value as AnyObject
]
let status = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)
guard status != errSecDuplicateItem else {
throw KeychainError.duplicateItem
}
guard status == errSecSuccess else {
throw KeychainError.unknown(status)
}
}
// MARK: - Load
static func get(account: String) throws -> String {
try String(decoding: get(account: account), as: UTF8.self)
}
static func get(account: String) throws -> Data {
let query: [String: AnyObject] = [
kSecAttrService as String: service as AnyObject,
kSecAttrAccount as String: account as AnyObject,
kSecClass as String: kSecClassGenericPassword,
kSecMatchLimit as String: kSecMatchLimitOne,
kSecReturnData as String: kCFBooleanTrue,
]
var result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)
guard status != errSecItemNotFound else {
throw KeychainError.itemNotFound
}
guard status == errSecSuccess else {
throw KeychainError.unknown(status)
}
guard let password = result as? Data else {
throw KeychainError.invalidItemFormat
}
return password
}
// MARK: - Delete
static func delete(account: KeychainAccount) throws {
let query: [String: AnyObject] = [
kSecAttrService as String: service as AnyObject,
kSecAttrAccount as String: account.rawValue as AnyObject,
kSecClass as String: kSecClassGenericPassword
]
let status = SecItemDelete(query as CFDictionary)
guard status == errSecSuccess else {
throw KeychainError.unknown(status)
}
}
}
기타 특성
- 사용자가 직접 제거하지 않는 앱을 삭제하지 않아도 데이터는 남아있음
- 같은 개발자가 만든 앱인 경우, 앱들 간의 키체인 공유 가능
키체인을 가져오지 못 하는 경우
- 키체인이 사라지는 경우
- iCloud로 백업 / 복원 시
- kSecAttrAccessible 이 잠금상태 일때 접근 못 하도록 막은 경우
- 접근이 안 되기 때문에 SecItemCopyMatching()을 해도 error가 남
참고
*https://developer.apple.com/documentation/security/keychain_services/
*https://beankhan.tistory.com/202
'IOS' 카테고리의 다른 글
Swift Pakcage Manager로 모듈화 하기 (기타 삽질편) (0) | 2024.07.06 |
---|---|
Swift Pakcage Manager로 프로젝트 기능 모듈화 하기 (0) | 2024.06.22 |
웹뷰(WKWebview)에서 발생하는 에러 감지하기 (1) | 2023.12.09 |
Tuist 삽질기 (3) - Asset추가 그리고 ResourceSynthesizer (0) | 2023.05.05 |
Tuist 삽질기 (2) - 기존 프로젝트에 tuist 적용해보기 (0) | 2023.05.03 |