用途
本技能用于在 Rails 应用中,通过内置的 Active Record Encryption 在应用层加密敏感数据。适用于为模型属性添加 encrypts 声明、首次配置加密密钥、将已有未加密数据迁移为加密数据、轮换密钥、查询确定性加密属性、加密 Action Text 内容,以及调试解密失败、查询不匹配等加密相关错误。
核心原则
- 只加密真正需要加密的字段——加密会增加复杂度和存储开销,需谨慎决定。
- 仅在必须查询时使用确定性加密——非确定性更安全,应作为默认选择。
- 密钥属于 credentials,不属于代码——绝不提交加密密钥。
- 加密前先规划迁移——已有未加密数据需要迁移策略。
- 测试时启用加密——fixtures 需要
encrypt_fixtures: true,否则测试会失败。
决策矩阵
- 需要查询/
find_by该字段?是 →deterministic: true;否 → 默认(非确定性)。 - 需要唯一性校验?是 →
deterministic: true(若需忽略大小写,加downcase/ignore_case)。 - 需要保留大小写但按大小写不敏感查询?是 →
deterministic: true, ignore_case: true(会新增original_列)。 - 仅保护静态数据(日志、备份)?是 → 默认(非确定性)即可。
主要能力与使用方式
1. 生成并存储密钥
运行 bin/rails db:encryption:init,将输出的三个值存入 credentials(bin/rails credentials:edit):
active_record_encryption:
primary_key: ...
deterministic_key: ...
key_derivation_salt: ...
也可使用环境变量(12-factor 应用):
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"]
验证:bin/rails runner "puts ActiveRecord::Encryption.config.primary_key.present?" 应输出 true。
2. 检查现有模式
搜索代码库中已有的加密声明与配置,检查计划加密字段的列大小,并沿用项目现有约定(环境变量或 credentials)。
3. 声明加密属性
非确定性(默认,最安全):
class User < ApplicationRecord
encrypts :ssn
encrypts :medical_notes
end
确定性(可查询):
class User < ApplicationRecord
encrypts :email, deterministic: true
encrypts :phone, deterministic: true, downcase: true
end
Action Text:
class Message < ApplicationRecord
has_rich_text :content, encrypted: true
end
序列化属性(顺序重要):
class Article < ApplicationRecord
serialize :metadata, type: Hash # 先
encrypts :metadata # 后
end
4. 处理列大小
加密后数据更大:string(255) ASCII 需 string(510);string(255) Unicode 需 string(1275);string(500) 非西方字符需 string(2255);text 无需变更。列过小需生成迁移调整。
5. 迁移已有未加密数据
分三阶段:
- 启用共存:
config.active_record.encryption.support_unencrypted_data = true与extend_queries = true。 - 加密已有记录(任务或迁移中
User.find_each(&:encrypt))。 - 验证全部加密后,移除共存配置。
6. 配置密钥轮换(仅非确定性)
确定性加密不支持密钥轮换。非确定性可将新密钥加入列表(最后一个用于加密,所有密钥用于解密),重新加密已有数据后移除旧密钥。
7. 配置测试环境
在 config/environments/test.rb 中设置 config.active_record.encryption.encrypt_fixtures = true。迁移数据时同样启用共存配置。
8. 正确查询
确定性字段可正常查询;非确定性字段无法查询(每次密文不同)。若需按非确定性字段查找,可改用确定性(安全性降低)或另加确定性摘要/哈希列。
9. 唯一性与大小写
唯一性校验需要确定性加密。不要在校验上使用 case_sensitive: false,而应在 encrypts 上使用 downcase: true 或 ignore_case: true。ignore_case: true 需要额外的 original_<column_name> 列。
关键错误与修复
Errors::Configuration:密钥未配置 → 运行rails db:encryption:init并存入 credentials。Errors::Decryption:密钥错误或数据损坏 → 检查密钥配置;迁移期间启用support_unencrypted_data。- 查询已有数据返回 nil:查询了非确定性字段 → 添加
deterministic: true。 undefined column 'original_X':使用ignore_case: true但未迁移 → 添加original_<column>列。- Fixture 测试失败:fixtures 未加密 → 测试配置中设置
encrypt_fixtures = true。 - 序列化属性静默失效:声明顺序错误 →
serialize放在encrypts之前。
适用场景
适用于 Rails 应用中需要保护 PII、财务数据、健康信息等敏感字段的场景,包括新增加密、密钥配置、数据迁移、密钥轮换、查询调试与测试配置。
限制与注意事项
- 确定性加密安全性低于非确定性,且不支持密钥轮换。
- 非确定性加密字段无法直接查询。
- 加密会增加存储开销,需调整列大小。
- 密钥必须存入 credentials 或真实环境变量,不得写入仓库。
- 不要加密所有字段,仅加密真正敏感的字段。
- 迁移已有数据时必须先启用共存配置,否则读取会崩溃。