昨今のアプリはオンラインでの利用が当たり前であり、その中で多くのユーザ情報を扱っています。アプリの利便性やパフォーマンス向上のため、以下のようなことはよく行われているかと思います。
- Web サービスに自動ログインするための ID やパスワードを保存する。
- ユーザの個人情報を端末内にキャッシュする。
これらの情報は極めて慎重に取り扱う必要があるのは言うまでもありません。では iOS でこういったデータを安全に保管するにはどうしたらよいのか、施策とそこで利用できる機能について紹介したいと思います。
Keychain Services
Keychain Services を使うと、パスワード等の重要な情報を OS 側に管理させることができます。登録した情報はアプリケーションフォルダの下に保存されるわけではないので、簡単にはアクセスすることはできないでしょう。
ただ iOS8.1 の時点では Keychain Services にアクセスする API は、C の関数でしか提供されていません。そのまま利用するのはやや敷居が高いので、LUKeychainAccess というオープンソースライブラリ(MIT ライセンス)を利用した例を紹介します。
LUKeychainAccess は CocoaPods から簡単に導入できますので、手順は割愛します。
使い方についてもさほど難しくありません。
// Keychain Service からパスワードを取得 + (NSString*)password { LUKeychainAccess* keyChainAccess = [LUKeychainAccess standardKeychainAccess]; return [keyChainAccess stringForKey:@"password"]; }
// Keychain Service にパスワードを保管 + (void)savePassword:(NSString*)password { [keyChainAccess setString:password forKey:@"password"]; }
OS 管理下にあるからといって、必ずしも安全とは限りません。端末が JailBreak された場合はその内容を読み取られる可能性もあります。異なるアルゴリズムの暗号化をかけたり、パスワードではなく、ログイン時にサーバが発行するアクセストークンを Keychain Services で保管するなど、複数の対策を行うとより安全でしょう。
Data Protection
Keychain Services に保管できるデータのサイズは限られるので、大きなデータを扱う場合はファイルを利用することになります。iOS では Data Protection というファイル保護の仕組みが備わっています。
- 端末ロック時にファイルの保護を行い、アンロック中はこれを解除する。
- ユーザがパスコードロックを設定していないと機能しない。
iOS では、下記のデータ保護属性が定義されています。
- NSFileProtectionNone: データ保護を一切行わない。
- NSFileProtectionComplete: 端末ロック中はデータを保護。
- NSFileProtectionCompleteUnlessOpen: 端末ロック中でも、ファイル作成とロック解除中に開いたファイルに限りアクセス可能。これら以外で端末ロック中はデータを保護。
- NSFileProtectionCompleteUntilFirstUserAuthentication: 端末起動後、ユーザがロック解除するまでデータを保護。
これらはバックグラウンドタスクでファイルへのアクセスが必要かどうか等、アプリの特性によって使い分けることになります。
ファイル作成時にデフォルトでどの属性が適用されるかは、フレームワークや API によって異なります。例えば NSData#writeToURL:options:error:
では特に指定が無い場合は NSFileProtectionComplete
が適用されますが、NSPersistentStoreCoordinator
で永続化した Core Data のデータベースファイルは NSFileProtectionCompleteUntilFirstUserAuthentication
が適用されます。
ファイルにどのデータ保護が適用されているかは、次のようにファイルの属性を調べることで知ることができます。
+ (NSString*)fileProtectionKeyAttributeForFileURL:(NSURL*)URL { NSError* error = nil; NSDictionary* attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[URL path] error:&error]; return attributes ? attributes[NSFileProtectionKey] : nil; }
今あるファイルのデータ保護属性を変更することもできます。
+ (BOOL)setFileProtectionAttribute:(NSSting*)attribute toFileURL:(NSURL*)URL { NSError* error = nil; return [[NSFileManager defaultManager] setAttributes:@{NSFileProtectionKey: attribute} ofItemAtPath:[URL path] error:&error]; }
Data Protection の振る舞いを検証するため、データ保護を行ったファイル protected.dat
と、保護を行っていないファイル unprotected.dat
を iExplorer というツールを使って確認してみました。
端末ロック状態で検証アプリの Documents フォルダを見てみると、どちらのファイルも表示されています。この 2 つのファイルを選択してエクスポートすると、unprotected.dat
は Mac に転送されましたが、protected.dat
は転送できませんでした。
このように Data Protection によって端末ロック中はデータを読み取ることができなくなりますが、ロックを解除されてしまった場合は読み取ることができてしまいます。そのため、データ自体の暗号化も行うことを検討しましょう。
データの暗号化
iOS でデータの暗号化を行う方法としては、CommonCrypto を利用するという手があります。ただこれもまた C の API を呼び出す必要があります。
SQLite を利用してデータを保管している場合は、CommonCrypto で DB ファイル全体を暗号化するのでは効率が良くありません。そこで SQLCipher というライブラリ(BSD ライセンス)を利用する例を紹介したいと思います。
オープンソース版 SQLCipher もまた CocoaPods を介して取り込むことができます。SQLite ラッパーの FMDB とともにプロジェクトに取り込むには、Podfile に以下を追記します。
pod 'FMDB/SQLCipher'
これだけで FMDB が SQLCipher を介して DB にアクセスしてくれるようになりますが、その際には必ず鍵となる認証キーを指定する必要があります。
- (NSArray*)readAllDataFromDatabase:(FMDatabase*)db { [db open]; [db setKey:AUTH_KEY]; // 認証キー NSArray* allData = ... // executeQuery: で全データを取得 [db close]; return allData; }
SQLite データベースビューワで 作成された DB ファイルを開こうとしても、開くことはできません。バイナリエディタで見ても、暗号化されていることが分かります。
鍵の保管先の問題
データの暗号化において共通する問題は「鍵をどこへ保管するか?」です。いくらデータを暗号化しても、その復号鍵が手に入ってしまえば元のデータを読み取ることができてしまいます。
一番手軽で有効な方法は、上記の Keychain Services に鍵を保管してしまうことでしょう。一般的な対策として鍵の分散化やサーバに保管といった方法もありますが、アプリの場合はこのような方法は難しいかもしれません。端末内には極力重要な情報は保管しなくて済むようなアプリ設計を行うこと も、有効な対策のひとつでしょう。
エンドユーザも顧客も安心して使える、アプリ開発の一助になれば幸いです。