56

I saw a while ago the possibility to decrypt and encrypt strings in rails without including any library, but I can't find the blog post.

I want to be able to encrypt and decrypt strings without including anything. Using the same key has for the everything else in rails, signed cookies for example.

Any ideas?

Linus Oleander
  • 17,746
  • 15
  • 69
  • 102

3 Answers3

143

You mean this one?: ActiveSupport::MessageEncryptor. Here is the way to reuse Rails 5+ on Ruby 2.4+ application's secret:

crypt = ActiveSupport::MessageEncryptor.new(Rails.application.secrets.secret_key_base[0..31])
encrypted_data = crypt.encrypt_and_sign('my confidental data')

And encrypted data can be decrypted with:

decrypted_back = crypt.decrypt_and_verify(encrypted_data)

The above example uses first 32 characters of Rails app secret as an encryption and signing key, because the default MessageEncryptor cipher aes-256-gcm requires exactly 256 bit key. By convention, during the app creation, Rails generates a secret as a string of 128 hex digits.

Important! Ruby 2.4 upgrade note

Before Ruby 2.4 and Rails 5 there was no key size restriction and it was popular to just past full secret into the encryptor initializer:

# pre-2.4
crypt = ActiveSupport::MessageEncryptor.new(Rails.application.secrets.secret_key_base)

Internally the encryption algorithm (AES256GCM provided by OpenSSL) was using only 32 characters from the key, however the signing algorithm (SHA1) was consuming all 128 characters.

Therefore, while upgrading an app from pre-2.4 Ruby, and where the app previously encrypted the data with an unrestricted key size, the MessageEncryptor must get a full secret in the second parameter to avoid ActiveSupport::MessageVerifier::InvalidSignature on the legacy data decryption:

# post-2.4 upgrade
crypt = ActiveSupport::MessageEncryptor.new(Rails.application.secrets.secret_key_base[0..31], Rails.application.secrets.secret_key_base)
gertas
  • 16,869
  • 1
  • 76
  • 58
  • 4
    A great solution to what I was looking for, thanks! Just a heads up, the ActiveSupport::MessageEncryptor interface has changed since Rails 4.0 and the example is outdated. You should call encrypt_and_sign instead of encrypt and decrypt_and_verify instead of decrypt. – Georgi Atsev Mar 20 '14 at 08:27
  • Rails.configuration.secret_key_base is nil for me. I have it set in secrets.yml. Any idea why this may be? – Dave Oct 28 '14 at 15:57
  • 1
    @Dave please see this post http://stackoverflow.com/questions/26721790/rails-configuration-secret-key-base-returning-nil/26722044#26722044 – computer_smile Nov 03 '14 at 20:00
  • 21
    To get secret_key_base use `Rails.application.secrets.secret_key_base` – Gee-Bee Dec 23 '14 at 21:39
  • 2
    As noted in https://stackoverflow.com/questions/5492377/encrypt-decrypt-using-rails/5492450#comment93082974_49560436 , this will only work in Rails 5 if the secret_key_base is at least 32 bytes long. – Daniel Oct 31 '18 at 23:13
  • To convert ANY size of secret_key_base to exactly 32 bytes one can do this, for example: `Digest::SHA256.digest(Rails.application.secrets.secret_key_base)` – Sergio Tulentsev Dec 06 '18 at 10:31
19

Rails 5 requires that the key be 32 bytes.

Edit to Rails 4 answer that works for Rails 5:

 key = SecureRandom.random_bytes(32)
 crypt = ActiveSupport::MessageEncryptor.new(key) 
 encrypted_data = crypt.encrypt_and_sign('my confidental data')

Decrypt:

 decrypted_back = crypt.decrypt_and_verify(encrypted_data)
guero64
  • 1,019
  • 1
  • 12
  • 18
  • 2
    Is the key persisted anywhere? Or is this just for one request at a time (or something that remains in memory)? I think the idea of using the Rails secret key base is that it is re-usable between requests. – marksiemers Apr 11 '18 at 00:37
  • Several things to note: (1) In this example the key isn't persisted, but it could be persisted pretty easily. (2) "secret must be *at least* as long as the cipher key size. For the default 'aes-256-gcm' cipher, this is 256 bits [32 bytes]" ( https://rubydocs.org/d/rails-5-2-0/classes/ActiveSupport/MessageEncryptor.html#method-c-new , emphasis mine). (3) "...most Rails applications are using a secret_key_base value that is 64 bytes long" (https://medium.com/@michaeljcoyne/understanding-the-secret-key-base-in-ruby-on-rails-ce2f6f9968a1 ) so the top-voted answer will still work in most cases. – Daniel Oct 31 '18 at 23:07
9

Rails 5 update:

crypt = ActiveSupport::MessageEncryptor.new(Rails.application.secrets.secret_key_base[0..31])
encrypted_data = crypt.encrypt_and_sign('my confidental data')

Rails 5.x Needs a key of exactly 32 bytes.

To verify a previously signed message with a longer key:

crypt = ActiveSupport::MessageEncryptor.new(Rails.application.secrets.secret_key_base[0..31], Rails.application.secrets.secret_key_base)
encrypted_data = crypt.encrypt_and_sign('my confidental data')

as described in the documentation and the discussion on this change

Bhavik Parmar
  • 424
  • 1
  • 6
  • 9
estani
  • 24,254
  • 2
  • 93
  • 76