Skip to content

Active Record Encryptionを使った機密データの暗号化

はじめに

Active Record Encryptionは、Rails 7で導入された機能で、アプリケーションレベルでデータベースに保存されるデータを透明に暗号化できます。機密性の高いPII(個人識別情報)やクレジットカード情報などを安全に保存するための強力なツールです。

なぜActive Record Encryptionが必要なのか

ruby
# 従来の問題点
class User < ApplicationRecord
  # このデータはプレーンテキストでDBに保存される
  # email: "user@example.com"
  # ssn: "123-45-6789"
  # credit_card: "4111-1111-1111-1111"
end

# データベースを直接見た場合
# SELECT email, ssn FROM users;
# user@example.com | 123-45-6789

リスク:

  • データベースダンプの漏洩
  • 開発者による不適切なアクセス
  • ログファイルへの機密データ出力
  • バックアップファイルの管理不備

基本的な設定

1. 暗号化の有効化

ruby
# config/application.rb
config.active_record.encryption.support_unencrypted_data = true
config.active_record.encryption.extend_queries = true

# config/credentials.yml.enc に暗号化キーを追加
# rails credentials:edit
active_record_encryption:
  primary_key: your_primary_key_here
  deterministic_key: your_deterministic_key_here
  key_derivation_salt: your_salt_here

2. 暗号化の初期化

ruby
# config/initializers/encryption.rb
Rails.application.configure do
  config.active_record.encryption.primary_key = Rails.application.credentials.active_record_encryption[:primary_key]
  config.active_record.encryption.deterministic_key = Rails.application.credentials.active_record_encryption[:deterministic_key]
  config.active_record.encryption.key_derivation_salt = Rails.application.credentials.active_record_encryption[:key_derivation_salt]
end

暗号化の実装

1. 基本的な暗号化

ruby
class User < ApplicationRecord
  # 基本的な暗号化
  encrypts :email
  encrypts :phone_number
  encrypts :ssn
  
  # 複数の属性を一度に暗号化
  encrypts :credit_card_number, :bank_account_number
end

# 使用例
user = User.create!(
  name: "John Doe",
  email: "john@example.com",  # 暗号化される
  ssn: "123-45-6789"         # 暗号化される
)

puts user.email  # => "john@example.com" (復号化されて表示)

2. 決定論的暗号化

ruby
class User < ApplicationRecord
  # 検索可能な暗号化
  encrypts :email, deterministic: true
  
  # 通常の暗号化(検索不可)
  encrypts :ssn
end

# 決定論的暗号化では検索が可能
User.where(email: "john@example.com")  # 動作する
User.where(ssn: "123-45-6789")         # 動作しない(エラー)

3. 大文字小文字を無視した暗号化

ruby
class User < ApplicationRecord
  encrypts :email, deterministic: true, downcase: true
  encrypts :username, deterministic: true, ignore_case: true
end

# 使用例
user = User.create!(email: "John@Example.COM")

# 検索時に大文字小文字が自動的に処理される
User.find_by(email: "john@example.com")     # 見つかる
User.find_by(email: "JOHN@EXAMPLE.COM")     # 見つかる

高度な暗号化設定

1. カスタム暗号化キー

ruby
class PaymentInfo < ApplicationRecord
  # 支払い情報専用のカスタムキー
  encrypts :credit_card_number, key: :payment_key
  encrypts :cvv, key: :payment_key
  
  # キーの定義
  def self.payment_key
    Rails.application.credentials.payment_encryption_key
  end
end

2. コンテキストベースの暗号化

ruby
class Document < ApplicationRecord
  encrypts :content, context: -> { "document_#{id}" }
  
  # 異なるコンテキストで暗号化
  encrypts :metadata, context: -> { "metadata_#{organization_id}" }
end

3. 暗号化の圧縮

ruby
class LargeData < ApplicationRecord
  # 大きなデータの暗号化時に圧縮を適用
  encrypts :large_text, compress: true
  encrypts :json_data, compress: true
end

データ移行の戦略

1. 既存データの暗号化

ruby
# db/migrate/encrypt_existing_user_data.rb
class EncryptExistingUserData < ActiveRecord::Migration[7.0]
  def up
    User.find_each do |user|
      # 暗号化を一時的に無効にして既存データを取得
      user.class.without_encryption do
        email = user.email_before_type_cast
        ssn = user.ssn_before_type_cast
        
        # 暗号化を有効にして保存
        user.update!(email: email, ssn: ssn)
      end
    end
  end
  
  def down
    # 必要に応じて復号化処理
  end
end

2. 段階的な暗号化移行

ruby
class User < ApplicationRecord
  # 移行期間中は暗号化されていないデータも読み取り可能
  encrypts :email, support_unencrypted_data: true
  
  # 移行完了後にこのオプションを削除
end

# 移行の確認
def check_encryption_migration
  unencrypted_count = User.where.not(email: nil).count do |user|
    user.class.without_encryption { user.email_before_type_cast == user.email }
  end
  
  puts "Unencrypted records: #{unencrypted_count}"
end

パフォーマンスの考慮事項

1. インデックスの最適化

ruby
class User < ApplicationRecord
  encrypts :email, deterministic: true
  encrypts :ssn  # 非決定論的
  
  # 決定論的暗号化されたフィールドのみインデックス作成可能
  add_index :users, :email
  # add_index :users, :ssn  # これは無効
end

2. クエリのパフォーマンス

ruby
# 効率的なクエリ
class UserSearchService
  def self.find_by_email(email)
    # 決定論的暗号化により高速検索が可能
    User.where(email: email).first
  end
  
  def self.search_by_name_and_email(name, email)
    # 複合検索の最適化
    User.where(name: name, email: email)
  end
  
  # 非効率なクエリの例
  def self.search_ssn_like(pattern)
    # SSNは非決定論的暗号化のため、LIKE検索は不可能
    # 全レコードを復号化する必要がある(非常に遅い)
    User.all.select { |user| user.ssn&.include?(pattern) }
  end
end

3. キャッシュ戦略

ruby
class User < ApplicationRecord
  encrypts :email, deterministic: true
  encrypts :profile_data
  
  # 復号化済みデータのキャッシュ
  def cached_profile_data
    @cached_profile_data ||= JSON.parse(profile_data)
  end
  
  # キャッシュの無効化
  after_save :clear_profile_cache
  
  private
  
  def clear_profile_cache
    @cached_profile_data = nil
  end
end

セキュリティのベストプラクティス

1. キーローテーション

ruby
# config/initializers/encryption.rb
Rails.application.configure do
  # 複数のキーを設定してローテーションを可能にする
  config.active_record.encryption.key_provider = ActiveRecord::Encryption::EnvelopeEncryptionKeyProvider.new
  
  # 古いキーと新しいキーの設定
  config.active_record.encryption.primary_key = [
    Rails.application.credentials.encryption_key_v2,  # 新しいキー
    Rails.application.credentials.encryption_key_v1   # 古いキー
  ]
end

# キーローテーションの実行
def rotate_encryption_keys
  User.find_each do |user|
    # 古いキーで復号化し、新しいキーで再暗号化
    user.touch  # saved時に最新のキーで再暗号化される
  end
end

2. 監査ログ

ruby
class User < ApplicationRecord
  encrypts :email, deterministic: true
  encrypts :ssn
  
  # 暗号化データへのアクセスをログ記録
  after_find :log_access
  before_save :log_modification
  
  private
  
  def log_access
    if ssn_changed? || email_changed?
      AuditLogger.log("Encrypted data accessed", {
        user_id: id,
        fields: changes.keys,
        accessor: Current.user&.id
      })
    end
  end
  
  def log_modification
    if will_save_change_to_ssn? || will_save_change_to_email?
      AuditLogger.log("Encrypted data modified", {
        user_id: id,
        fields: changed_attributes.keys,
        modifier: Current.user&.id
      })
    end
  end
end

3. 環境別の設定

ruby
# config/environments/development.rb
config.active_record.encryption.support_unencrypted_data = true
config.active_record.encryption.extend_queries = true

# config/environments/test.rb
config.active_record.encryption.support_unencrypted_data = true
config.active_record.encryption.extend_queries = false

# config/environments/production.rb
config.active_record.encryption.support_unencrypted_data = false
config.active_record.encryption.extend_queries = true

トラブルシューティング

1. よくある問題と解決法

ruby
# 問題: 暗号化データが読み取れない
# 解決: キーの確認
def debug_encryption_keys
  puts "Primary key present: #{Rails.application.config.active_record.encryption.primary_key.present?}"
  puts "Deterministic key present: #{Rails.application.config.active_record.encryption.deterministic_key.present?}"
end

# 問題: 検索ができない
# 解決: 決定論的暗号化の確認
class User < ApplicationRecord
  # 検索したいフィールドは deterministic: true にする
  encrypts :email, deterministic: true  # 検索可能
  encrypts :ssn                         # 検索不可
end

# 問題: パフォーマンスが悪い
# 解決: クエリの最適化
def optimized_user_search(email)
  # 効率的:決定論的暗号化フィールドでの検索
  User.where(email: email)
  
  # 非効率:暗号化フィールドでのLIKE検索
  # User.where("email LIKE ?", "%#{email}%")  # 避ける
end

2. データ整合性の確認

ruby
class EncryptionIntegrityChecker
  def self.check_user_data
    User.find_each do |user|
      begin
        # 暗号化データが正常に復号化できるかテスト
        user.email if user.email.present?
        user.ssn if user.ssn.present?
      rescue ActiveRecord::Encryption::Errors::Decryption => e
        puts "Decryption error for User #{user.id}: #{e.message}"
      end
    end
  end
end

まとめ

Active Record Encryptionは、機密データを安全に保存するための強力な機能です。適切に実装することで、データの機密性を保ちながら、アプリケーションの使いやすさを維持できます。

重要なポイント:

  • 検索が必要なフィールドは決定論的暗号化を使用
  • パフォーマンスを考慮したクエリ設計
  • 定期的なキーローテーション
  • 適切な監査ログの実装

導入時は段階的に進め、既存データの移行計画を慎重に立てることが成功の鍵となります。

AI が自動生成した技術記事をまとめたテックブログ