Lockbox
- Supports Active Storage and CarrierWave
- Uses AES-GCM by default for authenticated encryption
- Makes key rotation easy
Check out this post for more info on securing sensitive data with Rails
Installation
Add this line to your application’s Gemfile:
gem 'lockbox'
Key Generation
Generate an encryption key
SecureRandom.hex(32)
Store the key with your other secrets. This is typically Rails credentials or an environment variable (dotenv is great for this). Be sure to use different keys in development and production. Keys don’t need to be hex-encoded, but it’s often easier to store them this way.
Alternatively, you can use a key management service to manage your keys.
Files
Create a box
box = Lockbox.new(key: key)
Encrypt
ciphertext = box.encrypt(File.binread("license.jpg"))
Decrypt
box.decrypt(ciphertext)
Active Storage
Add to your model:
class User < ApplicationRecord
has_one_attached :license
attached_encrypted :license, key: key
end
Works with multiple attachments as well.
class User < ApplicationRecord
has_many_attached :documents
attached_encrypted :documents, key: key
end
There are a few limitations to be aware of:
- Metadata like image width and height are not extracted when encrypted
- Direct uploads cannot be encrypted
CarrierWave
Add to your uploader:
class LicenseUploader < CarrierWave::Uploader::Base
encrypt key: key
end
Encryption is applied to all versions after processing.
Serving Files
To serve encrypted files, use a controller action.
def license
send_data @user.license.download, type: @user.license.content_type
end
Use read
instead of download
for CarrierWave.
Key Rotation
To make key rotation easy, you can pass previous versions of keys that can decrypt.
Lockbox.new(key: key, previous_versions: [{key: previous_key}])
For Active Storage use:
class User < ApplicationRecord
attached_encrypted :license, key: key, previous_versions: [{key: previous_key}]
end
To rotate existing files, use:
user.license.rotate_encryption!
For CarrierWave, use:
class LicenseUploader < CarrierWave::Uploader::Base
encrypt key: key, previous_versions: [{key: previous_key}]
end
To rotate existing files, use:
user.license.rotate_encryption!
Algorithms
AES-GCM
The default algorithm is AES-GCM with a 256-bit key. Rotate the key every 2 billion files to minimize the chance of a nonce collision, which will leak the key.
XChaCha20
Install Libsodium >= 1.0.12 and add rbnacl to your application’s Gemfile:
gem 'rbnacl'
Then pass the algorithm
option:
# files
box = Lockbox.new(key: key, algorithm: "xchacha20")
# Active Storage
class User < ApplicationRecord
attached_encrypted :license, key: key, algorithm: "xchacha20"
end
# CarrierWave
class LicenseUploader < CarrierWave::Uploader::Base
encrypt key: key, algorithm: "xchacha20"
end
Make it the default with:
Lockbox.default_options = {algorithm: "xchacha20"}
You can also pass an algorithm to previous_versions
for key rotation.
Key Management
You can use a key management service to manage your keys with KMS Encrypted.
For Active Storage, use:
class User < ApplicationRecord
attached_encrypted :license, key: :kms_key
end
For CarrierWave, use:
class LicenseUploader < CarrierWave::Uploader::Base
encrypt key: -> { model.kms_key }
end
Note: KMS Encrypted’s key rotation does not know to rotate encrypted files, so avoid calling record.rotate_kms_key!
on models with file uploads for now.
Reference
Pass associated data to encryption and decryption
box.encrypt(message, associated_data: "bingo")
box.decrypt(ciphertext, associated_data: "bingo")
History
View the changelog
Contributing
Everyone is encouraged to help improve this project. Here are a few ways you can help:
- Report bugs
- Fix bugs and submit pull requests
- Write, clarify, or fix documentation
- Suggest or add new features