はじめに
Xamarin.iOS で Realm データベースを暗号化して書き込み・読み込みをし、暗号キーをキーチェーンに保存し再利用する方法を例示します。
最初は、公式のサンプルがいきなり間違えていたので手間取りましたが、なんとかゴールに到達することができました。
あと、暗号キーを16進文字列にする処理がありますが、これは Realm Browser が暗号化された Realm データベースを読み込むのに128文字の暗号キーを入力する必要があること、キーチェーンには文字列のデータしか格納できないという理由のためです。
Realm の通常の使い方、およびキーチェーンの扱いについては以下の記事を参照してください。
では実際に動作したコードを見ていきます。
暗号キーを生成してRealm データベースを暗号化する
Realm データベースを暗号化する暗号キーは64ビットのバイト配列である必要があります。
暗号キー乱数で生成しますが、同じ暗号キーを使い回せるようシングルトンで生成しています。
また、暗号キーは16進文字列に変換して Realm Browser やキーチェーンへの保存に利用しています。
ソースコード全体は後でまとめて掲載します。
以下はデータとして利用する Person クラスです。
using System; using Realms; namespace Xamarin.iOS.KeyChain { public class Person:RealmObject { public string Name { get; set; } public int Age { get; set; } } }
64ビットの暗号キーを生成するためのメソッドは以下のようになります。シングルトンで生成しているため、同じキーを何度も取得できます。
//暗号キーをシングルトンで取得するためにstaticで宣言 private static byte[] byteKey = null; //(中略) //64ビットの暗号キーを生成(シングルトン) private byte[] CreateByteKey() { if (byteKey == null) { //64ビットの暗号キーを生成 var random = new Random(); byteKey = new Byte[64]; random.NextBytes(byteKey); return byteKey; } else { return byteKey; } }
64ビットのバイト配列を16進文字列に変換するメソッドです。
//バイト配列から16進文字列を生成する private string GetEcriptKeyString(byte[] key) { var encriptKey = BitConverter.ToString(key); encriptKey = encriptKey.Replace("-", string.Empty); return encriptKey; }
これらにより、以下の Realm データベースを暗号化しデータを保存し、キーチェーンに暗号キーを保存します。
partial void buttonSaveDown(UIButton sender) { //64ビットの暗号キーを生成 var key = CreateByteKey(); //暗号キーを出力(16進文字列) var encriptKey = GetEcriptKeyString(key); Debug.WriteLine(encriptKey); //Realmデータベース設定に暗号キーを設定 var config = new RealmConfiguration("Mine.realm"); config.EncryptionKey = key; //暗号化してデータベースを開く var realm = Realm.GetInstance(config); //Realmデータベースのパスを出力 Debug.WriteLine(realm.Config.DatabasePath); //データを保存する realm.Write(() => { realm.Add(new Person { Name = "山田太郎", Age = 23 }); realm.Add(new Person { Name = "佐藤花子", Age = 18 }); realm.Add(new Person { Name = "田中哲朗", Age = 33 }); }); //暗号キーをキーチェーンに保存する var prop = new Dictionary<string, string>(); prop["encriptKey"] = encriptKey; var account = new Xamarin.Auth.Account("userName", prop); var store = Xamarin.Auth.AccountStore.Create(); store.Save(account, "serviceId"); }
このメソッドにより、3件のデータが暗号化され Realm データベースに格納されました。
そして、以下のメソッドでキーチェーンから読み出した16進文字列の暗号キーをバイト配列に戻します。
//16進文字列からバイト配列を生成する private byte[] GetEncriptKeyByteArray(string encriptKey) { var bs = new List<byte>(); for (int i = 0; i < encriptKey.Length / 2; i++) { bs.Add(Convert.ToByte(encriptKey.Substring(i * 2, 2), 16)); } return bs.ToArray(); }
これらにより、以下のメソッドでキーチェーンから暗号キーを読み込み、Realm データベースの復号を行い読み込んだデータを出力します。
partial void buttonReadDown(UIButton sender) { //キーチェーンからパスワードを取り出す var store = Xamarin.Auth.AccountStore.Create(); var account = store.FindAccountsForService("serviceId") .SingleOrDefault(); var encriptKey = account.Properties["encriptKey"]; Debug.WriteLine(encriptKey); //Realmデータベース設定に暗号キーを設定(暗号キーは16進文字列からbyte配列変換) var config = new RealmConfiguration("Mine.realm"); config.EncryptionKey = GetEncriptKeyByteArray(encriptKey); //暗号化してデータベースを開く var realm = Realm.GetInstance(config); //Realmデータベースのパスを出力 Debug.WriteLine(realm.Config.DatabasePath); //データベースのデータを読み込み var person = realm.All<Person>(); foreach (var p in person) { Debug.WriteLine(p.Name + ":" + p.Age.ToString()); } }
このメソッドを実行した結果は以下のようになり、正しく処理が行われていることが分かります。
山田太郎:23 佐藤花子:18 田中哲朗:33
以上の全体のソースコードは以下のようになります。
using System; using UIKit; using System.Diagnostics; using System.Collections.Generic; using System.Linq; using Realms; namespace Xamarin.iOS.KeyChain { public partial class ViewController : UIViewController { //暗号キーをシングルトンで取得するためにstaticで宣言 private static byte[] byteKey = null; partial void buttonSaveDown(UIButton sender) { //64ビットの暗号キーを生成 var key = CreateByteKey(); //暗号キーを出力(16進文字列) var encriptKey = GetEcriptKeyString(key); Debug.WriteLine(encriptKey); //Realmデータベース設定に暗号キーを設定 var config = new RealmConfiguration("Mine.realm"); config.EncryptionKey = key; //暗号化してデータベースを開く var realm = Realm.GetInstance(config); //Realmデータベースのパスを出力 Debug.WriteLine(realm.Config.DatabasePath); //データを保存する realm.Write(() => { realm.Add(new Person { Name = "山田太郎", Age = 23 }); realm.Add(new Person { Name = "佐藤花子", Age = 18 }); realm.Add(new Person { Name = "田中哲朗", Age = 33 }); }); //暗号キーをキーチェーンに保存する var prop = new Dictionary<string, string>(); prop["encriptKey"] = encriptKey; var account = new Xamarin.Auth.Account("userName", prop); var store = Xamarin.Auth.AccountStore.Create(); store.Save(account, "serviceId"); } partial void buttonReadDown(UIButton sender) { //キーチェーンからパスワードを取り出す var store = Xamarin.Auth.AccountStore.Create(); var account = store.FindAccountsForService("serviceId") .SingleOrDefault(); var encriptKey = account.Properties["encriptKey"]; Debug.WriteLine(encriptKey); //Realmデータベース設定に暗号キーを設定(暗号キーは16進文字列からbyte配列変換) var config = new RealmConfiguration("Mine.realm"); config.EncryptionKey = GetEncriptKeyByteArray(encriptKey); //暗号化してデータベースを開く var realm = Realm.GetInstance(config); //Realmデータベースのパスを出力 Debug.WriteLine(realm.Config.DatabasePath); //データベースのデータを読み込み var person = realm.All<Person>(); foreach (var p in person) { Debug.WriteLine(p.Name + ":" + p.Age.ToString()); } } //64ビットの暗号キーを生成(シングルトン) private byte[] CreateByteKey() { if (byteKey == null) { //64ビットの暗号キーを生成 var random = new Random(); byteKey = new Byte[64]; random.NextBytes(byteKey); return byteKey; } else { return byteKey; } } //バイト配列から16進文字列を生成する private string GetEcriptKeyString(byte[] key) { var encriptKey = BitConverter.ToString(key); encriptKey = encriptKey.Replace("-", string.Empty); return encriptKey; } //16進文字列からバイト配列を生成する private byte[] GetEncriptKeyByteArray(string encriptKey) { var bs = new List<byte>(); for (int i = 0; i < encriptKey.Length / 2; i++) { bs.Add(Convert.ToByte(encriptKey.Substring(i * 2, 2), 16)); } return bs.ToArray(); } protected ViewController(IntPtr handle) : base(handle) { } public override void ViewDidLoad() { base.ViewDidLoad(); } public override void DidReceiveMemoryWarning() { base.DidReceiveMemoryWarning(); } } }
おわりに
キーチェーンを使用したことにより若干複雑になってしまいましたが、実際の実装では必要になると思いこの形式にしました。
Realm データベースの暗号化、キーチェーンへの暗号キーの保存はなかなか情報がないため参考にしてみてください。