Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support encrypting binary columns #50920

Merged
merged 3 commits into from Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 9 additions & 0 deletions activerecord/CHANGELOG.md
@@ -1,3 +1,12 @@
* Add support for encrypting binary columns

Ensure encryption and decryption pass `Type::Binary::Data` around for binary data.

Previously encrypting binary columns with the `ActiveRecord::Encryption::MessageSerializer`
incidentally worked for MySQL and SQLite, but not PostgreSQL.

*Donal McBreen*

* Deprecated `ENV["SCHEMA_CACHE"]` in favor of `schema_cache_path` in the database configuration.

*Rafael Mendonça França*
Expand Down
Expand Up @@ -81,7 +81,7 @@ def previous_type?
@previous_type
end

def decrypt(value)
def decrypt_as_text(value)
with_context do
unless value.nil?
if @default && @default == value
Expand All @@ -99,6 +99,10 @@ def decrypt(value)
end
end

def decrypt(value)
text_to_database_type decrypt_as_text(value)
end

def try_to_deserialize_with_previous_encrypted_types(value)
previous_types.each.with_index do |type, index|
break type.deserialize(value)
Expand Down Expand Up @@ -129,12 +133,16 @@ def serialize_with_current(value)
encrypt(casted_value.to_s) unless casted_value.nil?
end

def encrypt(value)
def encrypt_as_text(value)
with_context do
encryptor.encrypt(value, **encryption_options)
end
end

def encrypt(value)
text_to_database_type encrypt_as_text(value)
end

def encryptor
ActiveRecord::Encryption.encryptor
end
Expand All @@ -150,6 +158,14 @@ def decryption_options
def clean_text_scheme
@clean_text_scheme ||= ActiveRecord::Encryption::Scheme.new(downcase: downcase?, encryptor: ActiveRecord::Encryption::NullEncryptor.new)
end

def text_to_database_type(value)
if value && cast_type.binary?
ActiveModel::Type::Binary::Data.new(value)
else
value
end
end
end
end
end
19 changes: 19 additions & 0 deletions activerecord/test/cases/encryption/encryptable_record_test.rb
Expand Up @@ -394,6 +394,25 @@ def name
assert_predicate OtherEncryptedPost.type_for_attribute(:title).scheme.previous_schemes, :one?
end

test "binary data can be encrypted" do
all_bytes = (0..255).map(&:chr).join
assert_equal all_bytes, EncryptedBookWithBinary.create!(logo: all_bytes).logo
assert_nil EncryptedBookWithBinary.create!(logo: nil).logo
assert_equal "", EncryptedBookWithBinary.create!(logo: "").logo
end

test "binary data can be encrypted uncompressed" do
low_bytes = (0..127).map(&:chr).join
high_bytes = (128..255).map(&:chr).join
assert_equal low_bytes, EncryptedBookWithBinary.create!(logo: low_bytes).logo
assert_equal high_bytes, EncryptedBookWithBinary.create!(logo: high_bytes).logo
end

test "serialized binary data can be encrypted" do
json_bytes = (32..127).map(&:chr)
assert_equal json_bytes, EncryptedBookWithSerializedBinary.create!(logo: json_bytes).logo
end

private
def build_derived_key_provider_with(hash_digest_class)
ActiveRecord::Encryption.with_encryption_context(key_generator: ActiveRecord::Encryption::KeyGenerator.new(hash_digest_class: hash_digest_class)) do
Expand Down
13 changes: 13 additions & 0 deletions activerecord/test/models/book_encrypted.rb
Expand Up @@ -43,3 +43,16 @@ class EncryptedBookWithUnencryptedDataOptedIn < ActiveRecord::Base
validates :name, uniqueness: true
encrypts :name, deterministic: true, support_unencrypted_data: true
end

class EncryptedBookWithBinary < ActiveRecord::Base
self.table_name = "encrypted_books"

encrypts :logo
end

class EncryptedBookWithSerializedBinary < ActiveRecord::Base
self.table_name = "encrypted_books"

serialize :logo, coder: JSON
encrypts :logo
end
1 change: 1 addition & 0 deletions activerecord/test/schema/schema.rb
Expand Up @@ -163,6 +163,7 @@
t.string :format
t.column :name, :string, default: "<untitled>"
t.column :original_name, :string
t.column :logo, :binary

t.datetime :created_at
t.datetime :updated_at
Expand Down