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
Add option to configure digest algorithm in Active Record Encryption #44873
Conversation
635b8f4
to
b3150e0
Compare
5622b0c
to
cc5b2b8
Compare
Thanks! How do we feel about leaving apps that started in an older version on SHA1 for the long term? Is our recommendation that for this use case it's really fine either way, despite their differing security profiles... or is this a situation where using SHA1 really is less-advisable, and we're just warning that it's hard to change once you have data stored? Regardless of how strongly we encourage it, if people do want to switch, is there a story for how they can go about it? How much of A Thing would it be for us to provide read-old-write-new compatibility, as [I believe?] we do for key transitions? |
I don't think using SHA1 represents any security flaw here. Active Record encryption uses the digest algorithm to derive keys with the size expected by the cipher (AES256-GCM) from the configured secrets. The generated hashes are calculated on the fly and aren't exposed anywhere, so an attacker can't really exploit collision-related flaws in the algorithm to guess keys. Of course, as long as the original secrets remain private.
There is a path, but right now it's not directly supported. The system is prepared to work with multiple decryption keys, so it would be a matter of injecting keys with multiple hash digest algorithms (in |
# Active Record Encryption now uses SHA-256 as its hash digest algorithm. Important: If you have data encrypted | ||
# with previous versions, you should not set the new default or the existing data will fail to decrypt. | ||
# Rails.application.config.active_record.encryption.hash_digest_class = OpenSSL::Digest::SHA256 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is sufficient. If you have data encrypted with previous versions, then you should set the old version as a config. That way if you later do load_defaults 7.1
it won't change this under the hood.
# Active Record Encryption now uses SHA-256 as its hash digest algorithm. Important: If you have data encrypted | |
# with previous versions, you should not set the new default or the existing data will fail to decrypt. | |
# Rails.application.config.active_record.encryption.hash_digest_class = OpenSSL::Digest::SHA256 | |
# Active Record Encryption hash digest algorithm: 2 options | |
# | |
# The new default hash digest algorithm is SHA-256. If you haven't used Active Record Encryption | |
# yet, it is safe to upgrade. | |
# | |
# **Important**: If you have data encrypted already, it is likely encrypted using SHA-1. You should keep the old default or the existing data will fail to decrypt. | |
# | |
# New default: | |
# Rails.application.config.active_record.encryption.hash_digest_class = OpenSSL::Digest::SHA256 | |
# | |
# Old default: | |
# Rails.application.config.active_record.encryption.hash_digest_class = OpenSSL::Digest::SHA1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point @ghiculescu. I'll review this and rebase the branch shortly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ghiculescu I have reviewed the template for the new 7.1 defaults explaining things better. If someone decides to skip the "upgrade" path and just loads the new 7.1 defaults, decryption will fail, but that's what happens with other similar changes (e.g: with cookie serialization back in the day), so I think that's OK. Do you have a different suggestion here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think your changes look good 👍
@matthewd @jorgemanrubia can we add this to the 7.1 milestone? Unless I'm misreading, #44540 introduced a fairly bad bug into Rails |
# Active Record Encryption now uses SHA-256 as its hash digest algorithm. Important: If you have data encrypted | ||
# with previous versions, you should not set the new default or the existing data will fail to decrypt. | ||
# Rails.application.config.active_record.encryption.hash_digest_class = OpenSSL::Digest::SHA256 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point @ghiculescu. I'll review this and rebase the branch shortly.
c453211
to
1034c6c
Compare
ActiveRecord::Encryption.config.hash_digest_class = other_digest_class | ||
derived_with_other_digest_class = @generator.derive_key_from("some password", length: 12) | ||
|
||
assert_not_equal derived_with_digest_class, derived_with_other_digest_class |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(I'm not an encryption expert at all...) beyond asserting a difference, is there a way of asserting that a specific string was encrypted with a specific digest?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 I changed the test to actually check that it's deriving the expected key.
fa385e1
to
df4f651
Compare
This commit changes the active_record_encryption.configuration to be set on an after initialize block. After PR rails#44873, we can configure now hash_digest_class that will be used on ActiveRecord::Encryption. But the docs said that we could change it via an initializer by setting Rails.application.config.active_record.encryption.hash_digest_class. This wasn't working because the railtie "active_record_encryption.configuration" was being set before the initializer was loaded. This changes this behave to configure the active_record encryption after the initialize was loaded.
…ally with a hash digest of SHA1 There is currently a problem with Active Record encryption for users updating from 7.0 to 7.1 Before rails#44873, data encrypted with non-deterministic encryption was always using SHA-1. The reason is that `ActiveSupport::KeyGenerator.hash_digest_class` is set in an after_initialize block in the railtie config, but encryption config was running before that, so it was effectively using the previous default SHA1. That means that existing users are using SHA256 for non deterministic encryption, and SHA1 for deterministic encryption. This adds a new option `use_sha1_digest_for_non_deterministic_data` that users can enable to support for SHA1 and SHA256 when decrypting existing data.
…inistically with a SHA1 hash digest (#48530) * Make sure active record encryption configuration happens after initializers have run Co-authored-by: Cadu Ribeiro <mail@cadu.dev> * Add a new option to support previous data encrypted non-deterministically with a hash digest of SHA1 There is currently a problem with Active Record encryption for users updating from 7.0 to 7.1 Before #44873, data encrypted with non-deterministic encryption was always using SHA-1. The reason is that `ActiveSupport::KeyGenerator.hash_digest_class` is set in an after_initialize block in the railtie config, but encryption config was running before that, so it was effectively using the previous default SHA1. That means that existing users are using SHA256 for non deterministic encryption, and SHA1 for deterministic encryption. This adds a new option `use_sha1_digest_for_non_deterministic_data` that users can enable to support for SHA1 and SHA256 when decrypting existing data. * Set a default value of true for `support_sha1_for_non_deterministic_encryption` and proper initializer values. We want to enable the flag existing versions (< 7.1), and we want it to be false moving by default moving forward. * Make sure the system to auto-filter params supports different initialization orders This reworks the system to auto-filter params so that it works when encrypted attributes are declared before the encryption configuration logic runs. Co-authored-by: Cadu Ribeiro <mail@cadu.dev> --------- Co-authored-by: Cadu Ribeiro <mail@cadu.dev>
We introduced some deterministic encrypted columns while using Rails 7.0. These were encrypted with SHA256. It must have been the default. When we upgraded to Rails 7.1 while still using the 7.0 framework defaults, the encryption algorithm changed to SHA1. This resulted in decryption errors and could have resulted in mixed values had new records been inserted. Fortunately we realized what was going on before that could happen. It's possible that I missed something during the upgrade but it seems like upgrading changed the default algorithm even though we were still using the 7.0 defaults. Apologies if this isn't the right spot for this, just thought I would post it here for visibility. Edit: Looks like this might be tracked in #48204 |
This adds a new option to configure the digest algorithm in Active Record Encryption. It sets SHA-256 as the new default starting in 7.1 and SHA-1 for previous versions.
This is a second take on this problem (see #42929). I forgot about the first PR and it went unmerged 🙄.
Rails 7 uses
SHA256
as the new default digest. However, due to this bug , Active Record Encryption kept using SHA-1 until it was unintentionally fixed by this change. Because that fix is recent, I think setting SHA-1 as the pre-7.1 default makes sense.This shows why a mechanism to fix the digest algorithm in AR encryption is positive. Changing the digest prevents existing encrypted contents from decrypting, so we don't want the digest to change inadvertently when picking a new one in Rails.
To configure the new option:
Hat tip to @boomer196 for the original report, to @georgeclaghorn for the great troubleshooting here, and to @matthewd for the change that exposed this bug.
cc @matthewd