SlideShare a Scribd company logo
1 of 12
KeychainのBackupと
同期 (iOSの場合)
2017.5.15
Bitz Co., Ltd. 村上幸雄
• MOSA
• 1995年から続く団体。
• BUKURO.swiftは、毎月中旬の金曜日に池袋FORESTで開催。
https://mosa.connpass.com/
• Cocoa勉強会
• 2003年から続く勉強会。
• Cocoa勉強会 池袋は、毎月上旬の月曜日に池袋FORESTで開会。
https://cocoa-study.connpass.com/
• Cocoa勉強会 松戸は、毎月下旬の土曜日に松戸degilaboで開催。
https://connpass.com/series/499/
無料、コワーキングスペース代は各自で
支払うというスタイルです。
時間があったら、お茶する感じで気楽に
来ていてだければ嬉しいです。
github.com/murakami/workbook/tree/master/ios/Id
識別子
• 個人や機器が特定できる識別子は、セキュリティ上の問題から利用
できない。
• 各アプリでUUIDを生成して、それを識別子とする。
• UUIDをファイルに保存すると、バックアップ対象にできるが、
盗み見ることができる。
• keychainに保存すれば盗み見をガードできるが、バックアップ&
リカバリできない。
• iCloudでkeychainの同期ができるようになったが、これを利用す
れば!
Keychain
• 暗号化して保存。
• 暗号化パスワードはバックアップ対象外。
• アプリケーションは、自分が作成したものしか
アクセスできない。
Keychain Servicesとは
• 情報はアイテムと呼ばれる塊である変われる。
• アイテムに格納されるのはパスワードや暗号鍵、
証明書など。
• 情報を格納するデータは、パスワードによって保
護されている。
• 登録や変更、削除、検索するためのAPIが用意さ
れている。
API
• func SecItemAdd(_ attributes: CFDictionary,
_ result: UnsafeMutablePointer<CFTypeRef?>?)
-> OSStatus
• func SecItemCopyMatching(_ query: CFDictionary,
_ result: UnsafeMutablePointer<CFTypeRef?>?)
-> OSStatus
• func SecItemUpdate(_ query: CFDictionary,
_ attributesToUpdate: CFDictionary)
-> OSStatus
• func SecItemDelete(_ query: CFDictionary)
-> OSStatus
検索 + 取得
var uuidString = ""
var query = [String : AnyObject]()
query[kSecClass as String] = kSecClassGenericPassword /* パスワードクラス */
query[kSecAttrService as String] = self.service as AnyObject? /* サービス名 */
query[kSecAttrAccount as String] = Identifier.IDENTIFIER_KEY as AnyObject? /* アカウント */
query[kSecMatchLimit as String] = kSecMatchLimitOne /* 取得する結果の最大件数を1検討する */
query[kSecReturnAttributes as String] = kCFBooleanTrue /* 結果を辞書で受け取る(属性値) */
query[kSecAttrSynchronizable as String] = kCFBooleanTrue /* iCloud同期 */
var result = noErr
/* 検索 */
var queryResult: AnyObject?
let status = withUnsafeMutablePointer(to: &queryResult) {
result = SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))
}
print("status[(status)]")
if result == noErr {
var valueQuery = queryResult as! [String : AnyObject]
valueQuery[kSecClass as String] = kSecClassGenericPassword
valueQuery[kSecReturnData as String] = kCFBooleanTrue /* 検索をデータで受け取る(パスワード) */
var valueQueryResult: AnyObject?
let valueStatus = withUnsafeMutablePointer(to: &valueQueryResult) {
result = SecItemCopyMatching(valueQuery as CFDictionary, UnsafeMutablePointer($0))
}
print("valueStatus[(valueStatus)]")
if result == noErr {
if let passwordData = valueQueryResult as? Data {
if let password = String(data: passwordData, encoding: String.Encoding.utf8) {
uuidString = password
}
}
}
}
検索条件
iCloudキーチェーン
取得
登録
var query = [String : AnyObject]()
query[kSecClass as String] = kSecClassGenericPassword /* パスワードクラス */
query[kSecAttrService as String] = self.service as AnyObject? /* サービス名 */
query[kSecAttrAccount as String] = Identifier.IDENTIFIER_KEY as AnyObject? /* アカウント */
query[kSecAttrLabel as String] = "UUID" as AnyObject? /* ユーザへ表示する文字列 */
query[kSecAttrDescription as String] = "a universally unique identifier." as AnyObject? /* アイテムの説明 */
query[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock as AnyObject?
/* 再起動後最初のアンロック以降 次の再起動まで */
query[kSecValueData as String] = uuidString.data(using: String.Encoding.utf8) as AnyObject?
query[kSecAttrCreationDate as String] = Date() as AnyObject?
query[kSecAttrSynchronizable as String] = kCFBooleanTrue /* iCloud同期 */
/* 登録 */
let result = SecItemAdd(query as CFDictionary, nil)
if result != noErr {
print("[ERROR] Couldn't add the Keychain Item. result = (result) query = (query)")
}
削除
var query = [String : AnyObject]()
query[kSecClass as String] = kSecClassGenericPassword /* パスワードクラス */
query[kSecAttrService as String] = self.service as AnyObject? /* サービス名 */
query[kSecAttrAccount as String] = Identifier.IDENTIFIER_KEY as AnyObject? /* アカウント */
query[kSecAttrSynchronizable as String] = kCFBooleanTrue /* iCloud同期 */
/* 削除 */
let result = SecItemDelete(query as CFDictionary)
if result == noErr {
print("[INFO][noErr] Unique Installation Identifier is successfully reset.")
}
else if result == errSecItemNotFound {
print("[INFO][errSecItemNotFound] Unique Installation Identifier is successfully reset.")
}
else {
print("[ERROR] Coudn't delete the Keychain Item. result = (result) query = (query)")
}
検索 + 更新
var query = [String : AnyObject]()
query[kSecClass as String] = kSecClassGenericPassword /* パスワードクラス */
query[kSecAttrService as String] = self.service as AnyObject? /* サービス名 */
query[kSecAttrAccount as String] = Identifier.IDENTIFIER_KEY as AnyObject? /* アカウント */
query[kSecMatchLimit as String] = kSecMatchLimitOne /* 取得する結果の最大件数を1検討する */
query[kSecReturnAttributes as String] = kCFBooleanTrue /* 結果を辞書で受け取る(属性値) */
query[kSecAttrSynchronizable as String] = kCFBooleanTrue /* iCloud同期 */
var result = noErr
/* 検索 */
var queryResult: AnyObject?
let status = withUnsafeMutablePointer(to: &queryResult) {
result = SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))
}
print("status[(status)]")
if result == noErr {
query = [String : AnyObject]()
query[kSecClass as String] = kSecClassGenericPassword /* パスワードクラス */
query[kSecAttrService as String] = self.service as AnyObject? /* サービス名 */
query[kSecAttrAccount as String] = Identifier.IDENTIFIER_KEY as AnyObject? /* アカウント */
query[kSecAttrSynchronizable as String] = kCFBooleanTrue /* iCloud同期 */
var attributesToUpdate = [String : AnyObject]()
attributesToUpdate[kSecValueData as String] = uuidString.data(using: String.Encoding.utf8) as AnyObject?
attributesToUpdate[kSecAttrCreationDate as String] = Date() as AnyObject?
/* 更新 */
result = SecItemUpdate(query as CFDictionary, attributesToUpdate as CFDictionary)
if result == noErr {
print("SecItemUpdate: noErr")
}
else {
print("SecItemUpdate: error((result))")
}
}
動作確認をしてみて
• iCloudキーチェーンを利用すれば、キーチェーン
が復旧可能となる。
• 複数のデバイスで同期されるので、機器ごとの識
別子でなく、利用者の識別子ということになる。
• 利用者が機能を有効にする必要があるが、その方
法は分かりやすいとは言えないので、使ってもら
えるか。。。

More Related Content

More from 幸雄 村上

アプリケーション識別子.pdf
アプリケーション識別子.pdfアプリケーション識別子.pdf
アプリケーション識別子.pdf幸雄 村上
 
圧縮ネイティブ・ライブラリについて.pdf
圧縮ネイティブ・ライブラリについて.pdf圧縮ネイティブ・ライブラリについて.pdf
圧縮ネイティブ・ライブラリについて.pdf幸雄 村上
 
分散環境におけるジャストインタイム設定の試み
分散環境におけるジャストインタイム設定の試み分散環境におけるジャストインタイム設定の試み
分散環境におけるジャストインタイム設定の試み幸雄 村上
 
SwiftのOptionalを理解する
SwiftのOptionalを理解するSwiftのOptionalを理解する
SwiftのOptionalを理解する幸雄 村上
 
え!それって参照渡し?
え!それって参照渡し?え!それって参照渡し?
え!それって参照渡し?幸雄 村上
 
プライバシーとセキュリティ(リモート通知のデバイストークンの扱いなど)
プライバシーとセキュリティ(リモート通知のデバイストークンの扱いなど)プライバシーとセキュリティ(リモート通知のデバイストークンの扱いなど)
プライバシーとセキュリティ(リモート通知のデバイストークンの扱いなど)幸雄 村上
 
AppleScriptとは何ぞや
AppleScriptとは何ぞやAppleScriptとは何ぞや
AppleScriptとは何ぞや幸雄 村上
 
Web API 通信の符号化について
Web API 通信の符号化についてWeb API 通信の符号化について
Web API 通信の符号化について幸雄 村上
 
Master-Detail App を実装する
Master-Detail App を実装するMaster-Detail App を実装する
Master-Detail App を実装する幸雄 村上
 
SwiftのOptionalを理解する
SwiftのOptionalを理解するSwiftのOptionalを理解する
SwiftのOptionalを理解する幸雄 村上
 
Getting a packet trace
Getting a packet traceGetting a packet trace
Getting a packet trace幸雄 村上
 
The Bash in Tokyo : AppKitとUIKit
The Bash in Tokyo : AppKitとUIKitThe Bash in Tokyo : AppKitとUIKit
The Bash in Tokyo : AppKitとUIKit幸雄 村上
 
Swiftでブロックチェーンを実装する
Swiftでブロックチェーンを実装するSwiftでブロックチェーンを実装する
Swiftでブロックチェーンを実装する幸雄 村上
 
ゲームの企画書づくりに挑戦
ゲームの企画書づくりに挑戦ゲームの企画書づくりに挑戦
ゲームの企画書づくりに挑戦幸雄 村上
 
IBM Watson Services for Core ML
IBM Watson Services for Core MLIBM Watson Services for Core ML
IBM Watson Services for Core ML幸雄 村上
 
独自Documentクラス
独自Documentクラス独自Documentクラス
独自Documentクラス幸雄 村上
 
独自Documentクラス
独自Documentクラス独自Documentクラス
独自Documentクラス幸雄 村上
 

More from 幸雄 村上 (20)

アプリケーション識別子.pdf
アプリケーション識別子.pdfアプリケーション識別子.pdf
アプリケーション識別子.pdf
 
圧縮ネイティブ・ライブラリについて.pdf
圧縮ネイティブ・ライブラリについて.pdf圧縮ネイティブ・ライブラリについて.pdf
圧縮ネイティブ・ライブラリについて.pdf
 
分散環境におけるジャストインタイム設定の試み
分散環境におけるジャストインタイム設定の試み分散環境におけるジャストインタイム設定の試み
分散環境におけるジャストインタイム設定の試み
 
SwiftのOptionalを理解する
SwiftのOptionalを理解するSwiftのOptionalを理解する
SwiftのOptionalを理解する
 
え!それって参照渡し?
え!それって参照渡し?え!それって参照渡し?
え!それって参照渡し?
 
プライバシーとセキュリティ(リモート通知のデバイストークンの扱いなど)
プライバシーとセキュリティ(リモート通知のデバイストークンの扱いなど)プライバシーとセキュリティ(リモート通知のデバイストークンの扱いなど)
プライバシーとセキュリティ(リモート通知のデバイストークンの扱いなど)
 
AppleScriptなど
AppleScriptなどAppleScriptなど
AppleScriptなど
 
MojaveのDark Mode
MojaveのDark ModeMojaveのDark Mode
MojaveのDark Mode
 
AppleScriptとは何ぞや
AppleScriptとは何ぞやAppleScriptとは何ぞや
AppleScriptとは何ぞや
 
Web API 通信の符号化について
Web API 通信の符号化についてWeb API 通信の符号化について
Web API 通信の符号化について
 
Master-Detail App を実装する
Master-Detail App を実装するMaster-Detail App を実装する
Master-Detail App を実装する
 
SwiftのOptionalを理解する
SwiftのOptionalを理解するSwiftのOptionalを理解する
SwiftのOptionalを理解する
 
Getting a packet trace
Getting a packet traceGetting a packet trace
Getting a packet trace
 
The Bash in Tokyo : AppKitとUIKit
The Bash in Tokyo : AppKitとUIKitThe Bash in Tokyo : AppKitとUIKit
The Bash in Tokyo : AppKitとUIKit
 
RUDP
RUDPRUDP
RUDP
 
Swiftでブロックチェーンを実装する
Swiftでブロックチェーンを実装するSwiftでブロックチェーンを実装する
Swiftでブロックチェーンを実装する
 
ゲームの企画書づくりに挑戦
ゲームの企画書づくりに挑戦ゲームの企画書づくりに挑戦
ゲームの企画書づくりに挑戦
 
IBM Watson Services for Core ML
IBM Watson Services for Core MLIBM Watson Services for Core ML
IBM Watson Services for Core ML
 
独自Documentクラス
独自Documentクラス独自Documentクラス
独自Documentクラス
 
独自Documentクラス
独自Documentクラス独自Documentクラス
独自Documentクラス
 

キーチェーン・アクセスのバックアップと同期

  • 2. • MOSA • 1995年から続く団体。 • BUKURO.swiftは、毎月中旬の金曜日に池袋FORESTで開催。 https://mosa.connpass.com/ • Cocoa勉強会 • 2003年から続く勉強会。 • Cocoa勉強会 池袋は、毎月上旬の月曜日に池袋FORESTで開会。 https://cocoa-study.connpass.com/ • Cocoa勉強会 松戸は、毎月下旬の土曜日に松戸degilaboで開催。 https://connpass.com/series/499/ 無料、コワーキングスペース代は各自で 支払うというスタイルです。 時間があったら、お茶する感じで気楽に 来ていてだければ嬉しいです。
  • 4. 識別子 • 個人や機器が特定できる識別子は、セキュリティ上の問題から利用 できない。 • 各アプリでUUIDを生成して、それを識別子とする。 • UUIDをファイルに保存すると、バックアップ対象にできるが、 盗み見ることができる。 • keychainに保存すれば盗み見をガードできるが、バックアップ& リカバリできない。 • iCloudでkeychainの同期ができるようになったが、これを利用す れば!
  • 5. Keychain • 暗号化して保存。 • 暗号化パスワードはバックアップ対象外。 • アプリケーションは、自分が作成したものしか アクセスできない。
  • 6. Keychain Servicesとは • 情報はアイテムと呼ばれる塊である変われる。 • アイテムに格納されるのはパスワードや暗号鍵、 証明書など。 • 情報を格納するデータは、パスワードによって保 護されている。 • 登録や変更、削除、検索するためのAPIが用意さ れている。
  • 7. API • func SecItemAdd(_ attributes: CFDictionary, _ result: UnsafeMutablePointer<CFTypeRef?>?) -> OSStatus • func SecItemCopyMatching(_ query: CFDictionary, _ result: UnsafeMutablePointer<CFTypeRef?>?) -> OSStatus • func SecItemUpdate(_ query: CFDictionary, _ attributesToUpdate: CFDictionary) -> OSStatus • func SecItemDelete(_ query: CFDictionary) -> OSStatus
  • 8. 検索 + 取得 var uuidString = "" var query = [String : AnyObject]() query[kSecClass as String] = kSecClassGenericPassword /* パスワードクラス */ query[kSecAttrService as String] = self.service as AnyObject? /* サービス名 */ query[kSecAttrAccount as String] = Identifier.IDENTIFIER_KEY as AnyObject? /* アカウント */ query[kSecMatchLimit as String] = kSecMatchLimitOne /* 取得する結果の最大件数を1検討する */ query[kSecReturnAttributes as String] = kCFBooleanTrue /* 結果を辞書で受け取る(属性値) */ query[kSecAttrSynchronizable as String] = kCFBooleanTrue /* iCloud同期 */ var result = noErr /* 検索 */ var queryResult: AnyObject? let status = withUnsafeMutablePointer(to: &queryResult) { result = SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0)) } print("status[(status)]") if result == noErr { var valueQuery = queryResult as! [String : AnyObject] valueQuery[kSecClass as String] = kSecClassGenericPassword valueQuery[kSecReturnData as String] = kCFBooleanTrue /* 検索をデータで受け取る(パスワード) */ var valueQueryResult: AnyObject? let valueStatus = withUnsafeMutablePointer(to: &valueQueryResult) { result = SecItemCopyMatching(valueQuery as CFDictionary, UnsafeMutablePointer($0)) } print("valueStatus[(valueStatus)]") if result == noErr { if let passwordData = valueQueryResult as? Data { if let password = String(data: passwordData, encoding: String.Encoding.utf8) { uuidString = password } } } } 検索条件 iCloudキーチェーン 取得
  • 9. 登録 var query = [String : AnyObject]() query[kSecClass as String] = kSecClassGenericPassword /* パスワードクラス */ query[kSecAttrService as String] = self.service as AnyObject? /* サービス名 */ query[kSecAttrAccount as String] = Identifier.IDENTIFIER_KEY as AnyObject? /* アカウント */ query[kSecAttrLabel as String] = "UUID" as AnyObject? /* ユーザへ表示する文字列 */ query[kSecAttrDescription as String] = "a universally unique identifier." as AnyObject? /* アイテムの説明 */ query[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock as AnyObject? /* 再起動後最初のアンロック以降 次の再起動まで */ query[kSecValueData as String] = uuidString.data(using: String.Encoding.utf8) as AnyObject? query[kSecAttrCreationDate as String] = Date() as AnyObject? query[kSecAttrSynchronizable as String] = kCFBooleanTrue /* iCloud同期 */ /* 登録 */ let result = SecItemAdd(query as CFDictionary, nil) if result != noErr { print("[ERROR] Couldn't add the Keychain Item. result = (result) query = (query)") }
  • 10. 削除 var query = [String : AnyObject]() query[kSecClass as String] = kSecClassGenericPassword /* パスワードクラス */ query[kSecAttrService as String] = self.service as AnyObject? /* サービス名 */ query[kSecAttrAccount as String] = Identifier.IDENTIFIER_KEY as AnyObject? /* アカウント */ query[kSecAttrSynchronizable as String] = kCFBooleanTrue /* iCloud同期 */ /* 削除 */ let result = SecItemDelete(query as CFDictionary) if result == noErr { print("[INFO][noErr] Unique Installation Identifier is successfully reset.") } else if result == errSecItemNotFound { print("[INFO][errSecItemNotFound] Unique Installation Identifier is successfully reset.") } else { print("[ERROR] Coudn't delete the Keychain Item. result = (result) query = (query)") }
  • 11. 検索 + 更新 var query = [String : AnyObject]() query[kSecClass as String] = kSecClassGenericPassword /* パスワードクラス */ query[kSecAttrService as String] = self.service as AnyObject? /* サービス名 */ query[kSecAttrAccount as String] = Identifier.IDENTIFIER_KEY as AnyObject? /* アカウント */ query[kSecMatchLimit as String] = kSecMatchLimitOne /* 取得する結果の最大件数を1検討する */ query[kSecReturnAttributes as String] = kCFBooleanTrue /* 結果を辞書で受け取る(属性値) */ query[kSecAttrSynchronizable as String] = kCFBooleanTrue /* iCloud同期 */ var result = noErr /* 検索 */ var queryResult: AnyObject? let status = withUnsafeMutablePointer(to: &queryResult) { result = SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0)) } print("status[(status)]") if result == noErr { query = [String : AnyObject]() query[kSecClass as String] = kSecClassGenericPassword /* パスワードクラス */ query[kSecAttrService as String] = self.service as AnyObject? /* サービス名 */ query[kSecAttrAccount as String] = Identifier.IDENTIFIER_KEY as AnyObject? /* アカウント */ query[kSecAttrSynchronizable as String] = kCFBooleanTrue /* iCloud同期 */ var attributesToUpdate = [String : AnyObject]() attributesToUpdate[kSecValueData as String] = uuidString.data(using: String.Encoding.utf8) as AnyObject? attributesToUpdate[kSecAttrCreationDate as String] = Date() as AnyObject? /* 更新 */ result = SecItemUpdate(query as CFDictionary, attributesToUpdate as CFDictionary) if result == noErr { print("SecItemUpdate: noErr") } else { print("SecItemUpdate: error((result))") } }