사용자를 대신하여 작은 데이터들을 안전하게 저장하는 저장소
애플 문서를 보면, 사용자들은 종종 안전하게 보관해야 할 데이터들이 있다고 한다. 예를 들어, 대부분의 사람들은 수많은 온라인 계정을 관리한다. 로그인 정보 같은.
키 체인 서비스 API는 키체인이라는 암호화 된 데이터베이스에 사용자 데이터의 작은 비트를 저장하는 메커니즘을 앱에 제공한다. 암호를 안전하게 기억하게 도와준다는 뜻..!
키체인은 신용 카드 정보 또는 짧은 메모와 같이 여러 항목을 저장할 수도 있고 사용자가 필요하지만 알지 못하는 항목을 저장할 수도 있다고 한다. 예를 들어 인증서, 키 및 신뢰 서비스로 관리하는 암호화 키와 인증서를 사용하면 사용자가 보안 통신에 참여하고 다른 사용자 및 장치와 신뢰를 구축할 수 있다고 한다.
키체인 아이템을 사용하려면, 비밀로 저장해야 할 부분과 이 데이터를 접근하기 위해 공개되는 어트리뷰트들을 만들어서 아이템으로 패키징해야 한다.
해당 애플 문서의 그림을 보면, 숨겨야 할 데이터는 암호화하여 패키징 한 다음, 키체인 저장소에 보관된다. 물론, 어트리뷰트로 데이터를 가져올 수도 있는데, 이 때는 데이터를 해독하여 가져온다. 이 모든 암호화 과정들은 애플에서 직접! 키체인 API로 편하게 관리해줘서 그닥 손봐야할 것은 많지 않다!
그렇다면, 이제 키체인을 어떻게 쓸 수 있을까?! 알아보자.
일단 이해한 것을 토대로 로그인정보를 예로 들어, 간단하게 패스워드만 저장시키는 키체인을 구현해보았다!
func addItemsOnKeyChain() {
//간단하게 네임이 younsu이고, 패스워드가 ask123인 유저 구조체 Credentials를 생성하고 패스워드를 keychain 형식으로 저장
let credentials = Credentials(userName: "younsu", password: "ask12345")
let account = credentials.userName
let password = credentials.password.data(using: String.Encoding.utf8)!
let query: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
kSecAttrAccount: account,
kSecValueData: password]
let status = SecItemAdd(query as CFDictionary, nil)
if status == errSecSuccess {
print("add success")
} else if status == errSecDuplicateItem {
updateItemOnKeyChain(value: password, key: account)
} else {
print("add failed")
}
}
func updateItemOnKeyChain(value: Any, key: Any) {
let previousQuery: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
kSecAttrAccount: key]
let updateQuery: [CFString: Any] = [kSecValueData: value]
let status = SecItemUpdate(previousQuery as CFDictionary, updateQuery as CFDictionary)
if status == errSecSuccess {
print("update complete")
} else {
print("not finished update")
}
}
addItemsOnKeyChain 함수에서 상수로 이미 선언한 credentials 구조체를 넣어보는 것을 구현했다. 여러 입력값을 받고 싶다면, 함수 인자에 value랑 key를 받아와도 된다.
어쨌든! younsu라는 이름을 가진 사용자의 ask12345 패스워드를 안전하게 저장시키고 싶었다고 하자.
그래서 일단, item을 패키징하여 쿼리 배열을 짰다.
kSecClass는 키 항목 클래스가 담겨있다.
kSecClass
- kSecClassGenericPassword : 일반 암호 항목을 나타내는 값
- kSecClassInternetPassword : 인터넷 암호 항목을 나타내는 값
- kSecClassCertificate : 인증서 항목을 나타내는 값
- kSecClassKey : 암호화 키 항목
- kSecClassIdentity : ID 항목을 나타내는 값
나는 패스워드만 저장할 거라서, 그냥 일반적인 암호 항목을 나타내는 kSecClassGenericPassword로 정했다. 각 필요한 요소로 저장하면 될테지만, 나머지는 아직 안써봐서 필요할 때가 생기면 차차 더 깊게 공부할 수 있을거라 다짐하고...
해당 클래스를 정한 다음에, key에는 younsu, value에는 ask12345를 넣어 패스워드만 암호화되게 만들었다. 나중에 younsu라는 키값을 통해 value를 해독하여 불러올 예정이다.
이 배열을 SetItemAdd() 메소드를 이용해 키체인에 저장한다.
그렇게 되면 그냥 끝! status를 이용하여 잘 저장되었는지, 실패하였는지 까지 나타낼 수 있다.
여기서 만약에 아이템이 복제된 상태라면, 아~ value값이 이미 있는데, 업데이트를 시키고 싶은 거구나! 라고 생각하고 updateItemOnKeyChain() 메소드를 구현했다.
말그대로 값이 있는데, 똑같은 키값을 가지고 value만 업데이트 시킨다는 말이다.
만약에 younsu라는 사람의 비밀번호가 ssss123이었다고 하자. 이건 이미 키체인에 저장되어 있는 상태일 때, 비밀번호를 바꿔서 저장하고 싶다. 그래서 ask12345를 새롭게 저장할 때, SetItemAdd를 이용하여 넣으려 했는데, status에서 errSecDuplicateItem라고 뜨는 상황이다! 그럴 때 나는 ask12345를 다시 value로 해서 저장해야하기때문에 업데이트를 하는 것.
그렇다면 이전 쿼리의 younsu 배열과, update할 쿼리 배열을 생성해준다.
코드를 보면, SecItemUpdate() 메소드를 이용하여 이전 쿼리를 업데이트 쿼리로 바꿔준 것을 볼 수 있다! 그럼 이제 생성과 수정은 쉬운 구현은 끝!
func readItemsOnKeyChain() {
let account = "younsu"
let query: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
kSecAttrAccount: account,
kSecReturnAttributes: true,
kSecReturnData: true]
var item: CFTypeRef?
if SecItemCopyMatching(query as CFDictionary, &item) != errSecSuccess {
print("read failed")
return
}
guard let existingItem = item as? [String: Any] else { return }
guard let data = existingItem[kSecValueData as String] as? Data else { return }
guard let password = String(data: data, encoding: .utf8) else { return }
print(password)
}
어렵지 않다.. 해당 younsu를 키값으로 가지고 있는 value를 가져오면 된다.
ReturnData: true해주면 된다.! 나는 값을 받아야 하기때문에!
애플 문서 보고 하긴 했지만, 여기서 SecItemCopyMatching을 이용하여 쿼리에 맞는 아이템을 직접 찾아주는 메소드를 이용한다.
해당 kSecValueData인 value값을 가져와서 스트링으로 변환했다. 그럼 패스워드가 뙇! 나온다.
너무 신기했다
func deleteItemOnKeyChain(key: String) {
let deleteQuery: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
kSecAttrAccount: key]
let status = SecItemDelete(deleteQuery as CFDictionary)
if status == errSecSuccess {
print("remove key-data complete")
} else {
print("remove key-data failed")
}
}
삭제는 더 쉽다. 해당 키워드만 있으면 알아서 곧바로 삭제해준다.
SecItemDelete를 쓰면 된다.
사실 키체인 오픈 소스는 많으니, 다 참고해서 라이브러리로 사용해도 될 것 같다.
엄청 쉬운 예제이지만, 다른 class type들 쓸 때는 어떻게 쓰일지도 알아봐야 한다..
그래도 하나하나 이게 왜 그런지 알아갈 때마다 늘 짜릿 최고,,!
해당 전문은 깃헙에 똑같이 올려놓았으니 혹시 보시게 되는 분은 참고만 해주세요,, (__)