【Hyperledger Aries】Holder Agent(Wallet)のマスターキー管理とバックアップ&リストアを実装する

こんにちは。GMOグローバルサイン・ホールディングスCTO室で分散型IDの研究をしている開発者の神沼@t_kanumaです。

Holder Agent(Wallet)の運用においては、以下の2つが重要になると考えます。

  • マスターキー管理
  • バックアップ/リストア

この記事では、その一例として、弊社のringoがそれぞれに対し行った方式設計と実装について、デモ動画を交えながら共有したいと思います。

前提

これまでのHolder Agentに関する拙著群と同様に

  • Holder Agentの形態はAries Framework JavaScript(v0.3.3)を利用して作ったAndroidアプリです。
  • WalletはAgentと一体化しています、すなわちAndroidデバイスの中に、暗号化されたSQLiteとして実装されています。

Agentのソースコードリポジトリはこちらをご参照ください。

マスターキー管理

処理方式

WalletのマスターキーはWallet内のコンテンツを暗号化をするためのKEK(Key Encryption Key)になるわけですが、今回の管理の方式は以下の拙著で述べたそれと同じです。

【Hyperledger Indy/Aries】AFJでReact Native製Holder Agentを作りVCモデルを回す(中編)

掻い摘んで述べれば、Holderがアプリの初期設定で入力するPINコードとUUIDを元にパスフレーズを生成し、それをAndroid Keystore Systemを使い暗号化した上でSharedPreferencesに保管します。そしてアプリ起動時にパスフレーズをロードしてマスターキーを導出し、Walletと接続する仕組みです。

なお、今回はマスターキーのローテーションは実装していませんが、上記の記事にも書いてある通り、AFJにそのメソッドが用意されています。詳細は記事をご参照ください。

アプリ実装

  • 初期設定でのPINコードとUUIDを基にしたパスフレーズ生成と保管: SignUp.tsx
  • アプリ起動におけるPINコード認証からのWallet接続: SignIn.tsx

デモ

初回起動時のPINコード登録

起動2回目以降の認証

Walletのバックアップとリストア

処理方式

私はWebサービスへのログインパスワードの管理にAndroidアプリの1Passwordを利用しています。私の運用手法としては、クラウドではなくデバイス内にパスワードを保管し、バックアップファイルを定期的にGoogle Driveにアップしています。そして、スマートフォンの買い替えの際などに、バックアップファイルをインポートしています。

今回、ringoが実装した方式も以下の図のように、上記と同じです。
バックアップでは、Holderがリストア用パスフレーズを設定した上で、UIからWalletをローカルストレージにエクスポートします。リストアでは、HolderがUIからローカルストレージ上のバックアップファイルを選択しインポートします。

上記のマスターキー管理に記載した記事の中で述べている通り、AFJはIndy SDKを通してWallet(SQLite)を生成します。(正確に言うとv0.3までのAFJです。詳細はこちらをご参照ください)Indy SDKが上記の方式のため、そこに依存した形になっています。

Indy SDKのWalletエクスポート/インポートの設計はこちらのドキュメントに記述されています。

それによれば、エクスポート/インポート機能は以下の特徴を持つと書かれています。

  • Walletはエクスポート時に暗号化される。
  • パスフレーズからargon2で暗号鍵が生成される。
  • 暗号化のアルゴリズムは、ChaCha20-Poly1305-IETFである。
  • インポートはWalletが空の状態である時だけ、可能である。

Wallet自体は上記のマスターキーにより最初から暗号化されて存在します。この内容から読み取れることは、エクスポートの際に、暗号化されたWalletをさらに暗号化するのではないかと考えます。また、インポートに関する記述から、今回はインポート前に一度既存のWalletを削除する実装にしています。

アプリ実装

バックアップ

以下、Walletをバックアップするコードの一部です。

const exportConfig: WalletExportImportConfig = {
    path: RNFS.DownloadDirectoryPath + "/wallet.bak",
    key: "YourBackupPassphrase",
};

agent?.wallet.export(exportConfig);

AFJにWalletをバックアップするためのメソッドが用意されており、エクスポート先のファイルパスと、リストアで入力するパスフレーズをメソッドに渡すと、現在のWalletが出力されます。今回は上記のコードの通り、ファイルパスを固定にしています。詳しい実装は以下をご参照ください。

リストア

以下、Walletをファイルからリストアするコードの一部です。

// 既存のWalletを削除します。
await agent?.wallet.close();
await agent?.wallet.delete();

// keyは上記の"マスターキー管理"で述べた、マスターキーの導出元となる、PINコードとUUIDから作られるパスフレーズです。
const walletConfig: WalletConfig = {
    id: "rn-holder-wallet",
    key: "YourPassphraseForWallet",
};

// keyはバックアップ時に設定したパスフレーズです。
// backupFilePathは、HolderがUIから選択したバックアップファイルのパスです。
const importConfig: WalletExportImportConfig = {
    path: backupFilePath,
    key: "YourBackupPassphrase",
};

await agent?.wallet.import(walletConfig, importConfig);

詳しい実装は以下をご参照ください。

デモ

完遂したストーリーは以下の通りです。

  1. ringoのスマートフォンにて、AgentアプリのUIから、発行済みのVCを保有するWalletをバックアップする。(バックアップファイルはローカルストレージにエクスポートされる。)
  2. バックアップファイルを私に受け渡す。
  3. 私のスマートフォンにてAgentアプリをインストールし、UIからバックアップファイルをインポートする。発行済みのVCと、Proof提示履歴が存在することを確認する。
  4. 発行済みのVCをVerifierに提示して検証に成功する。

上記No.1のバックアップ

上記No.3のリストア

バックアップファイル選択前
選択中

“ファイルを選択する”ボタンを押下すると、デバイスのファイル一覧が表示されファイルを選択できます。私のデバイス内のプライベートな情報がどうしても写ってしまうため、ここは割愛しています。(また選択後、ファイルパスをUIに表示すればわかりやすかったのですが、できておりません。)

選択後

無事、バックアップとリストアが成功し、そこから発行済みVCの検証が通るところまでを確認できました。

以上でこの記事を終わります。
どなたかのお役に立てたならば幸いです。