Slide 1

Slide 1 text

快適な開発と高セキュリティを 
 実現するCryptoKitを活用した 
 Core Dataのデータ暗号化術 
 
 加藤 雄大 / @Takahiro_Kato15 
 iOSDC Japan 2024


Slide 2

Slide 2 text

● 加藤 雄大(かとう たかひろ) 
 ● LINEヤフー株式会社 
 ● 2019年中途入社 
 ● iOSエンジニア 
 自己紹介

Slide 3

Slide 3 text

1. Core Dataのデータ暗号化の重要性
 2. Core Dataの暗号化手法と選定方法
 3. CryptoKit 暗号化 / 復号のコード例
 4. CryptoKit のパフォーマンス検証 アジェンダ

Slide 4

Slide 4 text

Core Dataのデータ暗号化 の重要性


Slide 5

Slide 5 text

なぜCore Dataのデータを暗号化する 
 必要があるのでしょうか? 


Slide 6

Slide 6 text

iOSのデータ保護機能では防げない 
 ケースがあるからです。。。 
 なぜCore Dataのデータを暗号化する 
 必要があるのでしょうか? 


Slide 7

Slide 7 text

● パスワードロックをかけていない端末が盗難 され、端末の情報にアクセスされる
 ● macOSの脆弱性を利用して、Macに接続さ れた端末の情報にアクセスする
 考えられるリスクシナリオ

Slide 8

Slide 8 text

● ユーザの個人情報を脅威から守ることがで きる
 ● アプリのブランディングを向上させることがで きる
 想定リスクに対応することで

Slide 9

Slide 9 text

Core Dataの暗号化手法 と選定方法 


Slide 10

Slide 10 text

1. 全体の暗号化
 2. 保存情報の暗号化
 Core Dataの暗号化手法

Slide 11

Slide 11 text

全体の暗号化とは? User id name 1 2 3 Taro Jiro Saburo University id name 1 2 3 Waseda Keio Tokyo SQLiteファイル

Slide 12

Slide 12 text

● SQLCipher
 ○ OSS
 ○ SQLiteファイルごとAES-256暗号化
 ○ 複数のPlatformで利用することが可能
 ■ iOS, Android, Linux, Windows
 全体の暗号化の実現方法 


Slide 13

Slide 13 text

保存情報の暗号化とは? User id name 1 2 3 University id name 1 2 3 SQLiteファイル U2Fs… GVkX… 8kF8… 2N9e… F6Mz… 18Xc…

Slide 14

Slide 14 text

● CryptoKit
 ○ Apple公式の暗号化Framework
 ○ 幅広いアルゴリズムに対応
 ○ APIが簡易で使いやすい
 保存情報の暗号化の実現方法 


Slide 15

Slide 15 text

では、暗号化手法は 
 どうやって決めれば良いのでしょうか? 


Slide 16

Slide 16 text

では、暗号化手法は 
 どうやって決めれば良いのでしょうか? 
 銀の弾丸は存在しません! 
 メリット/デメリットを理解して、 
 チームやプロダクトに合う方法を採用しましょ う!


Slide 17

Slide 17 text

SQLCipherのメリット・デメリット 
 メリット
 デメリット 
 ● 暗号化すべきデータ の精査が不要
 ● 歴史があり、OSSとし て信用できる
 ● Swift Package Managerに非対応
 ● Build時間が長い
 ● コア部分のカスタム実 装が必要になる


Slide 18

Slide 18 text

● 保存する全ての情報を暗号化する必要があ る場合
 ● Core Dataだけでなく、SQLiteの深い知識を 持つチームである場合
 SQLCipherを選ぶケース 


Slide 19

Slide 19 text

CryptoKitのメリット・デメリット 
 メリット
 デメリット 
 ● Apple公式提供のため OSアップデートへの 追従が容易
 ● 暗号化 / 復号が容易 に実装できる
 ● 暗号化すべきデータ の精査が必要になる
 ● DB検索やSortの実装 に工夫が必要になる


Slide 20

Slide 20 text

● 特定の情報のみ暗号化する必要がある場 合
 ● Core Dataの標準機能の知識を持つチーム である場合
 CryptoKitを選ぶケース 


Slide 21

Slide 21 text

CryptoKit 暗号化 / 復号 のコード例 


Slide 22

Slide 22 text

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 }

Slide 23

Slide 23 text

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 }

Slide 24

Slide 24 text

「暗号化と復号」メソッドの呼び出し例 
 let name = “Takahiro_Kato15” let key = let nonce = try AES.GCM.Nonce() // 暗号化 let encrypted = try name.encrypt(key: key, nonce: nonce) // 復号 let decrypted = try encrypted.decrypt(key: key)

Slide 25

Slide 25 text

CryptoKitを使えば、 
 簡単に暗号化と復号ができる! 


Slide 26

Slide 26 text

とはいかず... 
 もちろん注意点があります。 


Slide 27

Slide 27 text

注意点①: 
 「暗号化 / 復号」時に例外を投げる 
 // 暗号化 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)

Slide 28

Slide 28 text

注意点①: 
 「暗号化 / 復号」時に例外を投げる 
 // 暗号化 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」を投げる 


Slide 29

Slide 29 text

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


Slide 30

Slide 30 text

Hash化
 Taro 8d96… name 注意点②: 
 暗号化情報の検索にHash値を利用する 
 User 1 2 3 U2Fs… GVkX… 8kF8… id name hashedName 8d96… 5e88… 0d4f… 検索
 +Secret Salt (Keychainに保持)

Slide 31

Slide 31 text

注意点②: 
 暗号化情報の検索に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() }

Slide 32

Slide 32 text

注意点③: 
 Sort専用のAttributeを用意する 
 User 1 2 3 U2Fs… GVkX… 8kF8… id name hashedName 8d96… 5e88… 0d4f… nameOrder 2 0 1 Sort専用のAttributeを定義 


Slide 33

Slide 33 text

注意点③: 
 データの追加/更新時に再計算する 
 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

Slide 34

Slide 34 text

CryptoKit の 
 パフォーマンス検証 


Slide 35

Slide 35 text

CryptoKitによる暗号化 / 復号 / Hash化 
 の処理速度を見てみましょう! 


Slide 36

Slide 36 text

CryptoKitによる1,000文字の文字列の暗 号化 / 復号の処理速度 
 端末
 暗号化 [sec]
 復号 [sec]
 iPhone6s Plus (iOS15)
 2.26e-5
 4.07e-5
 iPhone12 Pro (iOS17)
 1.42e-5 
 6.95e-6


Slide 37

Slide 37 text

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


Slide 38

Slide 38 text

まとめ


Slide 39

Slide 39 text

● Core Dataの暗号化はユーザの個人情報を 守り、アプリのブランディングを向上させる
 ● Core Dataの暗号化手法は2つある
 ○ 全体の暗号化(SQLCipher)
 ○ 保存情報の暗号化(CryptoKit)
 ● 暗号化手法はメリット・デメリットを理解した 上で選定する


Slide 40

Slide 40 text

● CryptoKitは本当に快適かつ安全に暗号化 できるのか
 ○ 暗号化 / 復号のメソッドは簡単に実装可 能だが、注意点がある
 ■ 要件に合わせた例外対応
 ■ Hash値による検索対応
 ■ 専用AttributeによるSort(都度Sortの 再計算は必要)


Slide 41

Slide 41 text

● CryptoKitのパフォーマンス
 ○ 暗号化 / 復号は高速
 ○ Hash化のストレッチング回数はパフォー マンスとのトレードオフ
 CryptoKitで工夫すれば、下記を実現できる 
 ・快適な開発 
 ・高セキュリティ 


Slide 42

Slide 42 text

Appendix 


Slide 43

Slide 43 text

case
 種別
 ● incorrectKeySize
 ● invalidParameter
 ● incorrectParameterSize 
 ● authenticationFailure 
 ● wrapFailure
 ● unwrapFailure
 恒久的に発生する
 ● underlyingCoreCryptoError(error: Int32) 
 一時的に発生する
 CryptoKitError 


Slide 44

Slide 44 text

暗号化 / 復号時の例外 の対処方法の例 


Slide 45

Slide 45 text

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


Slide 46

Slide 46 text

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


Slide 47

Slide 47 text

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


Slide 48

Slide 48 text

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


Slide 49

Slide 49 text

データマイグレーション 


Slide 50

Slide 50 text

データマイグレーションが必要なケース 
 ● 社内セキュリティ要件の変更
 ● 暗号化の対応漏れ etc


Slide 51

Slide 51 text

Heavy Weight Migrationに必要なもの 
 ● Model Mappingファイル
 ● Custom Policy


Slide 52

Slide 52 text

Model Mappingファイル 
 ● マイグレーション元とマイグレーション先の組 み合わせ毎にModel Mappingファイルが必要
 ○ v1 → v3
 ○ v2 → v3


Slide 53

Slide 53 text

Model Mappingファイル 
 ● マイグレーション元とマイグレーション先の組 み合わせ毎にModel Mappingファイルが必要
 ○ v1 → v3
 ○ v2 → v3
 対応Verが多いほど管理が大変... 


Slide 54

Slide 54 text

Custom Policy 
 ● NSEntityMigrationPolicyのカスタムClassを 実装
 ○ 元のデータを暗号化、Hash化する独自の ロジックを組み込むために必要


Slide 55

Slide 55 text

Custom Policy 
 ● カスタムClassは、Model Mappingファイル上 で、必要なEntity Mappingに指定する


Slide 56

Slide 56 text

@objc(CustomMigrationPolicy) class CustomMigrationPolicy: NSEntityMigrationPolicy { override func createDestinationInstances(forSource sourceInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws { // ここで暗号化、Hash化した値を設定する } } Custom Policy実装例 


Slide 57

Slide 57 text

その他


Slide 58

Slide 58 text

SQLCipherによるSQLファイルの暗号化につ いて(補足) 
 ● SQLiteのページ単位で暗号化する
 ○ 読み書きに必要なページのみに影響範 囲が限定されるため、パフォーマンス向 上に繋がる
 ○ CryptoKitでSQLiteファイルごと暗号化 / 復号するより高パフォーマンス


Slide 59

Slide 59 text

暗号化用の鍵の作り方 
 // 暗号化用の鍵は下記メソッドで、簡単に作成できる let key = SymmetricKey(size: .bits256)

Slide 60

Slide 60 text

GCMとCBCの違い 
 ● GCM:認証付き暗号化モード
 ● CBC:ブロック暗号化モード
 ● GCMはデータの改ざんを検知できるが、 CBCは単体では検知できない(Hashを併用 するなど対策が必要)


Slide 61

Slide 61 text

注意点②: 
 暗号化情報の検索にHash値を利用する 
 ● Saltを利用してHash化させることでHash結果 が常に異なる
 ● Secret Saltは、Hash化をより強化するため に利用する
 ● Saltだと検索不能なため、Secret Saltのみ 利用することを許容する


Slide 62

Slide 62 text

CryptoKitで100回連続で暗号化/復号を 実行した場合の処理速度 
 端末
 暗号化 [sec]
 復号 [sec]
 iPhone6s Plus (iOS15)
 2.29e-3
 2.25e-3
 iPhone12 Pro (iOS17)
 8.15e-4 
 6.65e-4
 ※異なる1,000文字の文字列を連続で暗号化/復号した結果の平均値