본문으로 건너뛰기
김신건의 로그

[Rails] Active Record Encryption

· 수정 · 📖 약 1분 · 578자/단어 #rails #encryption #security #active-record
rails encryption, encrypts attribute, deterministic encryption, rails 7 encryption

정의

Active Record Encryption (Rails 7.0+)은 모델 attribute 자동 암호화. 키 관리, 검색(deterministic), 마이그레이션 등 내장. 비밀번호는 has_secure_password (bcrypt), 일반 민감 데이터는 encrypts.

초기 설정

bin/rails db:encryption:init

출력된 키를 credentials에 저장:

# config/credentials.yml.enc (bin/rails credentials:edit)
active_record_encryption:
  primary_key: ...
  deterministic_key: ...
  key_derivation_salt: ...

또는 환경변수:

config.active_record.encryption.primary_key = ENV["AR_PRIMARY_KEY"]
config.active_record.encryption.deterministic_key = ENV["AR_DETERMINISTIC_KEY"]
config.active_record.encryption.key_derivation_salt = ENV["AR_SALT"]

사용

class User < ApplicationRecord
  encrypts :ssn
  encrypts :api_key
  encrypts :credit_card, deterministic: true    # 검색 가능
end

user = User.create(ssn: "123-45-6789")
user.ssn    # "123-45-6789" (자동 복호화)
User.where(ssn: "123-45-6789")    # ERROR (non-deterministic)
User.where(credit_card: "...")     # OK (deterministic)

DB에는 암호화된 값 저장.

Deterministic vs Non-deterministic

옵션검색 가능보안
Default (random IV)X강함 (같은 평문도 다른 ciphertext)
deterministic: trueO약함 (같은 평문 → 같은 ciphertext)

가능하면 non-deterministic. 검색이 필수일 때만 deterministic.

Downcase

encrypts :email, deterministic: true, downcase: true

검색 시 case-insensitive. User.where(email: "Alice@Example.com") 도 매치.

previous 키 (회전)

키 회전 시 옛 키로 복호화 가능:

class User < ApplicationRecord
  encrypts :ssn, previous: [
    { key: ENV["AR_OLD_PRIMARY_KEY"] },
  ]
end

새 키로 저장, 옛 키도 읽기. 데이터를 점진적으로 새 키로 마이그레이션:

bin/rails db:encryption:rotate

검증

class User < ApplicationRecord
  encrypts :ssn
  validates :ssn, presence: true, format: { with: /\A\d{3}-\d{2}-\d{4}\z/ }
end

검증은 평문에 대해 작동.

인덱스

add_index :users, :credit_card    # deterministic 컬럼만 의미 있음

non-deterministic은 인덱스 무용 (매번 다른 값).

자주 보는 패턴

개인정보

class Customer < ApplicationRecord
  encrypts :ssn
  encrypts :birthday
  encrypts :phone, deterministic: true    # 검색 필요
end

API 키

class Integration < ApplicationRecord
  encrypts :api_key
  encrypts :api_secret
end

외부 토큰

class User < ApplicationRecord
  encrypts :oauth_access_token
  encrypts :oauth_refresh_token
end

마이그레이션 (기존 데이터)

# 마이그레이션
class EncryptUserSsn < ActiveRecord::Migration[7.0]
  def up
    User.find_each do |user|
      user.ssn = user.read_attribute(:ssn)    # 평문 읽기
      user.save!    # 암호화 저장
    end
  end
end

또는 별도 작업으로 점진적 변환.

함정

1. 인덱스 컬럼 길이

encrypts :name    # 암호화 후 더 김

암호화된 값은 평문보다 큼 (base64). VARCHAR(255) → 부족할 수 있음. TEXT 권장 또는 확인.

2. 비교

user.ssn == "123-..."         # OK (메모리에서 복호화)
User.where(ssn: "123-...")    # deterministic만 가능

3. 검색 + 정렬

deterministic이라도 LIKE 검색은 의미 없음 (암호화된 substring 매치 X).

User.where("ssn LIKE ?", "123%")    # 동작 안 함

검색 + 암호화는 별도 검색 인덱스 (Searchable encryption) 또는 별 필드.

4. Rails 콘솔에서 평문 노출

User.first.ssn    # 평문 출력!

inspect도. 필요 시 attr_redact (커스텀 helper).

5. key 노출

config/credentials.yml.enc는 git OK (암호화). master.key는 절대 X. 노출 시 즉시 회전.

비밀번호는 다름

class User < ApplicationRecord
  has_secure_password    # bcrypt, irreversible
end

비밀번호는 hash (해시) - 복호화 못 함. encrypts는 복호화 가능. 비밀번호엔 has_secure_password, 일반 민감 데이터엔 encrypts.

Audit / 로그 보호

# config/initializers/filter_parameter_logging.rb
Rails.application.config.filter_parameters += [
  :ssn, :credit_card, :api_key,
]

로그에 [FILTERED] 표시. 암호화 + 로그 필터 둘 다.

외부 KMS

config.active_record.encryption.key_provider = MyKmsKeyProvider.new

AWS KMS, Vault 등 통합. 키 회전, 권한 관리 위임.

대안

솔루션특징
Active Record EncryptionRails 표준 (7.0+)
attr_encrypted옛 표준 (gem)
lockbox활발 (gem)
PG pgcryptoDB 레벨
어플리케이션 레벨 (Vault, KMS)강력

Rails 7+ 신규 프로젝트는 내장 Encryption.

보안 모범

  • 가능한 한 적게 암호화 (성능 + 복잡도)
  • 필드 단위 권한 (admin만 복호화)
  • 키 회전 정기
  • 백업도 암호화
  • 로그/응답에 비밀 X

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기