Lockbox

:lock: File encryption for Ruby and Rails

Supports Active Storage and CarrierWave

Uses AES-GCM by default for authenticated encryption

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 (typically Rails secrets or an environment variable).

Alternatively, you can use a key management service to manage your keys.

Files

Create a box

box = Lockbox.new(key: key)

Encrypt

box.encrypt(File.binread("license.jpg"))

Decrypt

box.decrypt(File.binread("license.jpg.enc"))

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.

XChaCha20

Install Libsodium 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: