Skip to content

Encryption and decryption for Rails applications, based on ActiveSupport::MessageEncryptor, using multiple secrets providers, and defining a simple, purpose-based encryption/decryption API.

License

Notifications You must be signed in to change notification settings

randoum/easy_crypt

Repository files navigation

EasyCrypt

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.

Features

  • 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.

Table of Contents

Installation

Add this line to your application's Gemfile:

gem 'easy_crypt'

Then execute:

bundle install

Or install it yourself:

gem install easy_crypt

Configuration

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

Usage

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.

Secrets Provider Configuration

EasyCrypt currently supports two types of secrets providers: environment variables (:env_vars) and Rails credentials (:rails_credentials).

Using 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.

Using Environment Variables

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.

Encrypting Data

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.

Decrypting 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.

Dynamic Method Calls

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.

Error Handling

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.

Customizing the Cipher

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.

Using Rails Credentials

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.

Using Environment Variables

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.

Advanced Topics

Message Expiration

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)

Performance Considerations

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.

Security Best Practices

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.

Contributing

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.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the EasyCrypt project's codebases and issue trackers is expected to follow the code of conduct.

About

Encryption and decryption for Rails applications, based on ActiveSupport::MessageEncryptor, using multiple secrets providers, and defining a simple, purpose-based encryption/decryption API.

Resources

License

Code of conduct

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published