あるSEのつぶやき・改

ITやシステム開発などの技術に関する話題を、取り上げたりしています。

Xamarin.iOSで暗号化したRealmデータベースを使用する方法

はじめに

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 データベースの暗号化、キーチェーンへの暗号キーの保存はなかなか情報がないため参考にしてみてください。