Active Recordはアプリケーションレベルの暗号化をサポートします。これは、暗号化する属性を宣言し、必要に応じて暗号化と復号化をシームレスに行うしくみです。暗号化の層は、データベース層とアプリケーション層の間に置かれます。アプリケーションがアクセスするのは暗号化されていないデータですが、データベースには暗号化されたデータが保存されます。

1 データをアプリケーションレベルで暗号化する理由

Active Record暗号化は、アプリケーション内の機密情報を保護するために存在します。典型的な機密情報の例は、個人を識別可能な情報です。既にデータベースを暗号化していてもアプリケーションレベルで暗号化したくなる理由は何でしょうか?

機密性の高い属性を暗号化することでただちに得られる実用的なメリットの1つは、セキュリティ層が追加されることです。たとえば、攻撃者がデータベースやデータベースのスナップショット、アプリケーションログにアクセスできたとしても、暗号化された情報は読めません。また、暗号化することで、開発者がうっかりユーザーの機密情報をアプリケーションログに出力してしまうことを防止できます。

しかしもっと重要なのは、Active Record暗号化を用いることで、アプリケーション内にある機密情報の構成要素をコードレベルで定義できることです。Active Record暗号化を利用すれば、アプリケーション内のデータアクセスやデータを実際に消費するサービスを細かく制御できるようになります。暗号化されたデータを保護する監査機能付きのRailsコンソールや、コントローラのparamsを自動フィルタする組み込みシステムなども検討できます。

2 基本的な利用法

2.1 セットアップ

最初に、Rails credentialsにキーをいくつか追加しておく必要があります。以下のようにbin/rails db:encryption:initを実行して、ランダムなキーセットを生成します。

$ bin/rails db:encryption:init
Add this entry to the credentials of the target environment:

active_record_encryption:
  primary_key: EGY8WhulUOXixybod7ZWwMIL68R9o5kC
  deterministic_key: aPA5XyALhf75NNnMzaspW7akTfZp0lPY
  key_derivation_salt: xEY0dt6TZcAMg52K7O84wYzkjvbA62Hz

生成される値は32バイト長です。これらを自分で生成する場合は、最低でも主キー用に12バイト(これはAESの32バイトのキー導出に用いられます)、ソルト(salt)用に20バイトが必要です。

2.2 暗号化属性の宣言

暗号化可能な属性はモデルレベルで定義します。これらの属性は、同名のカラムを用いる通常のActive Record属性です。

class Article < ApplicationRecord
  encrypts :title
end

このライブラリは、属性をデータベースに保存する前に透過的に暗号化し、取得時に復号化するようになります。

article = Article.create title: "すべて暗号化せよ!"
article.title # => "すべて暗号化せよ!"

しかし背後で実行されるSQLは以下のようになります。

INSERT INTO `articles` (`title`) VALUES ('{\"p\":\"n7J0/ol+a7DRMeaE\",\"h\":{\"iv\":\"DXZMDWUKfp3bg/Yu\",\"at\":\"X1/YjMHbHD4talgF9dt61A==\"}}')

値のほかにBase64エンコーディングとメタデータも保存されるので、暗号化を利用する場合はカラムの容量を余分に必要とします。組み込みのエンベロープ暗号化キープロバイダが使われる場合、最悪で250バイトほど余分に必要になると見積もれます。これは中大規模のtextカラムでは無視できる量ですが、255バイトのstringカラムではこれに応じて上限を増やしておく必要があります(推奨は510バイト)。

2.3 決定論的暗号化と非決定論的暗号化について

ActiveRecord暗号化では、デフォルトで非決定論的な(non-deterministic)暗号化を用います。ここで言う非決定論的とは、同じコンテンツを同じパスワードで暗号化しても、暗号化のたびに異なる暗号文が生成されるという意味です。非決定論的な暗号化手法によって、暗号解析の難易度を高めてデータベースへのクエリを不可能にすることで、セキュリティを向上させます。

deterministic:オプションを指定することで、初期化ベクトルを決定論的な手法で生成できるようになり、暗号化データへのクエリを効率よく行えるようになります。

class Author < ApplicationRecord
  encrypts :email, deterministic: true
end

Author.find_by_email("some@email.com") # You can query the model normally

データをクエリする必要が生じない限り、非決定論的な手法が推奨されます。

非決定論的モードのActive Recordでは、256ビットキーとランダムな初期化ベクトルを用いるAES-GCMが使われます。決定論的モードも同様にAES-GCMを用いますが、その初期化ベクトルは、キーと暗号化対象コンテンツのHMAC-SHA-256ダイジェストとして生成されます。

deterministic_keyを省略すると、決定論的暗号化を無効にできます。

3 機能

3.1 Action Text

Action Textの属性宣言にencrypted: trueを渡すことで、属性を暗号化できます。

class Message < ApplicationRecord
  has_rich_text :content, encrypted: true
end

Action Text属性に個別の暗号化オプションを渡すことについてはまだサポートされていません。グローバルな暗号化オプションに設定されている非決定的暗号化が用いられます。

3.2 フィクスチャ

config/environmentsのtest.rbに以下のオプションを追加すると、Railsのフィクスチャが自動的に暗号化されるようになります。

config.active_record.encryption.encrypt_fixtures = true

この機能を有効にすると、暗号化済み属性はモデルで定義されている暗号化設定に沿って暗号化されるようになります。

3.2.1 Action Textのフィクスチャ

Action Textのフィクスチャを暗号化するには、fixtures/action_text/encrypted_rich_texts.ymlに置く必要があります。

3.3 サポートされる型

active_record.encryptionは、暗号化の前に背後の型を用いて値をシリアライズしますが、型は文字列としてシリアライズ可能でなければなりませんserializedなどの構造化された型はそのまま利用可能です。

カスタム型をサポートする必要がある場合は、シリアライズ化属性の利用が推奨されます。シリアライズ化属性の宣言は、以下のように暗号化宣言より上の行に置いてください。

# 正しい
class Article < ApplicationRecord
  serialize :title, Title
  encrypts :title
end

# 誤り
class Article < ApplicationRecord
  encrypts :title
  serialize :title, Title
end

3.4 大文字小文字を区別しない場合

決定論的に暗号化されたデータへのクエリで大文字小文字を区別しないようにする必要が生じることがあります。この場合、大文字小文字を区別しないクエリをやりやすくするには2とおりの方法が考えられます。

1つは、以下のように暗号化属性を宣言するときにdowncase:オプションを指定することでコンテンツ暗号化の前に小文字に揃えておくことです。

class Person
  encrypts :email_address, deterministic: true, downcase: true
end

downcase:オプションを指定する場合は、元の大文字小文字の区別が失われます。大文字小文字の区別を失わずに、クエリでのみ大文字小文字を区別しないようにしたいこともあるでしょう。そのような場合は:ignore_caseオプションを指定できます。このオプションを利用する場合は、大文字小文字の区別を維持したコンテンツを保存するためのoriginal_<カラム名>というカラムを追加する必要があります。

class Label
  encrypts :name, deterministic: true, ignore_case: true # 大文字小文字を維持したコンテンツは`original_name`カラムに保存される
end

3.5 暗号化されていないデータのサポート

暗号化されていないデータを移行しやすくするため、このライブラリにはconfig.active_record.encryption.support_unencrypted_dataオプションも用意されています。このオプションをtrueにすると以下のようになります。

  • 実際には暗号化されていない暗号化済み属性を読み出してもエラーをraiseしなくなる
  • 決定論的に暗号化された属性を含むクエリに「クリアテキスト」バージョンの属性も含まれるようになり、暗号化の有無にかかわらずコンテンツを検索できるようになる。これを有効にするにはconfig.active_record.encryption.extend_queries = trueを設定する必要があります。

このオプションは、非暗号化データと暗号化済みデータの共存が避けられないので、あくまで過渡期の利用が目的です。デフォルトでは上記2つのオプションはどちらもfalseに設定されます。そしてこの設定は、あらゆるアプリケーションで推奨される目標でもあります。

3.6 以前の暗号化スキームのサポート

属性の暗号化プロパティを変更すれば既存のデータが破損します。たとえば、決定論的な属性を非決定的に変える場合、単にモデル内の宣言を変更すると、暗号化手法が異なるため、既存の暗号文を読み取れなくなってしまいます。

このような状況をサポートするため、以前の暗号化スキームを以下の2つのシナリオで利用される形で宣言できます。

  • ActiveRecord暗号化は、暗号化済みデータを読み取るときに現在の暗号化スキームが効かない場合は、以前の暗号化スキームで読み取りを試みる。
  • 決定論的に暗号化されたデータへのクエリでは、以前の暗号化スキームを用いた暗号化テキストも追加して、複数の暗号化スキームで暗号化されたデータのクエリをシームレスに行えるようにする。これを有効にするには、config.active_record.encryption.extend_queries = trueを設定しておかなければなりません。

以前の暗号化スキームは、以下のいずれかの方法で設定可能です。

  • グローバルに設定
  • 属性ごとに設定
3.6.1 以前の暗号化スキームをグローバルに設定する

以前の暗号化スキームは、config/application.rbで以下のようにprevious設定を用いてプロパティのリストとして追加可能です。

config.active_record.encryption.previous = [ { key_provider: MyOldKeyProvider.new } ]
3.6.2 以前の暗号化スキームを属性ごとに設定する

属性を宣言するときに以下のように:previousで指定します。

class Article
  encrypts :title, deterministic: true, previous: { deterministic: false }
end
3.6.3 暗号化スキームと決定論的な属性

以前の暗号化スキームを追加する場合、以下のように非決定論的/決定論的によって違いが生じます。

  • 非決定論的暗号化: 新しい情報は、常に最新の(すなわち現在の)暗号化スキームによって暗号化される
  • 決定論的暗号化: 新しい情報は、常にデフォルトで最も古い暗号化スキームによって暗号化される

決定的暗号化をあえて利用する場合は、暗号文を変えたくないのが普通です。この振る舞いはdeterministic: { fixed: false }で変更可能です。これにより、新しいデータを暗号化するときに最新の暗号化スキームが用いられるようになります。

3.7 一意性制約

一意性制約は、決定論的に暗号化されたデータでしか利用できません。

3.7.1 一意性バリデーション

一意性バリデーションは、config.active_record.encryption.extend_queries = trueによって拡張クエリが有効になっている限り、通常どおりサポートされます。

class Person
  validates :email_address, uniqueness: true
  encrypts :email_address, deterministic: true, downcase: true
end

これは、暗号化済みデータと非暗号化データを組み合わせた場合や、以前の暗号化スキームを設定した場合にも利用できます。

大文字小文字を区別しないようにしたい場合は、encrypts宣言で downcase:またはignore_case:を必ず指定すること。また、バリデーション内ではcase_sensitive:は機能しません。

3.7.2 一意インデックス

決定論的に暗号化されたカラムで一意インデックスをサポートするには、その暗号文が絶対に変更されないようにしておく必要があります。

決定論的な属性ではそのために、複数の暗号化スキームが設定されている場合はデフォルトでは常に最も古い暗号化スキームを利用するようになっています。そうでないと、属性の暗号化プロパティの変更を別の方法で防止しない限り、一意インデックスが効かなくなってしまいます。

class Person
  encrypts :email_address, deterministic: true
end

3.8 暗号化カラムをparamsでフィルタする

デフォルトでは、暗号化済みカラムはRailsのログで自動的にフィルタされますconfig/application.rbに以下を追加することで、この振る舞いを無効にできます。

config.active_record.encryption.add_to_filter_parameters = false

特定のカラムだけを自動フィルタの対象から外したい場合は、外したいカラムをconfig.active_record.encryption.excluded_from_filter_parametersに追加します。

3.9 エンコード

このライブラリは、非決定論的に暗号化された文字列値のエンコードを維持します。

エンコーディングは、暗号化済みペイロードとともに保存されるので、デフォルトでは強制的にUTF-8エンコーディングが使われます。すなわち、値が同じでもエンコードが異なれば暗号文も異なるものになります。これは、クエリや一意性制約を機能させるうえでは避けたいものなので、ライブラリが代わりに自動的に変換します。

決定論的暗号化でデフォルトのエンコードを指定するには、以下の設定を使います。

config.active_record.encryption.forced_encoding_for_deterministic_encryption = Encoding::US_ASCII

この振る舞いを無効にして常にエンコードを維持するには、以下の設定を使います。

config.active_record.encryption.forced_encoding_for_deterministic_encryption = nil

4 キーの管理

キープロバイダは、キー管理戦略を実装します。キープロバイダはグローバルに設定することも、属性ごとに指定することも可能です。

4.1 組み込みのキープロバイダ

4.1.1 DerivedSecretKeyProvider

DerivedSecretKeyProviderは、指定のパスワードからPBKDF2を用いて導出されるキーを提供するキープロバイダです。

config.active_record.encryption.key_provider = ActiveRecord::Encryption::DerivedSecretKeyProvider.new(["some passwords", "to derive keys from. ", "These should be in", "credentials"])

active_record.encryptionはデフォルトで、active_record.encryption.primary_keyで定義されているキーを用いるDerivedSecretKeyProviderを設定します。

4.1.2 EnvelopeEncryptionKeyProvider

EnvelopeEncryptionKeyProviderは、シンプルなエンベロープ暗号化戦略を実装します。

  • データ暗号化操作のたびにランダムなキーを生成する
  • データ自身のほかにデータキーも保存し、active_record.encryption.primary_key credentialで定義されている主キーを用いて暗号化される

以下をconfig/application.rbに追加することで、Active Recordでこのキープロバイダを使うよう設定できます。

config.active_record.encryption.key_provider = ActiveRecord::Encryption::EnvelopeEncryptionKeyProvider.new

他の組み込みのキープロバイダと同様に、active_record.encryption.primary_keyに主キーのリストを渡すことでキーローテーションスキームを実装できます。

4.2 カスタムのキープロバイダ

より高度なキー管理スキームを利用したい場合は、イニシャライザで以下のようにカスタムのキープロバイダを設定できます。

ActiveRecord::Encryption.key_provider = MyKeyProvider.new

キープロバイダは以下のインターフェイスを実装しなければなりません。

class MyKeyProvider
  def encryption_key
  end

  def decryption_keys(encrypted_message)
  end
end

2つのメソッドは、いずれもActiveRecord::Encryption::Keyオブジェクトを返します。

  • encryption_key: コンテンツの暗号化に使われたキーを返す
  • decryption keys: 指定のメッセージを復号化するのに使う可能性のあるキーのリストを返す

1つのキーには、メッセージと一緒に暗号化なしで保存される任意のタグを含められます。ActiveRecord::Encryption::Message#headersを使って、復号時にこれらの値を調べられます。

4.3 キープロバイダをモデルごとに指定する

key_provider:オプションで、キープロバイダをクラスごとに設定できます。

class Article < ApplicationRecord
  encrypts :summary, key_provider: ArticleKeyProvider.new
end

4.4 キーをモデルごとに指定する

key:オプションで、指定のキーをクラスごとに設定できます。

class Article < ApplicationRecord
  encrypts :summary, key: "some secret key for article summaries"
end

Active Recordは、データの暗号化や復号化に使うキーをこのキーで導出します。

4.5 キーのローテーション

active_record.encryptionでは、キーローテーションスキームの実装をサポートするキーのリストを利用できます。

  • 新しいコンテンツの暗号化には最下行のキーが用いられる
  • 復号化では、成功するまですべてのキーを試行する
active_record
  encryption:
    primary_key:
        - a1cc4d7b9f420e40a337b9e68c5ecec6 # 以前のキーは引き続き既存コンテンツを復号化する
        - bc17e7b413fd4720716a7633027f8cc4 # 新しいコンテンツを暗号化するアクティブなキー
    key_derivation_salt: a3226b97b3b2f8372d1fc6d497a0c0d3

これにより、「新しいキーの追加」「コンテンツの再暗号化」「古いキーの削除」を行ってキーのリストを短く保てるようになります。

キーローテーションは、決定論的暗号化では現在サポートされていません。

Active Record暗号化は、キーローテーション処理の自動管理機能をまだ提供していません(実装は可能ですが未実装の状態です)。

4.6 キー参照の保存

以下のようにactive_record.encryption.store_key_referencesを設定することで、active_record.encryptionが暗号化済みメッセージそのものに暗号化キーへの参照を保存するようになります。

config.active_record.encryption.store_key_references = true

この設定を有効にすると、システムがキーのリストを探索せずにキーを直接見つけられるようになり、復号のパフォーマンスが向上します。その代わり、暗号化データのサイズがやや肥大化します。

5 API

5.1 基本的なAPI

Active Record暗号化は宣言的に利用することを念頭に置いていますが、より高度なシナリオで使えるAPIも提供しています。

5.1.1 暗号化と復号化
article.encrypt # encrypt or re-encrypt all the encryptable attributes
article.decrypt # decrypt all the encryptable attributes
5.1.2 暗号文の読み出し
article.ciphertext_for(:title)
5.1.3 属性が暗号化されているかどうかのチェック
article.encrypted_attribute?(:title)

6 設定

6.1 設定オプション

Active Record暗号化のオプションは、config/application.rbで行うことも(ほとんどの場合このファイルに書きます)、config/environments/<環境名>.rbで特定の環境設定ファイルに設定することも可能です。

以下のように、設定オプションはすべてactive_record.encryption.configによって名前空間化されます。

config.active_record.encryption.key_provider = ActiveRecord::Encryption::EnvelopeEncryptionKeyProvider.new
config.active_record.encryption.store_key_references = true
config.active_record.encryption.extend_queries = true

利用可能な設定オプションは以下のとおりです。

キー
support_unencrypted_data true: 非暗号化データを通常どおり読み出せる。 false(デフォルト): 非暗号化データを読み出すとエラーになる。
extend_queries true: 決定論的に暗号化された属性を参照するクエリが、必要に応じて追加の値を含むように修正される。追加される値は非暗号化(support_unencrypted_datatrueの場合)または以前の暗号化スキームで暗号化される(previous:で指定された場合)。デフォルトはfalse
encrypt_fixtures true: フィクスチャ内の暗号化可能な属性が読み込み時に自動的に暗号化される。デフォルトはfalse
store_key_references true: 暗号化キーへの参照が暗号化済みメッセージのヘッダ内に保存され、キーが複数使われる場合の暗号化が高速になる。デフォルトはfalse
add_to_filter_parameters true: 暗号化された属性名が自動的にフィルタ対象paramsリストに追加され、ログに出力されなくなる。デフォルトはtrue
excluded_from_filter_parameters paramsのリストをフィルタしないよう設定する(add_to_filter_parameterstrueの場合)。デフォルトは[]
validate_column_size カラムのサイズに応じたバリデーションを追加する。圧縮が効きやすいはずのペイロードを用いる巨大な値を保存しないために推奨されている。デフォルトはtrue
primary_key rootデータ暗号化キーの導出に用いるキーまたはキーのリスト。キーの利用法はキープロバイダの設定によって異なる。active_record_encryption.primary_key credentialで設定するのが望ましい。
deterministic_key 決定論的暗号化で用いるキーまたはキーのリスト。active_record_encryption.deterministic_key credentialで設定するのが望ましい。
key_derivation_salt キー導出時に用いるソルト(salt)。active_record_encryption.key_derivation_salt credentialで設定するのが望ましい。
forced_encoding_for_deterministic_encryption 決定論的に暗号化された属性のデフォルトエンコーディング。このオプションをnilにするとエンコードの強制を無効化できる。デフォルトはEncoding::UTF_8

キーの保存場所には、Rails組み込みのcredentialサポートを用いることが推奨されます。設定プロパティを用いて手動で設定したい場合は、キーを誤ってコードと一緒にリポジトリにコミットしないようご注意ください(環境変数などを用いること)。

6.2 暗号化コンテキスト

暗号化コンテキストとは、ある時点に使われる暗号化コンポーネントを定義するものです。デフォルトではグローバルな設定に基づいた暗号化コンテキストが使われますが、特定の属性で用いるカスタムコンテキストや、コードの特定のブロックを実行するときのカスタムコンテキストを定義可能です。

暗号化コンテキストの設定メカニズムは柔軟ですが高度です。ほとんどのユーザーは気にする必要はないはずです。

暗号化コンテキストに登場する主なコンポーネントは以下のとおりです。

  • encryptor: データの暗号化や復号化に用いる内部APIを公開します。暗号化メッセージの作成とシリアライズのためにkey_providerとやりとりします。暗号化や復号化そのものはcypherで行われ、シリアライズはmessage_serializerで行われます。
  • cipher: 暗号化アルゴリズムそのもの(AES 256 GCM)
  • key_provider: 暗号化と復号化のキーを提供する
  • message_serializer: 暗号化されたペイロードをシリアライズおよびデシリアライズする(Message

独自のmessage_serializerを構築する場合は、任意のオブジェクトをデシリアライズすることのない安全なメカニズムを採用することが重要です。一般にサポートされているシナリオは、既存の非暗号化データを暗号化するときです。任意のオブジェクトがデシリアライズ可能だと、攻撃者がこれを利用して、暗号化が行われる前に改ざんしたペイロードを入力してリモートコード実行(RCE)を実行する可能性があります。つまり、独自のシリアライザでは Marshal、YAML.loadYAML.safe_loadにすること)、JSON.loadJSON.parse`にすること)の利用を避けるべきです。

6.2.1 グローバルな暗号化コンテキスト

グローバルな暗号化コンテキストはデフォルトで利用されます。他の設定プロパティと同様、config/application.rbや環境ごとの設定ファイルで以下のように設定可能です。

config.active_record.encryption.key_provider = ActiveRecord::Encryption::EnvelopeEncryptionKeyProvider.new
config.active_record_encryption.encryptor = MyEncryptor.new
6.2.2 属性ごとの暗号化コンテキスト

以下のように属性の宣言で暗号化コンテキストを渡すことで、暗号化コンテキストを上書きできます。

class Attribute
  encrypts :title, encryptor: MyAttributeEncryptor.new
end
6.2.3 特定のコードブロックを実行中の暗号化コンテキスト

ActiveRecord::Encryption.with_encryption_contextを使うと、指定のコードブロックで暗号化コンテキストを設定できます。

ActiveRecord::Encryption.with_encryption_context(encryptor: ActiveRecord::Encryption::NullEncryptor.new) do
  ...
end
6.2.4 Rails組み込みの暗号化コンテキスト
6.2.4.1 暗号化を無効にする

以下を用いると、暗号化を無効にしてコードを実行できます。

ActiveRecord::Encryption.without_encryption do
   ...
end

この場合、暗号化テキストを読み出すと暗号文のまま返され、saveしたコンテンツは暗号化なしで保存されることになります。

6.2.4.2 暗号化済みデータを保護する

以下を用いると、暗号化を無効にすると同時に、暗号化済みコンテンツが上書きされないようにコードを実行できます。

ActiveRecord::Encryption.protecting_encrypted_data do
   ...
end

これは、暗号化データを保護しつつ、任意のコードを実行したい場合に便利です(Railsコンソールなど)。

フィードバックについて

Railsガイドは GitHub の yasslab/railsguides.jp で管理・公開されております。本ガイドを読んで気になる文章や間違ったコードを見かけたら、気軽に Pull Request を出して頂けると嬉しいです。Pull Request の送り方については GitHub の README をご参照ください。

原著における間違いを見つけたら『Rails のドキュメントに貢献する』を参考にしながらぜひ Rails コミュニティに貢献してみてください 🛠💨✨

本ガイドの品質向上に向けて、皆さまのご協力が得られれば嬉しいです。

Railsガイド運営チーム (@RailsGuidesJP)

支援・協賛

Railsガイドは下記のサポーターから継続的な支援を受けています。支援・協賛にご興味あれば協賛プランから気軽にお問い合わせいただけると嬉しいです。

  1. Star
  2. このエントリーをはてなブックマークに追加