【Rails】Active Record暗号化を試してみた
はじめに
お疲れ様です。
おおくまです。
今回は、「【Rails】Active Record暗号化を試してみた」ということで、Active Record暗号化についてまとめてみました。
少しでも皆様の参考になりますと幸いです。
対象読者
注意点
環境
Active Record暗号化とは
Active Record暗号化とは、その名の通り、Active Recordを使用してデータベースに保存するデータを暗号化することです。
暗号化する属性を宣言することで、Active Recordが暗号化と復号をサポートしてくれます。
Active Record暗号化を使用する理由としては、データベースに保存されるデータが暗号化されるため、仮に攻撃者がデータベースにアクセスしたとしても、データを読み取ることができないため、データの安全性を高めることができます。
Active Record暗号化を試してみる
前提
今回は、Articleテーブルのbodyカラムを暗号化してみます。
以下のようなArticleテーブルを作成しました。
class Article < ApplicationRecord
validates :title, presence: true, length: { maximum: 255 }
validates :body, presence: true, length: { maximum: 65_535 }
end
create_table "articles", force: :cascade do |t|
t.string "title", null: false
t.string "body", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
暗号化の設定をする前に、Articleレコードを作成してみます。
docker compose exec app rails c
Loading development environment (Rails 7.0.8.6)
irb(main):001> Article.create(title:"title", body:"body")
TRANSACTION (0.1ms) BEGIN /*application:TestApp*/
Article Create (1.7ms) INSERT INTO `articles` (`title`, `body`, `created_at`, `updated_at`) VALUES ('title', 'body', '2024-12-22 11:47:02.622943', '2024-12-22 11:47:02.622943') /*application:TestApp*/
TRANSACTION (2.6ms) COMMIT /*application:TestApp*/
=>
#<Article:0x0000ffff65cf4620
id: 1,
title: "title",
body: "body",
created_at: Sun, 22 Dec 2024 20:47:02.622943000 JST +09:00,
updated_at: Sun, 22 Dec 2024 20:47:02.622943000 JST +09:00>
データベースでArticleレコードを確認してみます。
docker compose exec db mysql -u root -p
mysql> USE test_app_development;
Database changed
mysql> SELECT * FROM articles\G;
*************************** 1. row ***************************
id: 1
title: title
body: body
created_at: 2024-12-22 11:47:02.622943
updated_at: 2024-12-22 11:47:02.622943
1 row in set (0.00 sec)
Articleレコードが作成されており、値が暗号化されていないことが確認できました。
では、実際にArticleテーブルのbodyカラムを暗号化してみます。
Active Record暗号化の設定
まずは、Active Record暗号化を使用するための設定を行います。
docker compose exec app bin/rails db:encryption:init
Add this entry to the credentials of the target environment:
active_record_encryption:
primary_key: tVZdJ1FxwuzLMjS5yLB3cnGlJicXYIcR
deterministic_key: UTx3Az9dnwOav4RgHpfj3IhMDbn3kFi3
key_derivation_salt: ChRRKgQSvEdHF2u9Dn2Qpy3LKFmVgWpz
上記のコマンドを実行すると、Active Record暗号化で使用するためのキーが発行されます。
では、これらのキーをRails credentialsに追加します。
docker compose exec -e EDITOR="vim" app bin/rails credentials:edit
上記のコマンドを実行すると、vimでRails credentialsが開かれるので、キーを追加し、保存します。
docker compose exec app bin/rails credentials:show
# aws:
# access_key_id: 123
# secret_access_key: 345
# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.
secret_key_base: 21fa33728ddd1f36b6ea28bd9bb18e8c034ca0169ce63680baefa5a2f57786fbdfae6ef9a90f0348b57b347407091ee127807b8656cbbe73682b1692dc3ad633
active_record_encryption:
primary_key: tVZdJ1FxwuzLMjS5yLB3cnGlJicXYIcR
deterministic_key: UTx3Az9dnwOav4RgHpfj3IhMDbn3kFi3
key_derivation_salt: ChRRKgQSvEdHF2u9Dn2Qpy3LKFmVgWpz
上記のコマンドを実行し、Rails credentialsにキーが追加されていることが確認できればOKです。
次にArticleのモデルファイルで、暗号化するカラムを設定します。
今回は、bodyカラムを暗号化するため、以下のように設定します。
class Article < ApplicationRecord
+ encrypts :body
validates :title, presence: true, length: { maximum: 255 }
validates :body, presence: true, length: { maximum: 65_535 }
end
これで、Articleテーブルのbodyカラムが暗号化されるようになりました。
実際にArticleレコードを作成してみます。
暗号化されたデータを作成
docker compose exec app rails c
Loading development environment (Rails 7.0.8.6)
irb(main):001> Article.create(title:"title", body:"body")
TRANSACTION (0.2ms) BEGIN /*application:TestApp*/
Article Create (1.6ms) INSERT INTO `articles` (`title`, `body`, `created_at`, `updated_at`) VALUES ('title', '{\"p\":\"a1rcfg==\",\"h\":{\"iv\":\"CybdD1MAo/2fxlYl\",\"at\":\"UCbsKHRL8/8jmrw6snAYSQ==\"}}', '2024-12-22 11:50:58.009958', '2024-12-22 11:50:58.009958') /*application:TestApp*/
TRANSACTION (2.4ms) COMMIT /*application:TestApp*/
=>
#<Article:0x0000ffff79761640
id: 2,
title: "title",
body: "body",
created_at: Sun, 22 Dec 2024 20:50:58.009958000 JST +09:00,
updated_at: Sun, 22 Dec 2024 20:50:58.009958000 JST +09:00>
発行されたSQLが前回と異なることが確認できます。
では、データベースでArticleレコードを確認してみます。
docker compose exec db mysql -u root -p
mysql> USE test_app_development;
Database changed
mysql> SELECT * FROM articles\G;
*************************** 1. row ***************************
id: 1
title: title
body: body
created_at: 2024-12-22 11:47:02.622943
updated_at: 2024-12-22 11:47:02.622943
*************************** 2. row ***************************
id: 2
title: title
body: {"p":"a1rcfg==","h":{"iv":"CybdD1MAo/2fxlYl","at":"UCbsKHRL8/8jmrw6snAYSQ=="}}
created_at: 2024-12-22 11:50:58.009958
updated_at: 2024-12-22 11:50:58.009958
2 rows in set (0.00 sec)
データベースに保存されているbodyカラムが暗号化されていることが確認できました。
非暗号化データを取得する
テーブルを作成した段階で、暗号化するよう設定している場合は、特に問題ありませんが、もう既にレコードが存在している段階で、途中から暗号化の設定を行った場合は、非暗号化データと暗号化済みデータが混在することになります。
暗号化の設定を行っている状態で、非暗号化データを取得しようとすると、Active Recordが非暗号化データを復号しようとするため、エラーが発生します。
現状、ID=1のArticleレコードは非暗号化データ、ID=2のArticleレコードは暗号化済みデータとなっています。
試しに、ID=1のArticleレコードと、ID=2のArticleレコードをそれぞれ取得してみます。
docker compose exec app rails c
Loading development environment (Rails 7.0.8.6)
irb(main):001> Article.find(1)
Article Load (1.0ms) SELECT `articles`.* FROM `articles` WHERE `articles`.`id` = 1 LIMIT 1 /*application:TestApp*/
An error occurred when inspecting the object: #<ActiveRecord::Encryption::Errors::Decryption: ActiveRecord::Encryption::Errors::Decryption>
irb(main):002> Article.find(2)
Article Load (1.5ms) SELECT `articles`.* FROM `articles` WHERE `articles`.`id` = 2 LIMIT 1 /*application:TestApp*/
=>
#<Article:0x0000ffff658be880
id: 2,
title: "title",
body: "body",
created_at: Sun, 22 Dec 2024 20:50:58.009958000 JST +09:00,
updated_at: Sun, 22 Dec 2024 20:50:58.009958000 JST +09:00>
このように、暗号化されているデータを取得することはできますが、非暗号化データを取得しようとすると、Active Recordが非暗号化データを復号しようとするため、エラーが発生します。
そのため、Active Recordには、非暗号化データを取得してもエラーが発生しないようにするためのオプションが用意されています。
module TestApp
class Application < Rails::Application
+ config.active_record.encryption.support_unencrypted_data = true
end
end
このように記述することで、非暗号化データを取得してもエラーが発生しないようになります。
では、再度Articleレコードを取得してみます。
docker compose exec app rails c
Loading development environment (Rails 7.0.8.6)
irb(main):001> Article.find(1)
Article Load (0.4ms) SELECT `articles`.* FROM `articles` WHERE `articles`.`id` = 1 LIMIT 1 /*application:TestApp*/
=>
#<Article:0x0000ffff9315ca28
id: 1,
title: "title",
body: "body",
created_at: Sun, 22 Dec 2024 20:47:02.622943000 JST +09:00,
updated_at: Sun, 22 Dec 2024 20:47:02.622943000 JST +09:00>
irb(main):002> Article.find(2)
Article Load (1.2ms) SELECT `articles`.* FROM `articles` WHERE `articles`.`id` = 2 LIMIT 1 /*application:TestApp*/
=>
#<Article:0x0000ffff93885980
id: 2,
title: "title",
body: "body",
created_at: Sun, 22 Dec 2024 20:50:58.009958000 JST +09:00,
updated_at: Sun, 22 Dec 2024 20:50:58.009958000 JST +09:00>
無事、Articleレコードを取得することができました。
非暗号化データを暗号化する
現状、データベースには、以下のように非暗号化データと暗号化済みデータが混在しています。
docker compose exec db mysql -u root -p
mysql> USE test_app_development;
Database changed
mysql> SELECT * FROM articles\G;
*************************** 1. row ***************************
id: 1
title: title
body: body
created_at: 2024-12-22 11:47:02.622943
updated_at: 2024-12-22 11:47:02.622943
*************************** 2. row ***************************
id: 2
title: title
body: {"p":"a1rcfg==","h":{"iv":"CybdD1MAo/2fxlYl","at":"UCbsKHRL8/8jmrw6snAYSQ=="}}
created_at: 2024-12-22 11:50:58.009958
updated_at: 2024-12-22 11:50:58.009958
2 rows in set (0.00 sec)
非暗号化データを暗号化する場合は、以下のようにActive Recordを使用して暗号化することができます。
docker compose exec app rails c
Loading development environment (Rails 7.0.8.6)
irb(main):001> Article.all.map(&:encrypt)
Article Load (2.7ms) SELECT `articles`.* FROM `articles` /*application:TestApp*/
TRANSACTION (0.1ms) BEGIN /*application:TestApp*/
Article Update (1.1ms) UPDATE `articles` SET `articles`.`body` = '{\"p\":\"CNXG0g==\",\"h\":{\"iv\":\"ConRjMsP+GNfsD4F\",\"at\":\"XucI7X82OnjrfsrMoH5cnw==\"}}' WHERE `articles`.`id` = 1 /*application:TestApp*/
TRANSACTION (4.2ms) COMMIT /*application:TestApp*/
TRANSACTION (0.1ms) BEGIN /*application:TestApp*/
Article Update (0.4ms) UPDATE `articles` SET `articles`.`body` = '{\"p\":\"TFR5og==\",\"h\":{\"iv\":\"gSszdP/4VDBF7XFW\",\"at\":\"gQ2Cgw337rtLWDlTqs9xIA==\"}}' WHERE `articles`.`id` = 2 /*application:TestApp*/
TRANSACTION (1.3ms) COMMIT /*application:TestApp*/
=> [nil, nil]
これで、既存の非暗号化データが暗号化されました。
実際にデータベースでArticleレコードを確認してみます。
docker compose exec db mysql -u root -p
mysql> USE test_app_development;
Database changed
mysql> SELECT * FROM articles\G;
*************************** 1. row ***************************
id: 1
title: title
body: {"p":"CNXG0g==","h":{"iv":"ConRjMsP+GNfsD4F","at":"XucI7X82OnjrfsrMoH5cnw=="}}
created_at: 2024-12-22 11:47:02.622943
updated_at: 2024-12-22 11:47:02.622943
*************************** 2. row ***************************
id: 2
title: title
body: {"p":"TFR5og==","h":{"iv":"gSszdP/4VDBF7XFW","at":"gQ2Cgw337rtLWDlTqs9xIA=="}}
created_at: 2024-12-22 11:50:58.009958
updated_at: 2024-12-22 11:50:58.009958
2 rows in set (0.00 sec)
データベースに保存されているbodyカラムが暗号化されていることが確認できました。
まとめ
今回、初めてActive Record暗号化を試してみました。
意外と簡単に設定することができました。
今回は、Articleテーブルのbodyカラムを暗号化してみましたが、実務では、メールアドレスや住所などの個人情報を暗号化することが多いかと思います。
万が一、データベースに不正アクセスされた場合の保険として、Active Record暗号化を使用しても良いかもしれません。
最後まで読んでいただき、ありがとうございました!
参考文献
Discussion