-
키체인을 활용해 jwt Token 안전하게 저장하기공부/iOS 2024. 1. 16. 16:27
Keychain(키체인) 간단 소개:
키체인은 iOS에서 사용할 수 있는 secure 저장소입니다.
흔히 사용하게되는 UserDefaults의 경우 plist 파일에 저장되기 때문에 비밀번호, 신용카드 번호 암호화 키 등의 민감한 데이터를 저장해선 안됩니다.
키체인에 접근하기 위해선 디바이스의 비밀번호를 통해 보호되기에 사용자만 키체인에 저장된 데이터에 접근할 수 있습니다.
Keychain에 사용되는 데이터 보호 방식:
키체인은 AES-256-GCM 암호화 알고리즘을 사용하여 데이터를 암호화하며, Secure Enclave와 같은 하드웨어 보안 모듈을 활용하여 데이터를 안전하게 저장합니다.
키체인은 파일 시스템에 저장되어 있는 SQLite 데이터베이스로 구현되어 있습니다. identifier나 group에 대한 접근 권한을 설정할 수 있으며, 단일 프로세스로 접근을 제한하는 대신 접근 그룹을 통해 앱 간에 키체인 항목이 공유할 수 있습니다.
(같은 개발자의 다른 앱에서 그룹이 같다면 데이터 공유 가능합니다.)
더 자세한 내용은 키체인 데이터 보호 참고
Keychain을 사용할 때 🥳
- 비밀번호
- 신용카드 번호
- 암호화 키
- 민감한 데이터
Keychain에 사용하지 말아야 할 때 😡
- 사용자의 이름
- 이메일 주소
Keychain의 장점:
- 저장되는 모든 데이터는 안전하게 보관된다.
- 스레드로부터 안전하다. (여러 스레드가 문제를 일으키지 않고 동시에 저장된 데이터에 액세스할 수 있음)
Keychain의 단점:
- 속도가 느리다. (많은 정보를 읽거나 저장하기에 느리다)
- 공간이 한정적이다. (공간이 제한적이라 큰 용량의 데이터를 저장을 권장하지 않음)
키체인 사용하기
아래 예시 코드에선 기본적으로 Password Attribute Keys에 필요한 Item을 사용하고 있습니다.
kSecAttrAccessGroup 등의 그룹 식별자 제한 권한을 따로 설정하지 않고 간단하게 앱에서 CRUD 하는 코드입니다.0. Security를 import 한다.
import Foundation import Security
1. Create
class KeychainHelper { static let serviceName = "Ourry" @discardableResult static func create(token: String, forAccount account: String) -> Bool { let keychainItem = [ kSecClass: kSecClassGenericPassword, kSecAttrAccount: account, kSecAttrService: serviceName, kSecValueData: token.data(using: .utf8) as Any ] as [String: Any] // Add the password to the keychain. let status = SecItemAdd(keychainItem as CFDictionary, nil) guard status == errSecSuccess else { print("Keychain create Error") return false } return true } }
2. Read
class KeychainHelper { static let serviceName = "Ourry" static func read(forAccount account: String) -> String? { let keychainItem = [ kSecClass: kSecClassGenericPassword, kSecAttrAccount: account, kSecAttrService: serviceName, kSecReturnData: true ] as [String: Any] var item: AnyObject? let status = SecItemCopyMatching(keychainItem as CFDictionary, &item) if status == errSecSuccess { return String(data: item as! Data, encoding: .utf8) } if status == errSecItemNotFound { print("The token was not found in keychain") return nil } else { print("Error getting token from keychain: \(status)") return nil } } }
3. Update
class KeychainHelper { static let serviceName = "Ourry" @discardableResult static func update(token: String, forAccount account: String) -> Bool{ let keychainItem = [ kSecClass: kSecClassGenericPassword ] as [String: Any] let attributes = [ kSecAttrService: serviceName, kSecAttrAccount: account, kSecValueData: token.data(using: .utf8) as Any ] let status = SecItemUpdate(keychainItem as CFDictionary, attributes as CFDictionary) guard status != errSecItemNotFound else { print("The token was not found in keychain") return false } guard status == errSecSuccess else { print("Keychain update Error") return false } print("The token in keychain is updated") return true } }
4. Delete
class KeychainHelper { static let serviceName = "Ourry" @discardableResult static func delete(forAccount account: String) -> Bool{ let keychainItem = [ kSecClass: kSecClassGenericPassword, kSecAttrService: serviceName, kSecAttrAccount: account ] as [String: Any] let status = SecItemDelete(keychainItem as CFDictionary) guard status != errSecItemNotFound else { print("The token was not found in keychain") return false } guard status == errSecSuccess else { print("Keychain delete Error") return false } print("The token in keychain is deleted") return true } }
사용 예시
KeychainHelper.create(token: "token", forAccount: "example") KeychainHelper.read(forAccount: "example") ?? "" KeychainHelper.update(token: "newToken", forAccount: "example") KeychainHelper.delete(forAccount: "example")
추가로 고려해볼만 한 것
키체인의 UserDefaults와 가장 큰 차이점은 토큰의 삭제 여부이다.
UserDefaults에 저장된 값은 앱이 삭제되었을 때 사라지지만 키체인에 있는 값은 디바이스에 저장되기에 앱을 재설치해도 남아있기 때문이다. 만약 토큰을 활용해 자동 로그인을 구현하고 있다면 어색한 상황이 발생할 수도 있다.
https://ios-development.tistory.com/m/262
[iOS - swift] 앱을 첫 번째 실행 시, keychain정보를 삭제하는 방법
* 기본 세팅: KeychainAccess 프레임워크, 프로젝트 구성 * 앱 첫 실행 시 정보를 삭제하는 원리: UserDefaults에 플래그 값을 넣어, 앱이 삭제되면 플래그값도 사라지는 것을 이용 UserDefaults에 첫 번째 실
ios-development.tistory.com
디바이스라는게 굉장히 개인적인 물건이라 일반적으론 쓰이지 않겠지만 필요하다면 추가해보면 좋을 것 같다.
참고 자료
https://uniqueimaginate.tistory.com/30
[iOS] 키체인에 대해서 (서버에서 받은 token을 저장해보자)
https://developer.apple.com/documentation/security/keychain_services Apple Developer Documentation developer.apple.com 어떤 이유로 키체인에 대해 알아야 했을까? 현재 만들고 있는 앱은 서버에서 제공해준 카카오 OAuth을 통
uniqueimaginate.tistory.com
https://medium.com/@omar.saibaa/local-storage-in-ios-keychain-668240e2670d
Local Storage in iOS: Keychain
Keychain is a secure storage in iOS. It is used to store sensitive data, such as passwords, credit card numbers, and encryption keys. The…
medium.com
https://developer.apple.com/documentation/security/ksecreturndata
kSecReturnData | Apple Developer Documentation
A key whose value is a Boolean that indicates whether or not to return item data.
developer.apple.com
https://developer.apple.com/documentation/security/errsecsuccess
errSecSuccess | Apple Developer Documentation
No error.
developer.apple.com
https://mini-min-dev.tistory.com/114
[iOS] 토큰 데이터 저장 공간을 Keychain으로 바꿔보자
본격 떡밥 회수 프로젝트(?) [iOS] Access Token과 Refresh Token, 그리고 Auto Login까지 이번 나다 NADA 어플 릴리즈를 준비하면서 가장 많이 공부한 부분이 "로그인"과 관련된 부분일 거다. 처음 아요끼리 담
mini-min-dev.tistory.com
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
'공부 > iOS' 카테고리의 다른 글
[iOS] status code가 200일 때 빈 응답이 들어오는 경우 (0) 2024.02.17 [iOS]텍스트필드가 등장할 때 타임아웃 뜨는 버그 해결법 (2) 2024.01.17 Xcode15에서 XCTest를 할 때 Testing... 에서 멈추는 현상 (0) 2024.01.02 UIKit에서 Delegate Pattern를 쓰는 이유 (0) 2023.12.21 [UIKit]키보드 위에 수정 제안을 비활성화하기 (0) 2023.12.13