Active Record는 애플리케이션 레벨 암호화를 지원합니다. 이는 암호화할 속성을 선언하고 필요할 때 자동으로 암호화 및 복호화하는 방식으로 동작합니다. 암호화 계층은 데이터베이스와 애플리케이션 사이에 위치합니다. 애플리케이션은 암호화되지 않은 데이터에 접근하지만, 데이터베이스에는 암호화된 상태로 저장됩니다.
1 왜 애플리케이션 레벨에서 데이터를 암호화하나요?
Active Record Encryption은 애플리케이션의 민감한 정보를 보호하기 위해 존재합니다. 대표적인 예시로 사용자의 개인 식별 정보가 있습니다. 하지만 이미 데이터베이스를 저장 시점에 암호화하고 있다면 왜 애플리케이션 레벨의 암호화가 필요할까요?
즉각적인 실용적 이점으로, 민감한 속성을 암호화하면 추가적인 보안 계층이 생깁니다. 예를 들어, 공격자가 데이터베이스나 그 스냅샷, 또는 애플리케이션 로그에 접근했다 하더라도 암호화된 정보를 이해할 수 없게 됩니다. 또한, 암호화는 개발자가 실수로 애플리케이션 로그에 사용자의 민감한 데이터를 노출시키는 것을 방지할 수 있습니다.
하지만 더 중요한 것은, Active Record Encryption을 사용함으로써 코드 레벨에서 애플리케이션의 민감한 정보가 무엇인지 정의할 수 있다는 점입니다. Active Record Encryption은 애플리케이션과 애플리케이션의 데이터를 소비하는 서비스에서 데이터 접근을 세밀하게 제어할 수 있게 해줍니다. 예를 들어, 암호화된 데이터를 보호하는 감사 가능한 Rails 콘솔이나 컨트롤러 파라미터를 자동으로 필터링하는 내장 시스템을 살펴보세요.
2 기본 사용법
이 가이드는 Active Storage Overview를 읽었다는 가정하에 작성되었습니다.
2.1 Attaching Files to Records
파일을 record에 첨부하려면 has_one_attached
또는 has_many_attached
메소드 중 하나를 model 클래스에서 호출하세요.
class User < ApplicationRecord
has_one_attached :avatar
end
하나의 파일을 첨부하기 위해 has_one_attached
메소드를 사용하세요.
class Message < ApplicationRecord
has_many_attached :images
end
여러 파일을 첨부하기 위해 has_many_attached
메소드를 사용하세요.
2.2 셋업
랜덤한 키 세트를 생성하려면 bin/rails db:encryption:init
을 실행하세요:
$ bin/rails db:encryption:init
대상 environment의 credentials에 다음 항목을 추가하세요:
active_record_encryption:
primary_key: EGY8WhulUOXixybod7ZWwMIL68R9o5kC
deterministic_key: aPA5XyALhf75NNnMzaspW7akTfZp0lPY
key_derivation_salt: xEY0dt6TZcAMg52K7O84wYzkjvbA62Hz
이 값들은 생성된 값들을 기존 Rails credentials에 복사하여 붙여넣기 함으로써 저장할 수 있습니다. 또는 환경 변수와 같은 다른 소스로부터 이러한 값들을 구성할 수 있습니다:
config.active_record.encryption.primary_key = ENV["ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY"]
config.active_record.encryption.deterministic_key = ENV["ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY"]
config.active_record.encryption.key_derivation_salt = ENV["ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT"]
환경 변수를 통해 암호화 키를 전달하는 것이 좋습니다. Rails를 실행할 때 이러한 변수들을 설정해야 합니다.
생성된 값들은 32바이트 길이입니다. 직접 생성하는 경우, 사용해야 하는 최소 길이는 primary key의 경우 12바이트(이는 AES 32바이트 키를 생성하는데 사용됨)이며 salt의 경우 20바이트입니다.
2.3 Encrypted Attributes 선언
Encryptable attributes는 모델 레벨에서 정의됩니다. 이들은 동일한 이름의 컬럼으로 지원되는 일반적인 Active Record attributes입니다.
class Article < ApplicationRecord
encrypts :title
end
이 라이브러리는 데이터베이스에 저장하기 전에 이러한 attribute들을 자동으로 암호화하고, 조회할 때 복호화합니다:
article = Article.create title: "Encrypt it all!"
article.title # => "Encrypt it all!"
하지만 내부적으로는 실행되는 SQL이 다음과 같습니다:
INSERT INTO `articles` (`title`) VALUES ('{\"p\":\"n7J0/ol+a7DRMeaE\",\"h\":{\"iv\":\"DXZMDWUKfp3bg/Yu\",\"at\":\"X1/YjMHbHD4talgF9dt61A==\"}}')
2.3.1 중요: Storage와 Column Size에 대해
암호화는 Base64 인코딩과 암호화된 payload와 함께 저장되는 메타데이터로 인해 추가 공간이 필요합니다. 기본 제공되는 envelope encryption key provider를 사용할 때, 최악의 경우 오버헤드를 약 255바이트로 예상할 수 있습니다. 이 오버헤드는 더 큰 크기에서는 무시할 만한 수준입니다. 희석되기 때문만이 아니라 라이브러리가 기본적으로 압축을 사용하여 더 큰 payload의 경우 암호화되지 않은 버전에 비해 최대 30%의 저장 공간을 절약할 수 있기 때문입니다.
string 컬럼 크기에 대한 중요한 고려사항이 있습니다: 현대 데이터베이스에서 컬럼 크기는 바이트 수가 아닌 문자 수를 결정합니다. 예를 들어, UTF-8에서는 각 문자가 최대 4바이트를 차지할 수 있으므로, UTF-8을 사용하는 데이터베이스의 컬럼은 바이트 수 측면에서 크기의 최대 4배까지 저장할 수 있습니다. 암호화된 payload는 Base64로 직렬화된 이진 문자열이므로 일반 string
컬럼에 저장될 수 있습니다. ASCII 바이트 시퀀스이기 때문에 암호화된 컬럼은 암호화되지 않은 버전 크기의 최대 4배까지 차지할 수 있습니다. 따라서 데이터베이스에 저장된 바이트가 동일하더라도 컬럼은 4배 더 커야 합니다.
실제로 이는 다음을 의미합니다:
- 서양 알파벳(대부분 ASCII 문자)으로 작성된 짧은 텍스트를 암호화할 때는 컬럼 크기를 정의할 때 255바이트의 추가 오버헤드를 고려해야 합니다.
- 키릴 문자와 같은 비서양 알파벳으로 작성된 짧은 텍스트를 암호화할 때는 컬럼 크기를 4배로 해야 합니다. 저장 오버헤드는 최대 255바이트라는 점에 유의하세요.
- 긴 텍스트를 암호화할 때는 컬럼 크기 문제를 무시할 수 있습니다.
몇 가지 예시:
암호화할 콘텐츠 | 원래 컬럼 크기 | 권장 암호화 컬럼 크기 | 저장 오버헤드(최악의 경우) |
---|---|---|---|
이메일 주소 | string(255) | string(510) | 255바이트 |
짧은 이모지 시퀀스 | string(255) | string(1020) | 255바이트 |
비서양 알파벳으로 작성된 텍스트 요약 | string(500) | string(2000) | 255바이트 |
임의의 긴 텍스트 | text | text | 무시할만함 |
2.4 Deterministic 및 Non-deterministic Encryption
기본적으로 Active Record Encryption은 non-deterministic 방식으로 암호화를 수행합니다. 이 맥락에서 non-deterministic이란 동일한 내용을 동일한 비밀번호로 두 번 암호화하면 서로 다른 암호문이 생성된다는 것을 의미합니다. 이 접근 방식은 암호문의 암호 분석을 더 어렵게 만들고 데이터베이스 쿼리를 불가능하게 만듦으로써 보안을 향상시킵니다.
deterministic:
옵션을 사용하면 초기화 벡터를 deterministic 방식으로 생성할 수 있어, 암호화된 데이터를 쿼리할 수 있게 됩니다.
class Author < ApplicationRecord
encrypts :email, deterministic: true
end
Author.find_by_email("some@email.com") # 일반적으로 모델을 쿼리할 수 있습니다
데이터를 쿼리할 필요가 없다면 non-deterministic 접근 방식이 권장됩니다.
non-deterministic 모드에서 Active Record는 256비트 키와 랜덤 초기화 벡터를 사용하는 AES-GCM을 사용합니다. deterministic 모드에서도 AES-GCM을 사용하지만, 초기화 벡터는 키와 암호화할 내용의 HMAC-SHA-256 다이제스트로 생성됩니다.
deterministic_key
를 생략하면 deterministic 암호화를 비활성화할 수 있습니다.
3 Features
3.1 Action Text
encrypted: true
를 선언부에 전달하여 Action Text 속성을 암호화할 수 있습니다.
class Message < ApplicationRecord
has_rich_text :content, encrypted: true
end
Action Text 속성에 개별적인 encryption 옵션을 전달하는 것은 아직 지원되지 않습니다. 구성된 전역 encryption 옵션을 사용한 비결정적 encryption이 사용됩니다.
3.2 Fixtures
test.rb에 다음 옵션을 추가하여 Rails fixture를 자동으로 암호화할 수 있습니다:
config.active_record.encryption.encrypt_fixtures = true
fixture를 암호화할지 여부를 지정합니다. test 환경에서만 적용됩니다. 기본값은 false
입니다.
enabled되면 모델에 정의된 암호화 설정에 따라 암호화 가능한 모든 속성이 암호화됩니다.
3.2.1 Action Text Fixtures
Action Text fixtures를 암호화하려면 fixtures/action_text/encrypted_rich_texts.yml
에 배치해야 합니다.
3.3 지원되는 타입
active_record.encryption
은 값을 암호화하기 전에 기본 타입을 사용하여 직렬화하지만, 커스텀 message_serializer
를 사용하지 않는 한 문자열로 직렬화 가능해야 합니다. serialized
와 같은 구조적 타입은 기본적으로 지원됩니다.
커스텀 타입을 지원해야 하는 경우, 권장되는 방법은 serialized attribute를 사용하는 것입니다. serialized attribute 선언은 암호화 선언 이전에 이루어져야 합니다:
# 올바른 방법
class Article < ApplicationRecord
serialize :title, type: Title
encrypts :title
end
# 잘못된 방법
class Article < ApplicationRecord
encrypts :title
serialize :title, type: Title
end
3.4 대소문자 무시하기
결정적 암호화된 데이터를 쿼리할 때 대소문자를 무시해야 할 수 있습니다. 이를 쉽게 달성할 수 있는 두 가지 접근 방법이 있습니다:
암호화된 속성을 선언할 때 :downcase
옵션을 사용하여 암호화가 발생하기 전에 내용을 소문자로 변환할 수 있습니다.
class Person
encrypts :email_address, deterministic: true, downcase: true
end
이메일 주소가 저장되기 전에 모든 문자를 소문자로 변환하는 Person
모델입니다. deterministic encryption은 검색 목적으로 downcase된 값을 사용합니다.
:downcase
를 사용할 때, 원래의 대소문자는 손실됩니다. 때로는 쿼리할 때만 대소문자를 무시하고 원래의 대소문자는 저장하고 싶을 수 있습니다. 이런 경우에는 :ignore_case
옵션을 사용할 수 있습니다. 이 옵션을 사용하려면 대소문자가 변경되지 않은 내용을 저장하기 위한 original_<column_name>
이라는 새로운 컬럼을 추가해야 합니다:
class Label
encrypts :name, deterministic: true, ignore_case: true # 원래 대소문자는 `original_name` 칼럼에 저장됩니다
end
3.5 암호화되지 않은 데이터 지원
암호화되지 않은 데이터의 마이그레이션을 용이하게 하기 위해, 라이브러리는 config.active_record.encryption.support_unencrypted_data
옵션을 포함합니다. 이를 true
로 설정하면:
- 암호화되지 않은 암호화 속성을 읽으려고 할 때 오류를 발생시키지 않고 정상적으로 작동합니다.
- Deterministically-encrypted 속성이 있는 쿼리에는 암호화된 콘텐츠와 암호화되지 않은 콘텐츠를 모두 찾을 수 있도록 "일반 텍스트" 버전이 포함됩니다. 이를 활성화하려면
config.active_record.encryption.extend_queries = true
를 설정해야 합니다.
이 옵션은 일반 데이터와 암호화된 데이터가 공존해야 하는 전환 기간 동안 사용하기 위한 것입니다. 두 옵션 모두 기본적으로 false
로 설정되어 있으며, 이는 모든 애플리케이션에 권장되는 목표입니다: 암호화되지 않은 데이터로 작업할 때 오류가 발생합니다.
3.6 이전 암호화 체계 지원
속성의 암호화 속성을 변경하면 기존 데이터가 깨질 수 있습니다. 예를 들어 결정론적 속성을 비결정론적으로 변경하고 싶다고 가정해봅시다. 모델에서 선언만 변경하면, 이제 암호화 방법이 달라졌기 때문에 기존 암호문을 읽는 것이 실패할 것입니다.
이러한 상황을 지원하기 위해, 다음 두 가지 시나리오에서 사용될 이전 암호화 체계를 선언할 수 있습니다:
- 암호화된 데이터를 읽을 때, Active Record Encryption은 현재 체계가 작동하지 않으면 이전 암호화 체계를 시도합니다.
- 결정론적 데이터를 쿼리할 때, 서로 다른 체계로 암호화된 데이터에 대한 쿼리가 원활하게 작동하도록 이전 체계를 사용하여 암호문을 추가합니다. 이를 활성화하려면
config.active_record.encryption.extend_queries = true
를 설정해야 합니다.
이전 암호화 체계를 다음과 같이 구성할 수 있습니다:
- 전역적으로
- 속성별로
3.6.1 전역 이전 암호화 체계
application.rb
에서 previous
설정 속성을 사용하여 속성 목록으로 이전 암호화 체계를 추가할 수 있습니다:
config.active_record.encryption.previous = [ { key_provider: MyOldKeyProvider.new } ]
3.6.2 Per-attribute 암호화 구성표
:previous
를 attribute를 선언할 때 사용하세요:
class Article
encrypts :title, deterministic: true, previous: { deterministic: false }
end
이 예시는 현재는 deterministic encryption을 사용하지만 이전에는 사용하지 않았던 속성을 보여줍니다.
3.6.3 Encryption Scheme과 Deterministic Attribute
이전 encryption scheme을 추가할 때:
- non-deterministic encryption의 경우, 새로운 정보는 항상 최신(현재) encryption scheme으로 암호화됩니다.
- deterministic encryption의 경우, 새로운 정보는 기본적으로 항상 가장 오래된 encryption scheme으로 암호화됩니다.
일반적으로 deterministic encryption에서는 ciphertext가 일정하게 유지되기를 원합니다. deterministic: { fixed: false }
를 설정하여 이 동작을 변경할 수 있습니다. 이 경우, 새로운 데이터를 암호화할 때 최신 encryption scheme을 사용하게 됩니다.
3.7 Unique 제약조건
Unique 제약조건은 결정론적으로 암호화된 데이터에서만 사용할 수 있습니다.
3.7.1 Unique 유효성검사
Unique 유효성검사는 extended queries가 활성화된 경우(config.active_record.encryption.extend_queries = true
) 정상적으로 지원됩니다.
class Person
validates :email_address, uniqueness: true
encrypts :email_address, deterministic: true, downcase: true
end
위 코드에서 비결정적 암호화는 email_address가 unique 제약조건이 있기 때문에 작동하지 않을 것입니다. 대신 deterministic 옵션을 true로 설정하면 같은 평문이 항상 동일한 암호문으로 변환되어 유일성 검증이 가능해집니다. deterministic 암호화를 이메일 주소에 사용할 때는 downcase: true 옵션도 함께 설정하는 것이 좋습니다.
암호화된 데이터와 암호화되지 않은 데이터를 결합할 때나 이전 암호화 스키마를 구성할 때도 동작할 것입니다.
대소문자를 무시하려면 encrypts
선언에서 downcase:
또는 ignore_case:
를 사용해야 합니다. validation에서 case_sensitive:
옵션을 사용하는 것은 작동하지 않습니다.
3.7.2 Unique Indexes
deterministically-encrypted 컬럼에 unique 인덱스를 지원하려면, 해당 ciphertext가 절대 변경되지 않도록 해야 합니다.
이를 장려하기 위해, 여러 암호화 스키마가 구성되어 있을 때 deterministic 속성은 기본적으로 항상 사용 가능한 가장 오래된 암호화 스키마를 사용합니다. 그렇지 않으면, 이러한 속성에 대한 암호화 속성이 변경되지 않도록 하는 것은 여러분의 몫이며, 그렇지 않으면 unique 인덱스가 작동하지 않을 것입니다.
class Person
encrypts :email_address, deterministic: true
end
3.8 Encrypted Columns로 명명된 Params 필터링
기본적으로 encrypted columns은 Rails 로그에서 자동으로 필터링되도록 설정되어 있습니다. application.rb
에 다음을 추가하여 이 동작을 비활성화할 수 있습니다:
config.active_record.encryption.add_to_filter_parameters = false
암호화된 필드를 Rails의 필터링된 파라미터 목록에 자동으로 추가하는 것을 제어합니다. 기본값은 true입니다.
필터링이 활성화된 상태에서 특정 컬럼을 자동 필터링에서 제외하려면, config.active_record.encryption.excluded_from_filter_parameters
에 해당 컬럼을 추가하세요:
config.active_record.encryption.excluded_from_filter_parameters = [:catchphrase]
filter_parameters에서 제외할 암호화된 attributes를 지정할 수 있습니다. 이는 filter_parameters가 로깅되는 민감한 정보를 보호하는 것을 막는 경우에 유용합니다.
필터 파라미터를 생성할 때, Rails는 model 이름을 접두사로 사용합니다. 예시: Person#name
의 경우, 필터 파라미터는 person.name
이 됩니다.
3.9 인코딩
라이브러리는 비결정적으로 암호화된 문자열 값에 대한 인코딩을 유지합니다.
인코딩이 암호화된 페이로드와 함께 저장되기 때문에, 결정적으로 암호화된 값은 기본적으로 UTF-8 인코딩을 강제합니다. 따라서 다른 인코딩을 가진 동일한 값은 암호화될 때 다른 암호문을 생성합니다. 일반적으로 쿼리와 고유성 제약 조건이 작동하도록 이를 피하고 싶을 것이므로, 라이브러리는 자동으로 이 변환을 수행합니다.
결정적 암호화에 대한 기본 인코딩을 다음과 같이 구성할 수 있습니다:
config.active_record.encryption.forced_encoding_for_deterministic_encryption = Encoding::US_ASCII
deterministic encryption에서 사용되는 강제 인코딩을 설정합니다. 이는 모든 암호화된 데이터를 지정된 인코딩으로 표준화시킵니다.
아래와 같이 설정하면 이러한 동작을 비활성화하고 모든 경우에 인코딩을 유지할 수 있습니다:
config.active_record.encryption.forced_encoding_for_deterministic_encryption = nil
deterministic encryption에서 강제 인코딩을 지정합니다. nil(기본값)인 경우 기존 인코딩이 유지됩니다. UTF-8과 같은 특정 인코딩을 강제하려면 이 옵션을 사용하세요.
3.10 Compression
라이브러리는 기본적으로 암호화된 페이로드를 압축합니다. 이는 더 큰 페이로드의 경우 최대 30%의 저장 공간을 절약할 수 있습니다. encrypted 속성에 compress: false
를 설정하여 압축을 비활성화할 수 있습니다:
class Article < ApplicationRecord
encrypts :content, compress: false
end
압축에 사용되는 알고리즘을 설정할 수도 있습니다. 기본 compressor는 Zlib
입니다. #deflate(data)
와 #inflate(data)
에 응답하는 클래스나 모듈을 생성하여 자신만의 compressor를 구현할 수 있습니다.
require "zstd-ruby"
module ZstdCompressor
def self.deflate(data)
Zstd.compress(data)
end
def self.inflate(data)
Zstd.decompress(data)
end
end
class User
encrypts :name, compressor: ZstdCompressor
end
위 예시는 Zstandard(또는 zstd) 압축 알고리즘을 사용하기 위해 자체 compressor를 정의하는 방법을 보여줍니다. Zstandard는 높은 압축률과 빠른 압축 및 압축 해제 속도를 제공하는 실시간 압축 알고리즘입니다.
컴프레서를 전역적으로 설정할 수 있습니다:
config.active_record.encryption.compressor = ZstdCompressor
위 코드는 Active Record encryption에서 사용할 압축기를 ZstdCompressor로 설정합니다.
4 Key 관리
Key provider는 key 관리 전략을 구현합니다. Key provider를 전역으로 또는 attribute 단위로 구성할 수 있습니다.
4.1 내장 Key Provider들
4.1.1 DerivedSecretKeyProvider
PBKDF2를 사용하여 제공된 password로부터 도출된 key를 제공하는 key provider입니다.
config.active_record.encryption.key_provider = ActiveRecord::Encryption::DerivedSecretKeyProvider.new(["key를 도출할", "몇 개의 비밀번호", "이들은 credentials에", "있어야 합니다"])
기본적으로 active_record.encryption
은 active_record.encryption.primary_key
에 정의된 키들을 사용하여 DerivedSecretKeyProvider
를 구성합니다.
4.1.2 EnvelopeEncryptionKeyProvider
간단한 envelope encryption 전략을 구현합니다:
- 각 데이터 암호화 작업마다 무작위 키를 생성합니다
- 데이터 키를 credential
active_record.encryption.primary_key
에 정의된 primary key로 암호화하여 데이터와 함께 저장합니다
application.rb
에 다음을 추가하여 Active Record가 이 key provider를 사용하도록 구성할 수 있습니다:
config.active_record.encryption.key_provider = ActiveRecord::Encryption::EnvelopeEncryptionKeyProvider.new
다른 내장된 key provider들과 마찬가지로, active_record.encryption.primary_key
에 primary key 목록을 제공하여 key-rotation 체계를 구현할 수 있습니다.
4.2 Custom Key Providers
더 고급 key 관리 체계를 위해서는 initializer에서 custom key provider를 구성할 수 있습니다:
ActiveRecord::Encryption.key_provider = MyKeyProvider.new
키 제공자는 다음 인터페이스를 구현해야 합니다:
class MyKeyProvider
def encryption_key
end
def decryption_keys(encrypted_message)
end
end
두 메서드는 ActiveRecord::Encryption::Key
객체를 반환합니다:
encryption_key
는 특정 내용을 암호화하는 데 사용되는 키를 반환합니다decryption_keys
는 주어진 메시지를 복호화하는 데 사용될 수 있는 키들의 목록을 반환합니다
키에는 메시지와 함께 암호화되지 않은 상태로 저장될 임의의 태그가 포함될 수 있습니다. 복호화 시 ActiveRecord::Encryption::Message#headers
를 사용하여 이러한 값들을 검사할 수 있습니다.
4.3 Attribute별 Key Provider
attribute 단위로 :key_provider
옵션을 사용하여 key provider를 설정할 수 있습니다:
class Article < ApplicationRecord
encrypts :summary, key_provider: ArticleKeyProvider.new
end
4.4 Attribute-specific Keys
:key
옵션을 사용하여 주어진 key를 attribute별로 구성할 수 있습니다:
class Article < ApplicationRecord
encrypts :summary, key: "article summary를 위한 비밀 키"
end
Active Record는 데이터를 암호화하고 복호화하는데 사용되는 키를 도출하기 위해 키를 사용합니다.
4.5 키 순환
active_record.encryption
은 키 순환 체계를 구현하기 위해 키 목록과 함께 작동할 수 있습니다:
- 마지막 키는 새로운 콘텐츠를 암호화하는 데 사용됩니다.
- 콘텐츠를 복호화할 때는 하나가 작동할 때까지 모든 키를 시도합니다.
active_record_encryption:
primary_key:
- a1cc4d7b9f420e40a337b9e68c5ecec6 # 기존 콘텐츠를 여전히 복호화할 수 있는 이전 키
- bc17e7b413fd4720716a7633027f8cc4 # 활성화 상태로, 새로운 콘텐츠를 암호화
key_derivation_salt: a3226b97b3b2f8372d1fc6d497a0c0d3
이는 새로운 키를 추가하고, 콘텐츠를 재암호화하고, 오래된 키를 삭제하여 키 목록을 짧게 유지할 수 있는 워크플로우를 가능하게 합니다.
deterministic encryption에서는 현재 키 rotation이 지원되지 않습니다.
Active Record Encryption은 아직 키 rotation 프로세스의 자동 관리 기능을 제공하지 않습니다. 모든 구성 요소는 존재하지만, 아직 구현되지 않았습니다.
4.6 Key References 저장하기
active_record.encryption.store_key_references
를 설정하여 active_record.encryption
이 암호화된 메시지 자체에 암호화 키에 대한 reference를 저장하도록 할 수 있습니다.
config.active_record.encryption.store_key_references = true
이 설정은 Active Record가 encrypted attribute가 암호화될 때 사용한 key들의 참조를 저장할지 여부를 제어합니다. 이는 key rotation을 수행할 때 유용할 수 있습니다 - 암호화에 사용된 key들을 쉽게 추적할 수 있게 됩니다.
이렇게 하면 시스템이 키 목록을 시도하는 대신 키를 직접 찾을 수 있기 때문에 더 성능 좋은 복호화가 가능합니다. 그 대가로 저장 공간이 필요합니다: 암호화된 데이터가 약간 더 커집니다.
5 API
5.1 기본 API
ActiveRecord encryption은 선언적으로 사용되도록 설계되었지만, 고급 사용 시나리오를 위한 API를 제공합니다.
5.1.1 Encrypt와 Decrypt
article.encrypt # 암호화 가능한 모든 attributes를 암호화하거나 재암호화합니다
article.decrypt # 암호화 가능한 모든 attributes를 복호화합니다
5.1.2 Ciphertext 읽기
다음 예제는 Rails 응용 프로그램에서 암호화된 속성에 대한 데이터베이스 레벨 쿼리를 하기 위해 encryptor의 read
메소드를 어떻게 사용하는지 보여줍니다.
class Message < ApplicationRecord
encrypts :text
end
# 정확히 일치하는 암호화 텍스트를 찾기 위한 DB 쿼리
encrypted_text = Message.encryption_schemes[:text].encrypt("my text")
Message.find_by("text = ?", encrypted_text)
# Rails 7.1부터는, 다음 방법도 가능합니다:
Message.find_by(Message.encryption_schemes[:text].read("my text"))
ciphertext 헤더의 파싱은 read
메소드가 자동으로 설정합니다. 만약 암호화된 텍스트가 헤더 없이 암호화되었다면, 적절한 설정을 위해 encrypted_text
에 대한 파라미터를 전달할 수 있습니다:
ciphertext = Message.encryption_schemes[:text].read(
"my text",
purpose: :foo,
deterministic: true
)
article.ciphertext_for(:title)
5.1.3 속성이 암호화되었는지 여부 확인하기
You can check if a model's attribute is configured for encryption by calling encrypted_attribute?
:
모델의 속성이 암호화를 위해 설정되었는지는 encrypted_attribute?
를 호출하여 확인할 수 있습니다:
Message.encrypted_attribute?(:content) # => true
Message.encrypted_attribute?(:id) # => false
Message.encrypted_attribute?(:non_existent) # => false
message = Message.new
message.encrypted_attribute?(:content) # => true
article.encrypted_attribute?(:title)
6 Configuration
이 섹션에서는 애플리케이션의 다양한 Rails 컴포넌트들을 구성하기 위한 다양한 방법을 설명합니다.
완료 후에는 다음과 같은 내용을 알게됩니다:
- Rails 애플리케이션에서 일반적인 설정을 어떻게 조정할지
- 데이터베이스를 어떻게 구성할지
- 추가적인 설정 파일들을 사용해 애플리케이션의 다른 측면들을 구성하는 방법
- 개발, 테스트, 프로덕션 환경에 대해 서로 다른 설정을 적용하는 방법
Rails를 시작하기 위한 주요 내용에 대해서는 이미 설명된 Getting Started with Rails를 확인하세요.
6.1 Configuration Options
Rails의 Active Record Encryption 옵션은 application.rb
(가장 일반적인 시나리오)에서 설정하거나, 환경별로 설정하려면 특정 환경 설정 파일 config/environments/<env name>.rb
에서 설정할 수 있습니다.
키를 저장할 때는 Rails 내장 credentials 기능을 사용하는 것이 권장됩니다. config 속성을 통해 수동으로 설정하려는 경우, 코드와 함께 커밋되지 않도록 주의하세요(예: 환경 변수 사용).
6.1.1 config.active_record.encryption.support_unencrypted_data
true일 때, 암호화되지 않은 데이터를 정상적으로 읽을 수 있습니다. false일 때는 에러가 발생합니다. 기본값: false
.
6.1.2 config.active_record.encryption.extend_queries
true일 때, 결정적으로 암호화된 속성을 참조하는 쿼리는 필요한 경우 추가 값을 포함하도록 수정됩니다. 이러한 추가 값들은 값의 클린 버전(config.active_record.encryption.support_unencrypted_data
가 true일 때)과 이전 암호화 방식으로 암호화된 값들(previous:
옵션으로 제공된 경우)입니다. 기본값: false
(실험적).
6.1.3 config.active_record.encryption.encrypt_fixtures
true일 때, fixtures의 암호화 가능한 속성들은 로드될 때 자동으로 암호화됩니다. 기본값: false
.
6.1.4 config.active_record.encryption.store_key_references
true일 때, 암호화된 메시지의 헤더에 암호화 키에 대한 참조가 저장됩니다. 여러 키가 사용 중일 때 더 빠른 복호화가 가능합니다. 기본값: false
.
6.1.5 config.active_record.encryption.add_to_filter_parameters
true일 때, 암호화된 속성 이름들이 자동으로 config.filter_parameters
에 추가되어 로그에 표시되지 않습니다. 기본값: true
.
6.1.6 config.active_record.encryption.excluded_from_filter_parameters
config.active_record.encryption.add_to_filter_parameters
가 true일 때 필터링되지 않을 매개변수 목록을 설정할 수 있습니다. 기본값: []
.
6.1.7 config.active_record.encryption.validate_column_size
컬럼 크기를 기반으로 한 유효성 검사를 추가합니다. 이는 높은 압축률의 페이로드를 사용하여 거대한 값을 저장하는 것을 방지하기 위해 권장됩니다. 기본값: true
.
6.1.8 config.active_record.encryption.primary_key
루트 데이터 암호화 키를 생성하는 데 사용되는 키 또는 키 목록입니다. 사용 방법은 구성된 키 제공자에 따라 다릅니다. active_record_encryption.primary_key
credential을 통해 구성하는 것이 선호됩니다.
6.1.9 config.active_record.encryption.deterministic_key
결정적 암호화에 사용되는 키 또는 키 목록입니다. active_record_encryption.deterministic_key
credential을 통해 구성하는 것이 선호됩니다.
6.1.10 config.active_record.encryption.key_derivation_salt
키 생성에 사용되는 솔트입니다. active_record_encryption.key_derivation_salt
credential을 통해 구성하는 것이 선호됩니다.
6.1.11 config.active_record.encryption.forced_encoding_for_deterministic_encryption
결정론적으로 암호화된 속성의 기본 인코딩입니다. 이 옵션을 nil
로 설정하여 강제 인코딩을 비활성화할 수 있습니다. 기본값은 Encoding::UTF_8
입니다.
6.1.12 config.active_record.encryption.hash_digest_class
키를 도출하는 데 사용되는 다이제스트 알고리즘입니다. 기본값은 OpenSSL::Digest::SHA256
입니다.
6.1.13 config.active_record.encryption.support_sha1_for_non_deterministic_encryption
SHA1 다이제스트 클래스로 비결정론적으로 암호화된 데이터의 복호화를 지원합니다. 기본값은 false이며, 이는 config.active_record.encryption.hash_digest_class
에 구성된 다이제스트 알고리즘만 지원한다는 의미입니다.
6.1.14 config.active_record.encryption.compressor
암호화된 페이로드를 압축하는데 사용되는 압축기입니다. deflate
와 inflate
에 응답해야 합니다. 기본값은 Zlib
입니다. 압축기에 대한 자세한 정보는 Compression 섹션에서 확인할 수 있습니다.
6.2 암호화 컨텍스트
암호화 컨텍스트는 주어진 시점에 사용되는 암호화 컴포넌트들을 정의합니다. 전역 설정을 기반으로 하는 기본 암호화 컨텍스트가 있지만, 특정 속성이나 특정 코드 블록을 실행할 때 사용자 정의 컨텍스트를 구성할 수 있습니다.
암호화 컨텍스트는 유연하지만 고급 설정 메커니즘입니다. 대부분의 사용자는 이에 대해 신경 쓸 필요가 없습니다.
암호화 컨텍스트의 주요 컴포넌트는 다음과 같습니다:
encryptor
: 데이터 암호화와 복호화를 위한 내부 API를 제공합니다.key_provider
와 상호작용하여 암호화된 메시지를 생성하고 직렬화를 처리합니다. 실제 암호화/복호화는cipher
에 의해 수행되며 직렬화는message_serializer
에 의해 수행됩니다.cipher
: 암호화 알고리즘 자체(AES 256 GCM)key_provider
: 암호화 및 복호화 키를 제공합니다.message_serializer
: 암호화된 페이로드(Message
)를 직렬화하고 역직렬화합니다.
자체 message_serializer
를 구축하기로 결정한 경우, 임의의 객체를 역직렬화할 수 없는 안전한 메커니즘을 사용하는 것이 중요합니다. 일반적인 지원 시나리오는 기존의 암호화되지 않은 데이터를 암호화하는 것입니다. 공격자는 이를 활용하여 암호화가 이루어지기 전에 조작된 페이로드를 입력하고 RCE 공격을 수행할 수 있습니다. 이는 사용자 정의 직렬화기가 Marshal
, YAML.load
(대신 YAML.safe_load
사용) 또는 JSON.load
(대신 JSON.parse
사용)를 피해야 함을 의미합니다.
6.2.1 전역 암호화 컨텍스트
전역 암호화 컨텍스트는 기본적으로 사용되는 컨텍스트이며 application.rb
또는 환경 설정 파일에서 다른 설정 속성과 같이 구성됩니다.
config.active_record.encryption.key_provider = ActiveRecord::Encryption::EnvelopeEncryptionKeyProvider.new
config.active_record.encryption.encryptor = MyEncryptor.new
6.2.2 속성별 Encryption Context
attribute 선언에서 encryption context 파라미터를 전달하여 설정을 덮어쓸 수 있습니다:
class Attribute
encrypts :title, encryptor: MyAttributeEncryptor.new
end
6.2.3 코드 블록 실행 시 Encryption Context
코드 블록에 대한 encryption context를 설정하기 위해 ActiveRecord::Encryption.with_encryption_context
를 사용할 수 있습니다:
ActiveRecord::Encryption.with_encryption_context(encryptor: ActiveRecord::Encryption::NullEncryptor.new) do
# ...
end
특정 로직을 암호화하지 않고 실행하려면 NullEncryptor
암호화를 사용할 수 있습니다.
6.2.4 Built-in Encryption Contexts
6.2.4.1 Disable Encryption
암호화 없이 코드를 실행할 수 있습니다:
ActiveRecord::Encryption.without_encryption do
# ...
end
일시적으로 암호화를 비활성화하고 코드 블록을 실행합니다. 이는 암호화된 속성에 대한 필터링된 검색을 수행하거나 일부 마이그레이션 시나리오를 수행할 때 유용할 수 있습니다.
이는 암호화된 텍스트를 읽을 때는 ciphertext를 반환하고, 저장된 콘텐츠는 암호화되지 않은 상태로 저장됨을 의미합니다.
6.2.4.2 Encrypted Data 보호하기
암호화 없이 코드를 실행할 수 있지만 암호화된 콘텐츠를 덮어쓰는 것은 방지할 수 있습니다:
ActiveRecord::Encryption.protecting_encrypted_data do
# ...
end
암호화된 데이터를 보호하면서 주어진 코드 블록을 실행합니다. 이는 예를 들어 모든 암호화된 데이터가 삭제되어야 하는 중요한 작업을 실행할 때 유용합니다.
암호화된 데이터를 보호하면서도 임의의 코드를 실행하고 싶을 때(예: Rails console에서) 유용할 수 있습니다.