금요일에 쓴 글이 날아가버렸다.. 그래서 다시 쓰는 중인데.. 너무 슬프다.
월요일인 것도 화나는데
먼저 애플 공식 문서를 살펴보자.
어... 도와줘요 파파고
보잘 것 없는 영어 실력으로 정리해보자면,
키체인은 비밀번호나 신용카드 정보, 인증서 등과 같은 민감한 데이터를 저장하는 데이터베이스이다.
데이터는 암호화되어 저장되며, 암호화한 데이터에 쉽게 접근할 수 있도록 도와준다.
키체인은 하나의 암호화된 컨테이너이며 Keychain services API
는 민감한 데이터를 암호화하고 복호화하여 재사용하는 행위를 쉽고 안전하게 할 수 있도록 한다.
기본적으로 디바이스를 잠그면 키체인도 잠기고, 디바이스를 열면 키체인도 풀린다. 이렇게 키체인이 잠긴 상태에서는 데이터에 접근하는 것이 불가능하다.
키체인에는 민감하고 중요한 데이터를 저장한다고 했는데, 어떻게 저장할까?
저장할 데이터는 Keychain Item으로 패키징하여 저장된다.
데이터를 암호화하여 item으로 패키징하는데, 이때 Attribute도 함께 저장한다.
이 attribute는 데이터에 접근하거나 검색하는 것을 가능하게 한다.
이때 attribute는 직접적으로 접근하게 하는 것이 아님. 해당 데이터의 속성이나 특징을 보여주는 것이지, 접근하는 것은 접근 제어에 해당하는 프로세스만 가능함.
즉, 접근 가능성을 보여주는 것임.
앱은 비밀번호화 같은 민감한 사용자 데이터에 접근해야 하는 경우가 많은데, 데이터를 암호화하지 않고 저장하면 보안 위험이 따른다.
키체인은 암호화된 저장소에 쉽게 접근할 수 있도록 하여 이러한 문제를 해결한다. 다음과 같은 프로세스를 고려해보자.
먼저 item을 조회했을 때, 암호화된 데이터가 없다면 사용자가 입력한 정보가 유효한지 인증한 후에 키체인 item으로 저장한다. 만약 암호화된 데이터가 있다면, 그 정보가 유효한지 확인한다.
만약 유효할 경우에는 키체인 로그인 과정이 끝나게 된다. 만약 유효하지 않다면 사용자가 정보를 다시 입력하도록 하여 정보가 유효한지 검사한 후에 키체인 아이템을 업데이트 한다.
기본적으로 앱은 자기 자신의 키체인에만 접근할 수 있다.
또한 키체인에 저장된 정보는, 앱을 삭제하더라도 사라지지 않는다.
엥 어떻게요?
바로 여기서, 전에 공부했던 SandBox 개념이 등장한다.
모르는 사람은 [iOS] SandBox를 알아보자! 참고하기.
SandBox에 대해 짧게 정리해보자면,
앱이 외부로부터 공격을 받았을 때, 그 피해를 최소화하기 위해 고안된 모델이다. 애플의 모든 앱은 각각 하나의 SandBox로 감싸져 있고, 각 SandBox 안에 앱의 bundle나 데이터가 저장된다.
( UserDefaults도 이 SandBox 안에 저장된단 말씀!! )
위에서 키체인은 자기 자신의 키체인에만 접근할 수 있다고 설명했는데, 키체인 그룹을 사용하면 서로 다른 앱에서도 데이터를 공유할 수 있다.
키체인을 구성하는 요소는 총 3개로 나눌 수 있다.
키체인에 저장되는 데이터로, 키체인은 여러 개의 키체인 item을 가질 수 있다.
키체인 item에 저장되는 데이터의 종류를 지정할 수 있다.
item 클래스에 대한 속성을 저장할 수 있다.
SecItemAdd()
함수를 사용해 키체인 아이템을 생성한다.
public func add(_ value: String, forKey: String) {
let account = forKey
let password = value.data(using: String.Encoding.utf8)!
let query: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
kSecAttrAccount: account,
kSecValueData: password]
let status = SecItemAdd(query as CFDictionary, nil)
switch status {
// 성공
case errSecSuccess:
print"\(forKey) add successful")
// 키가 중복값이 있음
case errSecDuplicateItem:
update(password, forKey: account)
// 실패
default:
print("\(forKey) add failed")
}
}
SecItemCopyMatching()
함수를 사용해 키체인 아이템을 검색한다.
public func read(forKey: String) -> String {
let account = forKey
let query: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
kSecAttrAccount: account,
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["v_Data"] as? Data,
let password = String(data: data, encoding: .utf8) else { return nil }
return password
}
SecItemUpdate()
함수를 사용해 키체인 아이템을 수정한다.
private func update(_ value: Any, forKey: Any) {
let previousQuery: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
kSecAttrAccount: forKey]
let updateQuery: [CFString: Any] = [kSecValueData: value]
let status = SecItemUpdate(previousQuery as CFDictionary, updateQuery as CFDictionary)
switch status {
// 성공
case errSecSuccess:
print("\(forKey) update successful")
// 실패
default:
print("\(forKey) update failed")
}
}
SecItemDelete()
함수를 사용해 키체인 아이템을 삭제한다.
public func delete(forKey: String) {
let deleteQuery: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
kSecAttrAccount: forKey]
let status = SecItemDelete(deleteQuery as CFDictionary)
switch status {
// 성공
case errSecSuccess:
print("\(forKey) remove successful")
// 실패
default:
print("\(forKey) remove failed")
}
}
두번째 작성하다보니,,, 내가 얼마나 이해했는지 확인할 수 있어서 좋았던 듯?
응.. 그것만..
앞으로 저장 잘 해야징! (찡긋)