EasyCrypt is a Ruby utility that provides secure and flexible encryption and decryption capabilities for Ruby on Rails applications. It is built on top of Rails’ ActiveSupport::MessageEncryptor, allowing you to securely and easily encrypt and decrypt data.
- Multiple secrets providers support (currently Rails credentials and env variables).
- Simple, purpose-based encryption/decryption API.
- Configurable encryption cipher.
- Minimal configuration required.
- Built-in encryption signatures to ensure data integrity.
Add this line to your application's Gemfile:
gem 'easy_crypt'
Then execute:
bundle install
Or install it yourself:
gem install easy_crypt
Create an initializer in your Rails application (e.g., config/initializers/easy_crypt.rb
).
Configure EasyCrypt to specify how secrets are stored and which cipher to use:
EasyCrypt.configure do |config|
# Choose your secrets provider. Currently supports :rails_credentials or :env_vars
# Default is :rails_credentials
config.secrets_provider = :rails_credentials
# Set your default cipher
# Default is identical to ActiveSupport::MessageEncryptor.default_cipher,
# commonly 'aes-256-gcm' or 'aes-256-cbc', depending on your Rails configuration
config.default_cipher = 'aes-256-gcm'
end
EasyCrypt expects you to define secrets for multiple "purposes". This means that if you’re encrypting user data, you can set up one pair of secret and salt for the "user_data" purpose. If you’re encrypting tokens for session authentication, you might use the "authentication" purpose with a different pair of secret and salt, and so on.
EasyCrypt currently supports two types of secrets providers: environment variables (:env_vars
) and Rails credentials (:rails_credentials
).
With Rails credentials, you can define secrets for each purpose in config/credentials.yml.enc
:
easy_crypt:
authentication:
salt: "auth-salt"
secret: "auth-secret"
user_data:
salt: "user-data-salt"
secret: "user-data-secret"
This structure allows you to define multiple purposes under the easy_crypt
key, each with its own salt
and secret
. EasyCrypt will automatically retrieve the corresponding values when encrypting or decrypting using the corresponding purpose.
The example above defines "authentication" and "user_data" purpose, but you are free to pick the names that make sense for the purposes you need, as long as you follow the same structure.
If you prefer using environment variables, define the salt and secret for each purpose in your .env
file or directly in the environment. The format is:
EASY_CRYPT_<PURPOSE>_SALT=your-salt
EASY_CRYPT_<PURPOSE>_SECRET=your-secret
For example:
EASY_CRYPT_AUTHENTICATION_SALT=auth-salt
EASY_CRYPT_AUTHENTICATION_SECRET=auth-secret
EASY_CRYPT_USER_DATA_SALT=user-data-salt
EASY_CRYPT_USER_DATA_SECRET=user-data-secret
You can then encrypt or decrypt data for these purposes.
To encrypt a piece of data with the "user_data" purpose:
encrypted_value = EasyCrypt.encrypt_user_data("Sensitive Information")
Internally, EasyCrypt automatically retrieves the secret and salt for the "user_data" purpose, using your configured secrets provider. It uses ActiveSupport::MessageEncryptor
to encrypt and sign the data.
To decrypt the data you just encrypted, call:
decrypted_value = EasyCrypt.decrypt_user_data(encrypted_value)
if decrypted_value
puts "Decrypted successfully: #{decrypted_value}"
else
puts "Invalid or expired data."
end
If anything goes wrong in the decryption process (e.g., invalid signature or message expiration when using time-based encryption), EasyCrypt returns nil
rather than raising an exception.
EasyCrypt uses method_missing
to allow dynamic encrypt/decrypt methods for different purposes, following this pattern:
encrypt_<purpose>
decrypt_<purpose>
For example, if you want data to be encrypted with a "passwords" purpose:
encrypted_pw = EasyCrypt.encrypt_passwords("super_secret")
decrypted_pw = EasyCrypt.decrypt_passwords(encrypted_pw)
Given that you have properly defined the secret and salt for the "passwords" purpose in your secrets provider.
When decryption fails (e.g., altered data or expired message), EasyCrypt returns nil
to avoid exposing decryption errors.
For method names that don't match the encrypt_<purpose>
or decrypt_<purpose>
naming pattern, a NoMethodError
will be raised.
If your secrets provider returns invalid or missing credentials, such as when salt
or secret
are missing, EasyCrypt raises a MissingSecretsConfigurationError
.
EasyCrypt uses by default the cipher returned by ActiveSupport::MessageEncryptor.default_cipher
, which depends on your application configuration.
You can override the gem’s default cipher through EasyCrypt configuration:
EasyCrypt.configure do |config|
# Choose a different cipher if desired
config.default_cipher = 'aes-128-gcm'
end
Secrets Providers can also specify cipher for a particular purpose.
In config/credentials.yml.enc
:
easy_crypt:
data_in_transit:
cipher: "aes-128-gcm"
salt: "auth-salt"
secret: "auth-secret"
Here, the data_in_transit
purpose will use the aes-128-gcm
cipher, overriding the default cipher.
You can set the cipher for a purpose using an environment variable:
EASY_CRYPT_DATA_IN_TRANSIT_CIPHER=aes-128-gcm
EASY_CRYPT_DATA_IN_TRANSIT_SALT=auth-salt
EASY_CRYPT_DATA_IN_TRANSIT_SECRET=auth-secret
Here, the data_in_transit
purpose will use the aes-128-gcm
cipher, overriding the default cipher.
EasyCrypt supports passing expires_at
or expires_in
to the underlying encrypt_and_sign
method as described in Rails' documentation. If provided, the data becomes invalid after that time:
encrypted = EasyCrypt.encrypt_user_data("Sensitive Info", expires_in: 1.hour)
If you need to handle large data encryption, consider compressing it before calling the encrypt methods. Also, avoid redundant encryption in tight loops for better performance.
This list is not exhaustive:
- Use Strong Algorithms: Choose encryption methods wisely, AES-256 for symmetric and RSA-2048+ or elliptic curves for asymmetric encryption.
- Hashing for Validation: Use secure hashing algorithms (e.g., bcrypt, Argon2) to validate secrets you don't need to store, like passwords.
- Sign for Integrity: Sign data to prevent tampering (e.g., with RSA or ECDSA).
- Store Secrets Securely: Use secret management tools (e.g., HashiCorp Vault).
- Rotate Secrets Periodically: Regularly update keys and credentials to limit exposure.
- Avoid Exposing Secrets: Never log sensitive information or hardcode secrets in code or repositories.
- Encrypt Everywhere: Encrypt data at rest, in transit, and even on trusted networks.
- Rely on Proven Libraries: Avoid building custom encryption; use trusted libraries (e.g., OpenSSL, EasyCrypt). Note: EasyCrypt uses OpenSSL under the hood.
Bug reports and pull requests are welcome on GitHub at https://github.com/randoum/easy_crypt. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
Adding a rotation feature could be a good contribution, if you feel like doing so.
The gem is available as open source under the terms of the MIT License.
Everyone interacting in the EasyCrypt project's codebases and issue trackers is expected to follow the code of conduct.