7
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【実践編】MongoDBの検索可能暗号の使い方を解説!!

Last updated at Posted at 2022-06-09

@kenmaroです。
普段は主に秘密計算、準同型暗号などの記事について投稿しています
秘密計算に関連するまとめの記事に関しては以下をご覧ください。

勝手に秘密計算アドベントカレンダーについて

この記事は

の「2日目」の記事
としようかと思っています。
興味のある方はアドベントカレンダー参加してみませんか?
ご連絡お待ちしております。

概要

前回の記事では、MongoDBが検索可能暗号を正式にサポートし、ベータ版として提供を開始した、
という記事について少し解説しました。

今回は、このMongoDBが、検索可能暗号を正式にサポートし、ベータ版として提供を開始した、
という記事について解説を行いました。

おさらいになりますが、
MongoDB は言わずと知れたSaaS型のDBサービスです。
ドキュメント指向データベース、もしくはNoSQLとも言われるタイプのデータベースです。

今記事では、実際にMongoDBの検索可能暗号を使ってみる、
つまりドキュメントにあるクイックスタートを追ってみる

ことにチャレンジしてみようと思います。

忙しい人のために

  • MongoDBという世界最大規模のクラウド型DBが、検索可能暗号をサポートし、ベータ版として公開したよ

  • この記事では実際に検索可能暗号DBを使ってみるよ

  • AWS のKMSとの連携ができることを確認したよ

  • 暗号化したいフィールドに対して暗号鍵を生成してみたよ

  • データをインサートして暗号状態で保存されていることを確認できたよ

  • 暗号化されていないフィールドに対して一致クエリを投げると復号して返してくれることを確認できたよ

  • 暗号化されているフィールドに対して一致クエリを投げるとエラーになってハマったので一旦検証を終了したよ

  • 使ったコードはgithubに上げているよ

それでは、やっていきます。

初めに

前回の記事でお伝えしたように、
検索可能暗号DBでは外部の鍵マネジメントシステムの利用が必須となっています。

上を見る限り
AWS、Azure、GCPの三大クラウドプロバイダが利用できそうですが、
今回はAWSを使ってやってみようと思います。

というわけで、

ここをフォローすることになりそうです。

また、準備として

こちらから始めます。

準備 + エラー集

先ほどのRequirements に従って必要なものを揃えていきます。

  • Automatic Encryption Shared Library

暗号化に必要なライブラリのバイナリを取得し、展開します。
今回はtmp_libというフォルダに格納しています、
こちらを後続のプログラムからパス指定する必要があります。

mkdir tmp_lib && cd tmp_lib
wget https://downloads.mongodb.com/osx/mongo_crypt_shared_v1-macos-x86_64-enterprise-6.0.0-rc8.tgz?_ga=2.37189024.1258991149.1654659347-125648093.1654659347&_gac=1.150727364.1654660103.CjwKCAjw7vuUBhBUEiwAEdu2pBQ4IOlTXk-xf7_B_8edqZnsQkBERhlLtOKG2R_LyIXIhRr3gskXlRoCag4QAvD_BwE

tar -xzvf mongo_crypt_shared_v1-macos-x86_64-enterprise-6.0.0-rc8.tgz
  • MongoDB driver Compatible with Queryable Encryption

MongoDBのドライバーを取得します。

バージョンはきちんと従う

Atlas は6.0以上で(Dedicated) で立ち上げておく必要があります。
最初ケチってSharedでデプロイしたところ、バージョンが5系しか対応しておらず、時間を溶かしましたので、
Dedicated で6.0以上できちんとデプロイしましょう。
ちなみに、latest バージョンで立ち上げた場合も、コネクションエラーが起きてしまったため、6.0で立ち上げた方が無難かと思います。

チュートリアルコードに注意

こちらを結局はコピペすることになりますが、そのまま使うとエラーになりますので注意。

ReferenceError: keyVaultColl is not defined

これは
const keyVaultColl = keyVaultClient.db(eDB).collection(eKV);
が記載漏れしています。
npm install mongodb-client-encryption

gitレポジトリにある
package.json
に従い、
npm install 
しましょう。
"mongodb-client-encryption": "^2.2.0-alpha.0"
が必要です。
TypeError: A crypt_shared override path was specified

クリプトライブラリのパスは、
  const extraOptions = {
    cryptSharedLibPath: "/Users/xxx/Downloads/tmp_lib/lib/mongo_crypt_v1.dylib",
    cryptSharedRequired: true,
  };

というように指定します。

user権限のエラー

MongoServerError: user is not allowed to do action [dropDatabase] on [Cluster0.]

MongoDBコンソールのタブから、security --> user にadmin権限を付与して解決しました。
Screen Shot 2022-06-09 at 13.51.47.png

MongoServerError: BSON field 'create.clusteredIndex' is the wrong type 'object', expected types '[bool, long, int, decimal, double']

MongoDBのAtlasのバージョンによるエラーでした、6系にきちんとすると消えました。

コネクションエラー

MongoServerSelectionError: connect ECONNREFUSED 127.0.0.1:27020

MongoDBのAtlasのバージョンによるエラーでした、6系にきちんとすると消えました。

MongoDB X NodeJSに不慣れな人は簡単に勉強が必要

brew tap mongodb/brew
brew install mongodb-community
brew services start mongodb-community
brew install mongodb-atlas-cli

プログラムの解説

使用した

  • make_data_key.js
  • insert_encrypted_document.js

について言葉で少しだけ解説します。

make_data_key.js

は、名前の通り、

  • KMSのマスターキー
  • 暗号化したいフィールドのコンフィギュレーション
    から、フィールドを暗号化するための鍵を生成するプログラムです。
const eDB = "encryption";
const eKV = "__keyVault";
const keyVaultNamespace = `${eDB}.${eKV}`;
const secretDB = "Cluster0";
const secretCollection = "patients";

この記述は、
encryption.__keyVault
というコレクションに必要な鍵を生成し、

実際に格納する(暗号化対象の)データは
Cluster0.patients
というコレクションに格納することを表しています。

  const clientEnc = new ClientEncryption(keyVaultClient, {
    keyVaultNamespace: keyVaultNamespace,
    kmsProviders: kmsProviders,
  });
  const dek1 = await clientEnc.createDataKey(provider, {
    masterKey: masterKey,
    keyAltNames: ["dataKey1"],
  });
  const dek2 = await clientEnc.createDataKey(provider, {
    masterKey: masterKey,
    keyAltNames: ["dataKey2"],
  });
  const dek3 = await clientEnc.createDataKey(provider, {
    masterKey: masterKey,
    keyAltNames: ["dataKey3"],
  });
  const dek4 = await clientEnc.createDataKey(provider, {
    masterKey: masterKey,
    keyAltNames: ["dataKey4"],
  });

このように鍵を作り、

  const encryptedFieldsMap = {
    [`${secretDB}.${secretCollection}`]: {
      fields: [
        {
          keyId: dek1,
          path: "phoneNumber",
          bsonType: "string",
          queries: { queryType: "equality" },
        },
        {
          keyId: dek2,
          path: "medications",
          bsonType: "array",
        },
        {
          keyId: dek3,
          path: "patientRecord.ssn",
          bsonType: "string",
          queries: { queryType: "equality" },
        },
        {
          keyId: dek4,
          path: "patientRecord.billing",
          bsonType: "object",
        },
      ],
    },
  };

このように暗号化したいフィールドに対してそれぞれの鍵を割り振っています。
また、queriesという箇所で、equalityを指定することで、
このフィールドに対して一致検索をすることを明示しています。
これらがコンフィギュレーションになっています。

実行すると、以下のように鍵が生成されているはずです。(MongoDB Compass を使って確認しています。)

Screen Shot 2022-06-09 at 14.17.42.png

insert_encrypted_document.js

次に、このプログラムですが、
実際に暗号化対象のデータを一件インサートし、
そのインサートされたデータに対して

  • 鍵の存在を知らないクライアント(unencryptedClient)
  • 鍵の存在を知っているクライアント(encryptedClient)

の二つのクライアントからクエリを投げ、それぞれ

  • 暗号化されているデータ
  • 復号されたデータ

が返ってくることを示すプログラムです。

具体的には、

    await encryptedColl.insertOne({
      firstName: "Jon",
      lastName: "Doe",
      patientId: 12345678,
      address: "157 Electric Ave.",
      phoneNumber: "012-3456-7890",
      patientRecord: {
        ssn: "987-65-4320",
        billing: {
          type: "Visa",
          number: "4111111111111111",
        },
      },
      medications: ["Atorvastatin", "Levothyroxine"],
    });

により一件の患者のデータを挿入しています。
データを挿入すると実際にこのように暗号化されています。

Screen Shot 2022-06-09 at 14.17.59.png

このうち、make_data_key.js で指定した

  • phoneNumber
  • patientRecord.ssn
  • patientRecord.billing
  • medications

が暗号化の対象となっています。

さらに、

await encryptedColl.findOne({ firstName: "Jon" })
await encryptedColl.findOne({ phoneNumber: "012-3456-7890" })

などでクエリを送信することができます。
上はfirstNameという暗号化されていないフィールドに対してのクエリであり、
下はphoneNumberという暗号化されているフィールドに対してのクエリです。

クエリの結果は、それぞれ

Screen Shot 2022-06-09 at 14.20.00.png

Screen Shot 2022-06-09 at 14.20.33.png

encryptedClientを経由すると、確かに復号されてデータを確認できることがわかります。

検証結果

検証の結果、

  • AWSのKMSで作ったマスター鍵との連携
  • 暗号化したいフィールドの指定、コンフィギュレーション
  • データのインサート
  • インサートされたデータが暗号化されていることの確認
  • 暗号化されていないフィールドに対しての一致検索、結果が復号されていることの確認

ができましたが、肝心の

  • 暗号化されているフィールドに対しての一致検索

をしようとした時のエラーが解決できず、一旦終了という結果になりました。

使ってみた感想

暗号化されているフィールドに対して一致検索をする、
つまり検索可能暗号をつかって暗号状態でクエリを投げて動くことを確かめたかったのですが、
後に書くエラーにより実行できず、時間が溶けそうだったので一旦諦めました。
おそらく私の問題だとは思うので、次また時間を見つけてチャレンジしてみようと思います。

その他のKMSとの連携や、フィールドに対して鍵を生成するプロセス、
データのインサートなどは(比較的)スムーズにいき、使いやすいなと思いました。

MongoDBクライアントはNodeJSのみ現在サポートされているようで、
今まで使ったことがある人はスムーズに検証できそうです。
私はNodeJSは経験がありますが、MongoDBをNodeJSから使ったことは過去にあったかないかわからないくらいのレベルから検証したので、理解するまでに少し時間がかかりました。

クライアントのライブラリ、暗号化に使うライブラリなどはOSSになっており、
エラーが出た時もソースコードを読むことでデバッグができる形にはなっていたため、
開発者に対しては使いやすいなと感じました。

また、公式のGitレポジトリに載っているプログラムが普通に初歩的なエラーを吐いたので、
まだ機能としても準備段階ではあるのだなと思いました。
結果的にこのエラーはコードをよく読めば解決できるものではあったのですが、、。

まとめ

使ったコードはgithubにあげています。

今回は、前回の記事に引き続き
MongoDB 6.0.0 以降に搭載された「検索可能暗号」機能の
実運用についてチュートリアルコードを追ってみました。

一番やりたかった暗号フィールドに対しての一致検索ができなかったのがとても悔しいですが、
おそらく自分の理解が足りていないのが原因だと思うのでまたチャレンジして記事をアップデートしてみようと思います。

今回の記事に関しては機能に対する説明などはあまり書いていませんが、
前回の記事に関しては検索可能暗号について考察を書いているので、開発者以外の方はそちらを読んでいただければと思います。

MongoDBは個人的に好きなサービスなので、これからも追っていこうと思っています。

また、このようなセキュリティに関連したリサーチャーや、開発者を米国で募集されているようなので、
興味のある方はコンタクトをとってみてはいかがでしょうか、
トップの開発者2人のツイッターはこちらのようです。

Senny Kamara さん

Tarik Moataz さん
https://twitter.com/tarikmoataz

というわけで今回はこの辺で。

@kenmaro

7
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?