Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

快適な開発と高セキュリティを実現するCryptoKitを活用したCoreDataのデータ暗号化術

 快適な開発と高セキュリティを実現するCryptoKitを活用したCoreDataのデータ暗号化術

永続化されたデータの保護は、現代のiOSアプリ開発において必要不可欠な要素です。
しかし、Appleが提供する永続化フレームワークであるCore Dataは標準でデータの暗号化を提供しません。
また、Core Dataのデータ暗号化には、「データベース全体を暗号化する方法(SQLCipher)」や「保存するデータを個別に暗号化する方法(CryptoKit)」があります。
この資料では、それぞれのメリット/デメリットを比較した上で、Core Dataにおけるデータの暗号化手法とその選定方法について詳しく紹介します。

参加者は以下の内容を学ぶことができます。

1. データ保護の重要性
2. Core Dataの各暗号化手法のメリット/デメリット
3. Core Dataの暗号化手法の選定方法
4. CryptoKitによる暗号化と復号化のコード例
5. CryptoKitによる暗号化と復号化のパフォーマンス検証

この資料を通して、「Core Data」の暗号化に必要なナレッジや各利点について理解を深めて頂ければ幸いです。

Takahiro Kato

August 23, 2024
Tweet

Other Decks in Programming

Transcript

  1. 1. Core Dataのデータ暗号化の重要性
 2. Core Dataの暗号化手法と選定方法
 3. CryptoKit 暗号化 /

    復号のコード例
 4. CryptoKit のパフォーマンス検証 アジェンダ
  2. 全体の暗号化とは? User id name 1 2 3 Taro Jiro Saburo

    University id name 1 2 3 Waseda Keio Tokyo SQLiteファイル
  3. 保存情報の暗号化とは? User id name 1 2 3 University id name

    1 2 3 SQLiteファイル U2Fs… GVkX… 8kF8… 2N9e… F6Mz… 18Xc…
  4. SQLCipherのメリット・デメリット 
 メリット
 デメリット 
 • 暗号化すべきデータ の精査が不要
 • 歴史があり、OSSとし

    て信用できる
 • Swift Package Managerに非対応
 • Build時間が長い
 • コア部分のカスタム実 装が必要になる

  5. CryptoKitのメリット・デメリット 
 メリット
 デメリット 
 • Apple公式提供のため OSアップデートへの 追従が容易
 •

    暗号化 / 復号が容易 に実装できる
 • 暗号化すべきデータ の精査が必要になる
 • DB検索やSortの実装 に工夫が必要になる

  6. String Extensionへの暗号化処理の定義例 
 func encrypt(key: SymmetricKey, nonce: AES.GCM.Nonce) throws ->

    String { guard let data = self.data(using: .utf8) else { throw NSError() } // SealedBoxを生成 let seal = try AES.GCM.seal(data, using: key, nonce: nonce) // 暗号化結果の取得 guard let encrypted = seal.combined?.base64EncodingString() else { throw NSError() } return encrypted }
  7. String Extensionへの復号処理の定義例 
 func decrypt(key: SymmetricKey) throws -> String {

    guard let data = Data(base64Encoded: self) else { throw NSError() } // SealedBoxを生成 let sealedBox = try AES.GCM.SealedBox(combined: data) // 復号結果を取得 let decryptedData = try AES.GCM.open(sealedBox, using: key) guard let decrypted = String(data: decryptedData, encoding: .utf8) else { throw NSError() } return decrypted }
  8. 「暗号化と復号」メソッドの呼び出し例 
 let name = “Takahiro_Kato15” let key = <Keychainに保存している鍵>

    let nonce = try AES.GCM.Nonce() // 暗号化 let encrypted = try name.encrypt(key: key, nonce: nonce) // 復号 let decrypted = try encrypted.decrypt(key: key)
  9. 注意点①: 
 「暗号化 / 復号」時に例外を投げる 
 // 暗号化 let seal

    = try AES.GCM.seal(data, using: key, nonce: nonce) // 復号 let sealedBox = try AES.GCM.SealedBox(combined: data) let decryptedData = try AES.GCM.open(sealedBox, using: key)
  10. 注意点①: 
 「暗号化 / 復号」時に例外を投げる 
 // 暗号化 let seal

    = try AES.GCM.seal(data, using: key, nonce: nonce) // 復号 let sealedBox = try AES.GCM.SealedBox(combined: data) let decryptedData = try AES.GCM.open(sealedBox, using: key) 「CryptoKitError」を投げる 

  11. 梅
 竹
 松
 • nil保存OK
 • nil保存NG
 • ユーザへの フィードバッ

    クなし
 • nil保存NG
 • ユーザへの フィードバッ クあり
 注意点①: 
 例外の対処方法は、アプリ要件次第 

  12. Hash化
 Taro 8d96… name 注意点②: 
 暗号化情報の検索にHash値を利用する 
 User 1

    2 3 U2Fs… GVkX… 8kF8… id name hashedName 8d96… 5e88… 0d4f… 検索
 +Secret Salt (Keychainに保持)
  13. 注意点②: 
 暗号化情報の検索にHash値を利用する 
 func hash(secretSalt: String, stretchingCount: Int) throws

    -> String { var hashData = Data((self + secretSalt).utf8) for _ in 0…stretchingCount { let digest = SHA256.hash(data: hashData) hashData = Data(digest) } return hashData.map { String(format: “%02hhx”, $0) }.joined() }
  14. 注意点③: 
 Sort専用のAttributeを用意する 
 User 1 2 3 U2Fs… GVkX…

    8kF8… id name hashedName 8d96… 5e88… 0d4f… nameOrder 2 0 1 Sort専用のAttributeを定義 

  15. 注意点③: 
 データの追加/更新時に再計算する 
 User 1 2 3 U2Fs… GVkX…

    8kF8… id name … … nameOrder 2 0 1 name Taro Jiro Saburo 復号
 Shiro Before Jiro Saburo Taro 0 1 2 Jiro Saburo Taro Shiro 0 1 2 3 After
  16. CryptoKitによる1,000文字の文字列の暗 号化 / 復号の処理速度 
 端末
 暗号化 [sec]
 復号 [sec]


    iPhone6s Plus (iOS15)
 2.26e-5
 4.07e-5
 iPhone12 Pro (iOS17)
 1.42e-5 
 6.95e-6

  17. CryptoKitによる1,000文字の文字列の Hash化の処理速度 
 
 Hash化 [sec]
 ストレッチング 回数
 10,000
 1,000


    100
 iPhone6s Plus (iOS15)
 3.95e-2
 4.03e-3
 5.26e-4
 iPhone12 Pro (iOS17)
 1.58e-2 
 1.55e-3
 1.94e-4

  18. case
 種別
 • incorrectKeySize
 • invalidParameter
 • incorrectParameterSize 
 •

    authenticationFailure 
 • wrapFailure
 • unwrapFailure
 恒久的に発生する
 • underlyingCoreCryptoError(error: Int32) 
 一時的に発生する
 CryptoKitError 

  19. 梅
 竹
 松
 • nil保存OK
 • nil保存NG
 • ユーザへの フィードバッ

    クなし
 • nil保存NG
 • ユーザへの フィードバッ クあり
 例外の対処方法は、アプリ要件次第 

  20. class CryptoValueTransformer: ValueTransformer { override func transformedValue( _ value: Any?)

    -> Any? { // ここで暗号化 } override func reverseTransformedValue( _ value: Any?) -> Any? { // ここで暗号化 } } ValueTransformerを利用した例 

  21. 梅
 竹
 松
 • nil保存OK
 • nil保存NG
 • ユーザへの フィードバッ

    クなし
 • nil保存NG
 • ユーザへの フィードバッ クあり
 例外の対処方法は、アプリ要件次第 

  22. class CustomManagedObjectContext: NSManagedObjectContext { override func save() throws { //

    ここで暗号化 try super.save() } • 暗号化失敗時にthrowsで例外を伝搬することで nil保存させない。
 • ユーザへのフィードバックは制御次第。
 NSManagedObjectContextの継承classを 実装する例 

  23. @objc(CustomMigrationPolicy) class CustomMigrationPolicy: NSEntityMigrationPolicy { override func createDestinationInstances(forSource sourceInstance: NSManagedObject,

    in mapping: NSEntityMapping, manager: NSMigrationManager) throws { // ここで暗号化、Hash化した値を設定する } } Custom Policy実装例 

  24. CryptoKitで100回連続で暗号化/復号を 実行した場合の処理速度 
 端末
 暗号化 [sec]
 復号 [sec]
 iPhone6s Plus

    (iOS15)
 2.29e-3
 2.25e-3
 iPhone12 Pro (iOS17)
 8.15e-4 
 6.65e-4
 ※異なる1,000文字の文字列を連続で暗号化/復号した結果の平均値