programming/Swift

[Swift/iOS] 토큰 데이터를 KeyChain에 안전하게 저장하기

마들브라더 2023. 5. 6. 23:04

앱에서 로그인 후, 서버에서 반환되는 사용자 인증토큰(accessToken과 refreshToken)을 저장하려고 합니다.
CoreData를 사용하기에는 너무 단순한 형태이고,

UserDefaults에는 간단한 설정값을 저장하는데 주로 사용하기 때문에,

인증토큰처럼 보안이 중요한 경우에는 다른 방법을 찾아보는게 좋다는 생각이 들었습니다.

 

KeyChain은 암호화된 저장소로 토큰 데이터를 저장하기에 알맞다고 판단한 후 앱에서 사용해보았습니다.

 

UserDefaults가 키-값 쌍으로 데이터를 저장하고 불러오는 것처럼, 키체인도 사용할 수 있습니다.

다만 KeyChain은 데이터와 속성을 묶고 추가적인 보안 속성을 함께 사용할 수 있습니다.

 

키체인 서비스 KeyChain Service

사용자를 대신해서 안전하게 작은 데이터 조각을 저장할 수 있습니다.

Device 안에 암호화된 데이터베이스라고 생각할 수 있습니다.

 

KeyChain Service API는 다양한 상황에서 여러 형태의 데이터를 암호화된 데이터베이스에 저장할 수 있는 메커니즘을 제공합니다.

비밀번호에 한정되지 않고, 신용카드 정보나 짧은 노트 등 사용자가 인식하지 못하지만 필요한 정보도 저장할 수 있습니다.

인증서, Key 등을 사용해서 관리하는 암호화 키 등을 저장할 때도 KeyChain을 사용합니다.

https://developer.apple.com/documentation/security/keychain_services

 

KeyChain API 구성요소

KeyChain Item

비밀번호나 암호키 같은 정보를 저장할 때, KeyChain Item으로 패키징합니다.

데이터와 item의 접근을 관리하고, 검색할 수 있도록 공개 Attributes함께 패키징합니다.

KeyChain 서비스는 데이터속성의 저장을 처리하고, 암호화도 처리합니다.

이후, 권한이 있는 프로세스는 KeyChain 서비스를 사용해서 아이템을 찾고 데이터를 복호화하는 데 사용합니다.

https://developer.apple.com/documentation/security/keychain_services/keychain_items


쉽게 말해서, 저장하고자 하는 데이터에 데이터 속성을 더한 것이 KeyChain Item입니다.

 

KeyChains

전체 키체인을 생성하고 관리하는 것입니다.

 

iOS에서는 앱이 하나의 KeyChain에만 접근할 수 있습니다.

iCloud KeyChain을 포함한 하나의 KeyChain이 존재하고,

앱은 앱의 KeyChain 항목 또는 해당 앱이 속한 그룹과 공유된 항목에만 접근할 수 있습니다.

KeyChain Container 자체를 관리할 수는 없습니다.

 

아이템 클래스의 키와 값 Item Class Keys and Values

키체인 아이템의 클래스는 여러 종류가 있습니다.

각 클래스는 적용되는 속성을 규정하고, 시스템이 암호화 여부를 결정합니다.

예를 들면 비밀번호는 암호화가 필요하고, 인증서는 암호화를 하지 않습니다.

 

Item Class Values

kSecClassGenericPassword

일반 비밀번호

kSecClassInternetPassword

인터넷 비밀번호

kSecClassCertificate

인증서

kSecClassKey

암호화 키

kSecClassIdentity

ID 항목

 

아이템 클래스마다 속성은 다릅니다.


저는 토큰을 저장할 것이기 때문에 kSecClassKey(암호화 키)를 한번 이용해보려고 합니다.

 

kSecClassKey의 속성페이지를 보면 굉장히 다양한 것을 확인할 수 있습니다.

https://developer.apple.com/documentation/security/ksecclasskey

 

몇 가지만 살펴보겠습니다.

- kSecAttrLabel : 아이템의 label을 나타내는 문자열 값

- kSecAttrApplicationLabel : 아이템의 애플리케이션 label을 나타내는 값

위 속성을 사용해서 토큰을 저장해보겠습니다.

 

키체인 저장하기

값 저장을 위해서 데이터를 변수에 임의로 선언했습니다.

let token = "abcd1234"
let label = "accessToken"
let appName = "AppName"

 

앞서, 아이템은 데이터와 속성을 합쳐서 패키징한다고 했습니다.

데이터와 속성을 키와 값 형태인 딕셔너리 형태로 만들어주었습니다.

let saveQuery: NSDictionary = [kSecClass: kSecClassKey,
                           kSecAttrLabel: label,
                kSecAttrApplicationLabel: appName,
                           kSecValueData: token.data(using: .utf8, allowLossyConversion: false)!]

첫 번째 키-값은 아이템 클래스의 키와 값을 적어주고,

두세 번째의 키-값은 아이템 클래스의 속성과 값을 적어주고,

마지막으로 데이터를 Data 형태로 저장했습니다.

 

두 번째 그림에서 본 것처럼,

키체인 서비스가 이 쿼리(속성값과 데이터)를 받아서 아이템으로 저장을 해주는 것입니다.

 

저장은 메서드 한 줄로 할 수 있습니다.

let status = SecItemAdd(saveQuery, nil)

SecItemAdd 메서드가 OSStatus 타입을 리턴하기 때문에, 이를 변수에 저장해서 결과를 확인할 수 있습니다.

if status == errSecSuccess {
    print("저장 성공")
} else {
    print(status)
    print("저장 실패")
}

저장에 성공했습니다.

 

키체인 불러오기

저장에 성공했으니 불러오는 코드를 살펴보겠습니다.

let searchQuery: NSDictionary = [kSecClass: kSecClassKey,
                             kSecAttrLabel: label,
                      kSecReturnAttributes: true,
                            kSecReturnData: true]

첫 번째 속성값은 역시 아이템 클래스의 키-값입니다.

이번에는 속성값을 하나만 넣어보았습니다.

속성값을 하나만 넣어도 결과가 돌아올까요?

 

var searchedResult: CFTypeRef?

검색 결과를 불러와서 저장할 변수를 먼저 만들어주었습니다.

 

검색 메서드는 역시나 간단한데,

let searchStatus = SecItemCopyMatching(searchQuery, &searchedResult)

저장 메서드와 비슷하게 결과를 리턴해주고, 첫번째 인자로는 쿼리를 전달해주면 됩니다.

두 번째에는 inout파라미터가 있는데, 결과를 저장할 변수를 인자로 넘겨주면 됩니다.

if searchStatus == errSecSuccess {
    guard let checkedItem = searchedResult,
          let token = checkedItem[kSecValueData] as? Data else { return }
    print("token, ", String(data: token, encoding: String.Encoding.utf8))
} else {
    print("불러오기 실패, status = ", searchStatus)
}

성공적으로 불러왔다면 token을 확인해볼 수 있습니다.

 

 

------- 

+ 추가

kSecClassKey를 사용할때 -25299(errSecDuplicateItem)와 -25300(errSecItemNotFound)에러가 반복해서 나타났습니다.

https://developer.apple.com/forums/thread/77074

위 문제와 비슷한 상황입니다.

kSecClassGenericPassword를 사용해도 비슷한 상황입니다.

 

저장하고 싶어! 안돼 -25299

그럼 지울게! 안돼 -25300

뭔데...?

저장할래! 안돼 -25300???

 

아직 해결하지 못했습니다.

-------

+ 추가

키체인 아이템 클래스마다 사용법이 조금 다른 것 같은데,

kSecClassGenericPassword을 사용하고, 속성을 간단히 최소화 해서 사용하니 오류 없이 사용할 수 있었습니다.

속성값을 어떻게 사용하느냐에 따라 검색, 저장 등에서 차이가 발생하는 것 같습니다.

현재는 kSecAttrAccount, kSecAttrService 속성만을 사용해서 간단히 사용하고 있습니다.

 

참조

https://developer.apple.com/documentation/security/keychain_services

https://applecider2020.tistory.com/48

https://pgnt.tistory.com/113

https://ios-development.tistory.com/66