Archive/꼼꼼한 재은씨 시리즈

[iOS14] 키 체인(key Chain)

lgvv 2021. 4. 23. 22:08

키 체인이란 애플 계열의 운영체제에서 동작하는 다양한 응용 프로그램에서 비밀번호나 계정정보를 저장하는 암호회된 저장소

아이클라우드, macOS, watchOS, 앱 아이디, 웹에 저장된 아이디, 와이파이 패스워드 등 광범위하게 이용된다.

 

키 체인의 특성

1. 기본적으로 앱은 자기 자신의 키 체인에만 접근 가능

2. iOS에서 키 체인의 위치는 *샌드박스 외부이므로, 앱을 삭제해도 키 체인에 저장되는 정보는 삭제되지 않음

3. 앱의 프로비저닝 파일을 이용해서 앱 간의 사용 경로를 구분하기 때문에, 동일한 애빙라도 프로비저닝 파일을 변경해서 기존 정보를 더 이상 조회할 수 없다.

4. 키 체인 그룹을 사용하여, 서로 다른 앱에서도 저장된 데이터를 공유 가능

5. 비번 또는 개인키처럼 보호가 필요한 항복은 암호화되어 키 체인으로 보호되며, 인증서처럼 보호가 필요하지 않은 항목은 암호화되지 않은 채로 저장된다.

6. 키 체인은 잠글 수 있어서 일단 잠기면 해제하기 전까지 저장된 데이터에 접근할 수 없지만 iOS에서는 기기의 잠금이 헤제되는 순간 키 체인의 잠그도 함께 헤제된다.

 

*샌드박스 : 외부에서 받은 파일을 바로 실행하지 않고 보호된 영역에서 실행시켜 봄으로써 잘못된 파일 프로그램이 내부 시스템 전체에 악영향을 주는 것을 미연에 방지하는 기술. 이를 확장하여 iOS 앱 간에 데이터 공유가 불가능하도록 격리된 각자의 샌드박스 공간을 제공하는데, iOS 시스템 자체에서는 파일을 함부로 쓸 수 없지만 샌드박스 내에서는 파일 쓰기가 허용된다. 쉽게 말해서 '망할 거면 너만 망해라' 우리가 앞에서 다룬 프로퍼티 리스트나 커스텀 파일 등은 모두 샌드박스 내에 저장된다.

 

키 체인의 구조

실질적으로 키 체인은 단순한 데베임. macOS에서 사용자나 앱은 원하는 만큼 키 체인을 만들 수 있지만, iOS에서는 모든 앱에서 사용할 수 있는 키 체인 하나만 제공된다. 아이클라우드의 경우는 조금 특벼랳서, 사용자가 디바이스에서 아이클라우드 계정에 로그인하면 시스템은 논리적으로 구분되는 아이클라우드용 키체인을 제공한다.

 

키체인을 구성하는 요소 

1. 키 체인 아이템 : 키 체인에 저장되는 데이터로, 키 체인은 여러 개의 키 체인 아이템을 가질 수 있다.

2. 아이템 클래스 : 저장할 데이터의 종류. id/pw, 인증서, 인터넷 비번 및 일반 비번 등을 선택할 수 있으며 임의로 아이템 클래스 추가는 불가하다.

대표적인 아이템 클래스로는 인터넷용 아이디/패스워드 저장할 때 사용하는 kSecClassInternetPassword, 인증서를 저장할 때 사용하는 kSecClassGenericPassword 등이 있다.

3. 어트리뷰트 : 아이템 클래스에 대한 속성, 아이템 클래스에 따라 설정할 수 있는 어트리뷰트의 종류가 달라진다.

 

키 체인 아이템을 정의할 때는 저장할 데이터에 맞는 아이템 클래스를 선택해야 한다.

각 아이템 클래스는 저장값 특성ㅇ ㅔ따라 서로 다른 어트리뷰트를 제공하기 때문이다.

가령 사파리 브라우저를 이용하는 웹사이트에 로그인하려고 시도하면 여러 아이템 클래스 중 하나를 이용해서 정보를 제공해줌

사용 가능한 어트리뷰트

일부 어트리뷰트는 단순 저장이지만 어떤 어트리뷰트는 매우 중요한 역할을 하기도 한다.

kSecAttrAccessControl은 값을 공유할 수 있는 그룹을 설정하는 데 사용된다. 이를 이를 이용하면 어플리케이션 간에 키 체인 데이터를 공유할 수 있다. 네이버 앱에서 로그인한 다음에 네이버 뮤직앱을 열면 로그인되어 있는 것을 볼 수 있는데 이는 키 체인을 이용해 로그인 정보를 공유하기 때문이다.

또한 한 사이트 내에서도 아이디/패스워드를 사용하는 곳이 여러 군데일 경우 각 경로별로 따로 값을 저장하고 불러올 수 있다.

이는 kSecAttrServer, kSecAttrProtocol, kSecAttrPort, kSecAttrPath, kSecAttrAccount이를 이용하여 서버도메인,HTTP/HTTPS프로토콜, 접속 포트, 경로, 사용자 계정 등을 저장한 다음 이를 현재의 웹사이트 정보와 비교하기 때문이다.

 

이외 전체 어트리뷰트는 애플 개잘자 사이트의 API 레퍼런스 확인해보기

 

kSecAttrService : 앱을 식별할 수 있는 서비스 아이디

kSecAttrAccount : 저장할 비밀번호에 대한 사용자 계정

 

키 체인 작업에 사용되는 코드

저장 : SecItemAdd

읽기 : SecItemCopyMatching

수정 : SecItemUpdate

삭제 : SecItemDelete

이들은 모두 C스타일로 코딩되어 있어서 키 체인 쿼리라고 불리는 CFDictionary 타입의 데이터 집합을 인자값으로 받아 사용한다.

데베로 치자면 테이블명과 키 값, 저장할 내용 등이 모두 어우러진 SQL문에 해당한다.

우리는 저장할 데이터, 아이템 클래스, 서비스명 등을 키 체인 쿼리에 정의한 다음, 목적에 맞는 함수를 호출하여 원하는 CRUD작업을 처리한다.

 

키체인 비밀번호 저장하는 코드

import Security // 프레임워크 삽입

// 키체인 쿼리 정의
let keyChainQuery : NSDictionary = [
	kSecClass : <아이템 클래스>,
	kSecAttrService : <서비스 아이디>,
	kSecAttrAccount : <사용자 계정>,
    kSecValueData : <저장할 값>
]

SecItemDelete(keyChainQuery) // 기존에 저장된 값 삭제

secItemAdd(keyChainQuery,nil) // 새로운 값 추가

키 체인 쿼리에 들어가는 키들은 모두 어트리뷰트들이며, 아이템 클래스 역시 키 체인의 일부로 정의된다. 

Security 프레임워크에 서 제공하는 어트리뷰트 키는 모두 CFString 타입으로 정의되어 있기 때문에, NSDictionary 객체에 저장하기 위해서 어트리뷰트 키들을 NSString 또는 String 타입으로 캐스팅 해야함.

 

kSecClass 는 아이템 클래스를 지정하는 항목이다. 쉽게 말해 '어떤 타입의 데이터를 저장할거냐'하는 거다.

지정할 수 있는 아이템 클래스 타입

 1. kSecClassGenericPassword

 2. kSecClassInternetPassword

 3. kSecClassCertificate 

 4. kSecClassKey

 5. kSecClassIdentity

 

이중에서 가장 많이 사용하는건  1,2,3번으로 각각 일반 비밀번호, 인터넷 비밀번호, 인증서 저장할 때 사용

 

kSecAttrService는 지정할 값에 대한 서비스 아이디를 지정하는 어트리뷰트

앱 번들 아이디를 사용하는 경우가 일반적

 

kSecAttrAccount는 저장할 값에 대한 사용자 계정을 지정하는 어트리뷰트

앱 중에서는 멀티 계정을 지원하는 것들이 있는데, 앱은 여러 개의 계정을 등록해놓고 필요할 경우 전환해 가면서 사용할 수 있도록 지원.

A와B 계정이 있을 경우 서비스 아이디는 동일하게 사용하되 사용자 계정만 A,B로 다르게 저장하는 식.

 

kSecValueData는 실제로 저장할 값. Data 타입의 값을 입력받기 때문에 실제로 값을 저장할 때에는 다음과 같이 Data 타입으로 인코딩 하는 과정이 포함된다.

let str = "저장할 비번"

let value = str.date(using: .utf8, allowLossyConversion : false)

 

키 체인은 중복된 값을 새로운 값이 자동으로 덮어쓰는 구조가 아니므로 키가 중복되면 새로운 값을 저장할 수 없음

따라서 항상 기존에 값을 삭제해주어야 한다.

 

새로운 값을 추가할 때는 단순히 함수 호출만 서술하고 있으나, 이 함수는 처리 결과를 OSSatus 타입으로 반환하기 때문에 필요에 따라서는 다음과 같이 저장 결과를 확인할 수 있는 구문으로 바꾸어 쓸 수 있다.

let status : OSSatus = SecItemAdd(keyCahinQuery, nil)

assert(status == noErr, "토큰 값 저장에 실패")

NSLog("status=\(status)")

아이템 클래스별 어트리뷰트

 

저장된 값을 읽어오는 코드를 살펴보자

import Security // 프레임워크 삽입

// 키체인 쿼리 정의
let keyChainQuery : NSDictionary = [
	kSecClass : <아이템 클래스>,
	kSecAttrService : <서비스 아이디>,
	kSecAttrAccount : <사용자 계정>,
    kSecReturnData : kCFBooleanTrye,
    kSecMatchLimit : kSecMatchLimitOne
]

// 저장된 값 읽기
var dataTypeRef : AnyObject?
secItemCopyMatching(keyChainQuery, &dataTypeRef)

// 읽어온 값 변환
let retrievedData = dataTypeRef as! Data
let value = String(data: retrieveData, encoding: String.Encoding.utf8)

 

읽어올 때도 가장 먼저 해야하는 것 키 체인 정의임

LimitOne은 일치하는 하나만 읽어오도록 처리하는 법. 물론 모조리 다 읽어오게 하는 것도 존재함

 

저장된 값 읽기에서 secItemCopyMatching를 사용해야하는데, 두번째 매개변수는 inout 타입으로 정의

함수 내부에서 수정된 인자값을 함수 외부에서도 참조할 수 있다는 것임.

(c의 call by reference와 개념 비슷)

 

읽어온 값을 데이터 타입을 변환하고, 스트링 타입으로 다시 변환하면 끝

 

 

키 체인 래핑하기

 

키 체인을 다루기 위해서는 다룰 코드가 좀 많다 ㅠㅠ

그래서 실무에서는 주로 키 체인을 쉽게 다룰 수 있게 래핑해서 사용하는 것이 일반적이라고 한다.