### Development Setup and Testing Source: https://github.com/ankane/lockbox/blob/master/README.md Instructions for setting up the development environment, including installing Libsodium, and running tests. ```sh git clone https://github.com/ankane/lockbox.git cd lockbox bundle install bundle exec rake test ``` -------------------------------- ### Install Libsodium for Ubuntu Source: https://github.com/ankane/lockbox/blob/master/README.md Use this command to install Libsodium on Ubuntu systems. ```bash sudo apt-get install libsodium23 ``` -------------------------------- ### Encrypt/Decrypt Strings with Lockbox Source: https://github.com/ankane/lockbox/blob/master/README.md Generate a key and create a Lockbox instance with encoding enabled. Encrypt strings and decrypt them. Use `decrypt_str` to get the value as UTF-8. ```ruby key = Lockbox.generate_key ``` ```ruby lockbox = Lockbox.new(key: key, encode: true) ``` ```ruby ciphertext = lockbox.encrypt("hello") ``` ```ruby lockbox.decrypt(ciphertext) ``` -------------------------------- ### Serve Encrypted CarrierWave File Source: https://github.com/ankane/lockbox/blob/master/README.md Create a controller action to serve encrypted files uploaded via CarrierWave. Use the `read` method to get the file content. ```ruby def license user = User.find(params[:id]) send_data user.license.read, type: user.license.content_type end ``` -------------------------------- ### Encrypt Action Text Body with Lockbox Source: https://context7.com/ankane/lockbox/llms.txt Encrypts the `body` column of `ActionText::RichText`. Use `migrating: true` for initial setup and `Lockbox.migrate` for backfilling. ```ruby class AddBodyCiphertextToRichTexts < ActiveRecord::Migration[8.1] def change add_column :action_text_rich_texts, :body_ciphertext, :text end end ``` ```ruby Lockbox.encrypts_action_text_body(migrating: true) ``` ```ruby Lockbox.migrate(ActionText::RichText) ``` ```ruby Lockbox.encrypts_action_text_body ``` ```ruby ActiveSupport.on_load(:action_text_rich_text) do ActionText::RichText.has_kms_key end ``` ```ruby Lockbox.encrypts_action_text_body(key: :kms_key) ``` -------------------------------- ### Get Individual Attribute Key Source: https://github.com/ankane/lockbox/blob/master/README.md Retrieve the specific key used for encrypting a particular attribute in a table. ```ruby Lockbox.attribute_key(table: "users", attribute: "email_ciphertext") ``` -------------------------------- ### Decrypt with Python AES-256-GCM Source: https://github.com/ankane/lockbox/blob/master/docs/Compatibility.md Decrypt ciphertext using Python's cryptography library. Install the 'cryptography' package. Key should be in hex format and ciphertext Base64 encoded. Skip Base64 decoding for files. ```python from cryptography.hazmat.primitives.ciphers.aead import AESGCM from base64 import b64decode key = '61e6ba4a3a2498e3a8fdcd047eff0cd9864016f2c83c34599a3257a57ce6f7fb' ciphertext = 'Uv/+Sgar0kM216AvVlBH5Gt8vIwtQGfPysl539WY2DER62AoJg==' key = bytes.fromhex(key) ciphertext = b64decode(ciphertext) # skip for files aesgcm = AESGCM(key) plaintext = aesgcm.decrypt(ciphertext[:12], ciphertext[12:], b'') print(plaintext) ``` -------------------------------- ### Configure Lockbox to Encrypt Action Text Body (Migrating) Source: https://github.com/ankane/lockbox/blob/master/README.md Create an initializer to configure Lockbox for Action Text encryption. Set `migrating: true` when initially encrypting existing data. ```ruby Lockbox.encrypts_action_text_body(migrating: true) ``` -------------------------------- ### Low-Level API with XSalsa20 Algorithm Source: https://context7.com/ankane/lockbox/llms.txt Use the low-level Lockbox API with the XSalsa20 algorithm. Generate a key, create a Lockbox instance with the specified algorithm, and perform encryption/decryption. ```ruby # XSalsa20 also works in the low-level API lockbox = Lockbox.new(key: Lockbox.generate_key, algorithm: "xsalsa20") ciphertext = lockbox.encrypt("hello") lockbox.decrypt(ciphertext) # => "hello" ``` -------------------------------- ### Encrypt Shrine Uploads Source: https://context7.com/ankane/lockbox/llms.txt Create a `Lockbox::Encryptor` instance with a derived key for Shrine. Use `encrypt_io` before passing to Shrine and `decrypt` after reading. ```ruby # In a controller or background job license = params.require(:user).fetch(:license) # ActionDispatch::Http::UploadedFile lockbox = Lockbox.new(key: Lockbox.attribute_key(table: "users", attribute: "license")) user.license = lockbox.encrypt_io(license) user.save! # Serve via controller def license user = User.find(params[:id]) lockbox = Lockbox.new(key: Lockbox.attribute_key(table: "users", attribute: "license")) send_data lockbox.decrypt(user.license.read), type: user.license.mime_type, filename: user.license_data["metadata"]["filename"] end ``` -------------------------------- ### Configure Lockbox to Encrypt Action Text Body (Production) Source: https://github.com/ankane/lockbox/blob/master/README.md After migrating data, update the initializer to remove the `migrating: true` option for standard Action Text body encryption. ```ruby Lockbox.encrypts_action_text_body ``` -------------------------------- ### Encrypt/Decrypt Local Files with Shrine Source: https://github.com/ankane/lockbox/blob/master/README.md Generate a key and create a Lockbox instance. Encrypt files before uploading them to Shrine and decrypt them after reading. ```ruby key = Lockbox.generate_key ``` ```ruby lockbox = Lockbox.new(key: key) ``` ```ruby LicenseUploader.upload(lockbox.encrypt_io(file), :store) ``` ```ruby lockbox.decrypt(uploaded_file.read) ``` -------------------------------- ### Serve Encrypted Active Storage File Source: https://github.com/ankane/lockbox/blob/master/README.md Create a controller action to serve encrypted files. This involves downloading the encrypted data and sending it with the correct content type. ```ruby def license user = User.find(params[:id]) send_data user.license.download, type: user.license.content_type end ``` -------------------------------- ### Encrypt/Decrypt Local Files Source: https://github.com/ankane/lockbox/blob/master/README.md Generate a key and create a Lockbox instance. Encrypt the content of a local file before storing it and decrypt it when needed. ```ruby key = Lockbox.generate_key ``` ```ruby lockbox = Lockbox.new(key: key) ``` ```ruby ciphertext = lockbox.encrypt(File.binread("file.txt")) ``` ```ruby lockbox.decrypt(ciphertext) ``` -------------------------------- ### Encrypt Local Files and Strings with Lockbox Source: https://context7.com/ankane/lockbox/llms.txt Use `Lockbox.new` to create an `Encryptor` for binary data or strings. Pass `encode: true` for Base64-encoded ciphertext. ```ruby key = Lockbox.generate_key lockbox = Lockbox.new(key: key, encode: true) # Encrypt / decrypt a string ciphertext = lockbox.encrypt("hello world") # => "abc123==" (Base64) plaintext = lockbox.decrypt(ciphertext) # => "hello world" plaintext_utf8 = lockbox.decrypt_str(ciphertext) # forces UTF-8 encoding # => "hello world" # Encrypt / decrypt a local file raw_ciphertext = Lockbox.new(key: key).encrypt(File.binread("secret.txt")) File.binwrite("secret.txt.enc", raw_ciphertext) decrypted = Lockbox.new(key: key).decrypt(File.binread("secret.txt.enc")) # Encrypt an IO object (preserves filename and content_type metadata) file_io = File.open("report.pdf") encrypted = lockbox.encrypt_io(file_io) # returns Lockbox::IO ``` -------------------------------- ### Zero-Downtime Migration with Lockbox Source: https://context7.com/ankane/lockbox/llms.txt Use `Lockbox.migrate` to backfill existing plaintext rows to `*_ciphertext` columns. The `migrating: true` flag allows simultaneous reading from both columns. ```ruby # Step 1 — add the ciphertext column class AddEmailCiphertextToUsers < ActiveRecord::Migration[8.1] def change add_column :users, :email_ciphertext, :text end end # Step 2 — enable migration mode class User < ApplicationRecord has_encrypted :email, migrating: true end # Step 3 — backfill in production console Lockbox.migrate(User) # batch_size: 1000 by default Lockbox.migrate(User, batch_size: 500, restart: false) # skip already-migrated rows # Step 4 — cut over class User < ApplicationRecord has_encrypted :email self.ignored_columns += ["email"] # keep until column is dropped end # Step 5 — drop the plaintext column class DropEmailFromUsers < ActiveRecord::Migration[8.1] def change remove_column :users, :email end end ``` -------------------------------- ### Set Global Default Algorithm to XSalsa20 Source: https://context7.com/ankane/lockbox/llms.txt Set XSalsa20 as the global default encryption algorithm for all Lockbox operations by modifying `Lockbox.default_options`. ```ruby # Make XSalsa20 the global default Lockbox.default_options[:algorithm] = "xsalsa20" ``` -------------------------------- ### Rotate Encryption for Local Files and Strings Source: https://github.com/ankane/lockbox/blob/master/README.md When creating a Lockbox instance for local files or strings, you can pass `previous_versions` to the constructor to enable key rotation for these data types. ```ruby Lockbox.new(key: key, previous_versions: [{key: previous_key}]) ``` -------------------------------- ### Per-Record Key Configuration Source: https://context7.com/ankane/lockbox/llms.txt Configure per-record encryption keys by providing a symbol that resolves to an instance method. This method should return the key for the specific record. ```ruby class User < ApplicationRecord has_encrypted :notes, key: :per_record_key def per_record_key KeyVault.fetch(id) end end ``` -------------------------------- ### Set Per-Record Encryption Key with Proc Source: https://github.com/ankane/lockbox/blob/master/README.md Use a proc to dynamically determine the encryption key for each record. ```ruby class User < ApplicationRecord has_encrypted :email, key: -> { some_method } end ``` -------------------------------- ### Create User with Encrypted Email Source: https://github.com/ankane/lockbox/blob/master/README.md Demonstrates creating a new user record with an encrypted email attribute. Lockbox handles the encryption automatically when the attribute is set. ```ruby User.create!(email: "hi@example.org") ``` -------------------------------- ### Configure KMS for Action Text Source: https://github.com/ankane/lockbox/blob/master/README.md Set up Action Text to use KMS for encrypting rich text bodies. Ensure to call `has_kms_key` on the ActionText::RichText model. ```ruby ActiveSupport.on_load(:action_text_rich_text) do ActionText::RichText.has_kms_key end Lockbox.encrypts_action_text_body(key: :kms_key) ``` -------------------------------- ### Encrypt with XSalsa20 Algorithm Source: https://github.com/ankane/lockbox/blob/master/README.md Configure a model to use the XSalsa20 encryption algorithm for a specific attribute. ```ruby class User < ApplicationRecord has_encrypted :email, algorithm: "xsalsa20" end ``` -------------------------------- ### Generate Lockbox Audits Source: https://github.com/ankane/lockbox/blob/master/README.md Run the `lockbox:audits` generator and migrate the database to set up the necessary schema for auditing data access. Then, create an audit record whenever sensitive data is viewed. ```sh rails generate lockbox:audits rails db:migrate ``` ```ruby class UsersController < ApplicationController def show @user = User.find(params[:id]) LockboxAudit.create!( subject: @user, viewer: current_user, data: ["name", "email"], context: "#{controller_name}##{action_name}", ip: request.remote_ip ) end end ``` ```ruby LockboxAudit.last(100) ``` -------------------------------- ### Mark Active Storage License for Migration Source: https://github.com/ankane/lockbox/blob/master/README.md When encrypting existing Active Storage files, mark the attachment with `migrating: true`. ```ruby class User < ApplicationRecord encrypts_attached :license, migrating: true end ``` -------------------------------- ### Encrypt StoreModel Configuration Source: https://github.com/ankane/lockbox/blob/master/README.md For StoreModel, encrypt the entire configuration attribute. Initialize the attribute to an empty hash after initialization if it might be nil. ```ruby class User < ApplicationRecord has_encrypted :configuration, type: Configuration.to_type after_initialize do self.configuration ||= {} end end ``` -------------------------------- ### Migrate Blind Index for User Email Source: https://github.com/ankane/lockbox/blob/master/README.md If using blind indexes, mark them as `migrating: true` during the data migration process to ensure they are also updated. ```ruby class User < ApplicationRecord blind_index :email, migrating: true end ``` -------------------------------- ### Integrate Lockbox with Shrine Models Source: https://github.com/ankane/lockbox/blob/master/README.md Include the attachment in your model and encrypt data in a controller or background job using a Lockbox instance with the appropriate key. Serve encrypted files using a controller action. ```ruby class User < ApplicationRecord include LicenseUploader::Attachment(:license) end ``` ```ruby license = params.require(:user).fetch(:license) lockbox = Lockbox.new(key: Lockbox.attribute_key(table: "users", attribute: "license")) user.license = lockbox.encrypt_io(license) ``` ```ruby def license user = User.find(params[:id]) lockbox = Lockbox.new(key: Lockbox.attribute_key(table: "users", attribute: "license")) send_data lockbox.decrypt(user.license.read), type: user.license.mime_type end ``` -------------------------------- ### Configure Hybrid Encryption Source: https://github.com/ankane/lockbox/blob/master/README.md Set up a model to use hybrid encryption, providing both encryption and decryption keys. Ensure decryption_key is nil on servers that should not decrypt. ```ruby class User < ApplicationRecord has_encrypted :email, algorithm: "hybrid", encryption_key: encryption_key, decryption_key: decryption_key end ``` -------------------------------- ### Set XSalsa20 as Default Algorithm Source: https://github.com/ankane/lockbox/blob/master/README.md Set XSalsa20 as the default encryption algorithm for all Lockbox operations. ```ruby Lockbox.default_options[:algorithm] = "xsalsa20" ``` -------------------------------- ### Per-Field Explicit Key Configuration Source: https://context7.com/ankane/lockbox/llms.txt Configure per-field encryption keys using a string or a proc that fetches the key. This allows for different keys for different attributes. ```ruby class User < ApplicationRecord has_encrypted :email, key: ENV["USER_EMAIL_KEY"] has_encrypted :phone, key: -> { fetch_key_from_vault } end ``` -------------------------------- ### Configure Lockbox for Key Rotation Source: https://github.com/ankane/lockbox/blob/master/README.md To facilitate key rotation, configure `previous_versions` in an initializer. This allows Lockbox to decrypt data encrypted with older keys. ```ruby Lockbox.default_options[:previous_versions] = [{master_key: previous_key}] ``` -------------------------------- ### Encrypt Active Storage Files with KMS Source: https://github.com/ankane/lockbox/blob/master/README.md Configure Active Storage to encrypt attached files using a KMS key. ```ruby class User < ApplicationRecord encrypts_attached :license, key: :kms_key end ``` -------------------------------- ### Set Per-Field Encryption Key with Proc Source: https://github.com/ankane/lockbox/blob/master/README.md Assign a specific encryption key to a field using a proc that returns the key. ```ruby class User < ApplicationRecord has_encrypted :email, key: -> { code } end ``` -------------------------------- ### Configure Lockbox Master Key in Initializer Source: https://github.com/ankane/lockbox/blob/master/README.md Configure the Lockbox master key in an initializer file, typically by referencing your Rails credentials. This ensures Lockbox uses the correct key upon application startup. ```ruby Lockbox.master_key = Rails.application.credentials.lockbox[:master_key] ``` -------------------------------- ### Backfill Encrypted Data for Users Source: https://github.com/ankane/lockbox/blob/master/README.md Run this command in the Rails console to backfill encrypted data for the User model. Ensure the model has `has_encrypted` defined. ```ruby Lockbox.migrate(User) ``` -------------------------------- ### Generate Encryption Key Source: https://github.com/ankane/lockbox/blob/master/README.md Use this method to generate a new encryption key. Store this key securely with your other secrets, using different keys for development and production environments. ```ruby Lockbox.generate_key ``` -------------------------------- ### Create Migration for Action Text Body Ciphertext Source: https://github.com/ankane/lockbox/blob/master/README.md Generate a migration to add a new `body_ciphertext` column to the `action_text_rich_texts` table. This column will store the encrypted content. ```ruby class AddBodyCiphertextToRichTexts < ActiveRecord::Migration[8.1] def change add_column :action_text_rich_texts, :body_ciphertext, :text end end ``` -------------------------------- ### Configure Binary Columns and Disable Base64 Encoding Source: https://context7.com/ankane/lockbox/llms.txt Use binary columns for encrypted data to save space. Disable Base64 encoding per attribute or globally. ```ruby class AddEmailCiphertextToUsers < ActiveRecord::Migration[8.1] def change add_column :users, :email_ciphertext, :binary end end ``` ```ruby class User < ApplicationRecord has_encrypted :email, encode: false end ``` ```ruby Lockbox.encode_attributes = false ``` -------------------------------- ### Custom Block Size for Padding Source: https://context7.com/ankane/lockbox/llms.txt Configure a custom block size for padding when encrypting data. This allows for different ciphertext sizes based on the specified block size. ```ruby # Custom block size padded_box32 = Lockbox.new(key: key, padding: 32) padded_box32.encrypt("fail").bytesize # => 60 ``` -------------------------------- ### Set Per-Record Encryption Key with Symbol Source: https://github.com/ankane/lockbox/blob/master/README.md Use a symbol to specify a method that returns the encryption key for each record. ```ruby class User < ApplicationRecord has_encrypted :email, key: :some_method end ``` -------------------------------- ### Migrate Action Text Rich Text Data Source: https://github.com/ankane/lockbox/blob/master/README.md Run this command in the Rails console to encrypt existing Action Text content. This assumes the `body_ciphertext` column has been added and the initializer is configured. ```ruby Lockbox.migrate(ActionText::RichText) ``` -------------------------------- ### Encrypt Existing Files with CarrierWave Source: https://github.com/ankane/lockbox/blob/master/README.md Define a new uploader to encrypt existing files. Mount it to your model and use a before_save hook to migrate data. Update the model to use the new uploader and then remove the old column. ```ruby class LicenseV2Uploader < CarrierWave::Uploader::Base encrypt key: Lockbox.attribute_key(table: "users", attribute: "license") end ``` ```ruby class User < ApplicationRecord mount_uploader :license_v2, LicenseV2Uploader before_save :migrate_license, if: :license_changed? def migrate_license self.license_v2 = license end end ``` ```ruby User.find_each do |user| if user.license? && !user.license_v2? user.migrate_license user.save! end end ``` ```ruby class User < ApplicationRecord mount_uploader :license, LicenseV2Uploader, mount_on: :license_v2 end ``` -------------------------------- ### Adding Binary Ciphertext Column Source: https://github.com/ankane/lockbox/blob/master/README.md Use binary columns for ciphertext storage instead of text columns to potentially save space. ```ruby class AddEmailCiphertextToUsers < ActiveRecord::Migration[8.1] def change add_column :users, :email_ciphertext, :binary end end ``` -------------------------------- ### Generate Lockbox Audit Model and Migration Source: https://context7.com/ankane/lockbox/llms.txt Generate the `LockboxAudit` model and its corresponding database migration using the provided Rails generator command. ```bash # Generate the audit model and migration # rails generate lockbox:audits # rails db:migrate ``` -------------------------------- ### Set Per-Field Encryption Key with ENV Variable Source: https://github.com/ankane/lockbox/blob/master/README.md Assign a specific encryption key to a field using an environment variable. ```ruby class User < ApplicationRecord has_encrypted :email, key: ENV["USER_EMAIL_ENCRYPTION_KEY"] end ``` -------------------------------- ### Use XSalsa20 Algorithm for Encryption Source: https://context7.com/ankane/lockbox/llms.txt Configure `has_encrypted` to use the XSalsa20 algorithm for a specific field. This algorithm uses an extended nonce, suitable for high-volume encryption. ```ruby # Use XSalsa20 for a specific field class User < ApplicationRecord has_encrypted :email, algorithm: "xsalsa20" end ``` -------------------------------- ### Create Migration for CarrierWave Encrypted Column Source: https://github.com/ankane/lockbox/blob/master/README.md Create a migration to add a column to store the CarrierWave uploaded file. This column will hold the encrypted file data. ```ruby class AddLicenseToUsers < ActiveRecord::Migration[8.1] def change add_column :users, :license, :string end end ``` -------------------------------- ### Derive and Use Encryption Key Source: https://context7.com/ankane/lockbox/llms.txt Derive a key for a specific attribute and use it to encrypt data with the low-level API. The derived key is a 64-character hex string. ```ruby key = Lockbox.attribute_key(table: "users", attribute: "email_ciphertext") # => "61e6ba4a..." (64-char hex string) # Use the derived key directly lockbox = Lockbox.new(key: key) ciphertext = lockbox.encrypt("sensitive value") ``` -------------------------------- ### Insert and Upsert Data with Lockbox Source: https://context7.com/ankane/lockbox/llms.txt Use these methods to insert or upsert records, with Lockbox automatically encrypting specified attributes. ```ruby User.insert({ email: "a@example.com", phone: "555-1234" }) ``` ```ruby User.insert_all([ { email: "b@example.com", phone: "555-0001" }, { email: "c@example.com", phone: "555-0002" } ]) ``` ```ruby User.upsert_all( [{ id: 1, email: "updated@example.com" }], unique_by: :id ) ``` -------------------------------- ### Encrypt CarrierWave Uploads Source: https://context7.com/ankane/lockbox/llms.txt Add `encrypt` to a CarrierWave uploader to encrypt all versions. Uses the master key by default or an explicit key. ```ruby class LicenseUploader < CarrierWave::Uploader::Base encrypt # uses master key by default # Or use an explicit key: # encrypt key: ENV["LICENSE_ENCRYPTION_KEY"] end class User < ApplicationRecord mount_uploader :license, LicenseUploader end # Serve via controller class UsersController < ApplicationController def license user = User.find(params[:id]) send_data user.license.read, type: user.license.content_type, filename: File.basename(user.license.path) end end ``` -------------------------------- ### Migrate Encrypted Email Column in User Model Source: https://github.com/ankane/lockbox/blob/master/README.md Add a new ciphertext column and mark it for migration. This is used for encrypting existing data without downtime. ```ruby class User < ApplicationRecord has_encrypted :email, migrating: true end ``` -------------------------------- ### Encrypt Multiple Active Storage Documents Source: https://github.com/ankane/lockbox/blob/master/README.md Encrypt multiple files attached to a model using `encrypts_attached` with `has_many_attached`. ```ruby class User < ApplicationRecord has_many_attached :documents encrypts_attached :documents end ``` -------------------------------- ### Generate Key Pair for Hybrid Cryptography Source: https://github.com/ankane/lockbox/blob/master/README.md Generate a public and private key pair for use with hybrid encryption. ```ruby Lockbox.generate_key_pair ``` -------------------------------- ### Add Lockbox Gem to Gemfile Source: https://github.com/ankane/lockbox/blob/master/README.md Add this line to your application's Gemfile to include the Lockbox gem. ```ruby gem "lockbox" ``` -------------------------------- ### Rotate Encryption for Active Storage Files Source: https://github.com/ankane/lockbox/blob/master/README.md Iterate through records with attached files and call `rotate_encryption!` on each attachment to rotate its encryption key. ```ruby User.with_attached_license.find_each do |user| user.license.rotate_encryption! end ``` -------------------------------- ### Rotate Encryption Keys for Action Text Source: https://github.com/ankane/lockbox/blob/master/README.md Use the `Lockbox.rotate` class method to rotate encryption keys for the `body` attribute of Action Text records. ```ruby Lockbox.rotate(ActionText::RichText, attributes: [:body]) ``` -------------------------------- ### Changing Padding Block Size Source: https://github.com/ankane/lockbox/blob/master/README.md Customize the block size for padding if the default 16 bytes is not suitable. ```ruby Lockbox.new(padding: 32) # bytes ``` -------------------------------- ### Encrypt CarrierWave Uploads with KMS Source: https://github.com/ankane/lockbox/blob/master/README.md Configure a CarrierWave uploader to encrypt files using a KMS key, dynamically fetching the key from the model. ```ruby class LicenseUploader < CarrierWave::Uploader::Base encrypt key: -> { model.kms_key } end ``` -------------------------------- ### Mount CarrierWave Uploader in User Model Source: https://github.com/ankane/lockbox/blob/master/README.md Mount the configured CarrierWave uploader to the User model. This links the model attribute to the uploader. ```ruby class User < ApplicationRecord mount_uploader :license, LicenseUploader end ``` -------------------------------- ### Create Migration for Encrypted Database Field Source: https://github.com/ankane/lockbox/blob/master/README.md Generate a migration to add a new text column for storing encrypted data. Use a 'text' type for the ciphertext regardless of the original data type. ```ruby class AddEmailCiphertextToUsers < ActiveRecord::Migration[8.1] def change add_column :users, :email_ciphertext, :text end end ``` -------------------------------- ### Configure Padding with has_encrypted Source: https://context7.com/ankane/lockbox/llms.txt Enable padding for a specific attribute using `has_encrypted`. This ensures that the ciphertext length is consistent regardless of the plaintext length. ```ruby # Padding also works with has_encrypted class User < ApplicationRecord has_encrypted :status, padding: true end ``` -------------------------------- ### Configure CarrierWave Uploader for Encryption Source: https://github.com/ankane/lockbox/blob/master/README.md Add the `encrypt` directive to your CarrierWave uploader class to enable encryption for all processed versions of the uploaded file. ```ruby class LicenseUploader < CarrierWave::Uploader::Base encrypt end ``` -------------------------------- ### Encrypt with Associated Data Source: https://context7.com/ankane/lockbox/llms.txt Encrypt data with associated data to bind the ciphertext to a specific context. Decryption will fail if the associated data does not match. ```ruby key = Lockbox.generate_key lockbox = Lockbox.new(key: key) ciphertext = lockbox.encrypt("sensitive", associated_data: "user:42:email") lockbox.decrypt(ciphertext, associated_data: "user:42:email") # => "sensitive" lockbox.decrypt(ciphertext, associated_data: "user:99:email") # raises Lockbox::DecryptionError ``` -------------------------------- ### Key Rotation with Lockbox Source: https://context7.com/ankane/lockbox/llms.txt Use `Lockbox.rotate` to re-encrypt records with the current key. `previous_versions` allows decryption of data encrypted with old keys during the transition. ```ruby # config/initializers/lockbox.rb — add previous key Lockbox.default_options[:previous_versions] = [{ master_key: ENV["LOCKBOX_PREVIOUS_MASTER_KEY"] }] # Rotate Active Record / Mongoid records Lockbox.rotate(User, attributes: [:email, :phone]) # Rotate Action Text bodies Lockbox.rotate(ActionText::RichText, attributes: [:body]) # Rotate Active Storage files User.with_attached_license.find_each do |user| user.license.rotate_encryption! end # Rotate CarrierWave files User.find_each do |user| user.license.rotate_encryption! end # Per-field previous versions (overrides global config for this attribute) class User < ApplicationRecord has_encrypted :email, previous_versions: [{ master_key: ENV["OLD_MASTER_KEY"] }] end # Rotate local files / strings lockbox = Lockbox.new(key: new_key, previous_versions: [{ key: old_key }]) new_ciphertext = lockbox.encrypt(lockbox.decrypt(old_ciphertext)) ``` -------------------------------- ### Use KMS Key for Active Record/Mongoid Source: https://github.com/ankane/lockbox/blob/master/README.md Configure a model to use a KMS key for encryption with Active Record or Mongoid. ```ruby class User < ApplicationRecord has_encrypted :email, key: :kms_key end ``` -------------------------------- ### Configure Hybrid Encryption with has_encrypted Source: https://context7.com/ankane/lockbox/llms.txt Configure `has_encrypted` for hybrid encryption, specifying the algorithm and encryption/decryption keys. Leave `decryption_key` nil on servers that must not decrypt. ```ruby class User < ApplicationRecord has_encrypted :medical_notes, algorithm: "hybrid", encryption_key: ENV["ENCRYPTION_KEY"], decryption_key: ENV["DECRYPTION_KEY"] # nil on servers that must not decrypt end user = User.create!(medical_notes: "Allergic to penicillin") user.medical_notes # => "Allergic to penicillin" (only on servers with decryption_key) ``` -------------------------------- ### Encrypting with Associated Data Source: https://github.com/ankane/lockbox/blob/master/README.md Pass extra context during encryption to ensure encrypted data is not moved to a different context. Decryption will fail without the same associated data. ```ruby lockbox = Lockbox.new(key: key) ciphertext = lockbox.encrypt(message, associated_data: "somecontext") ``` ```ruby lockbox.decrypt(ciphertext, associated_data: "somecontext") # success lockbox.decrypt(ciphertext, associated_data: "othercontext") # fails ``` -------------------------------- ### Set Master Key via Environment Variable Source: https://github.com/ankane/lockbox/blob/master/README.md Set the LOCKBOX_MASTER_KEY environment variable with your generated key. This is a common method for storing secrets, especially with tools like dotenv. ```sh LOCKBOX_MASTER_KEY=0000000000000000000000000000000000000000000000000000000000000000 ``` -------------------------------- ### Encrypt Active Storage License Attachment Source: https://github.com/ankane/lockbox/blob/master/README.md Use `encrypts_attached` to encrypt files attached to a model. This applies to single attachments like a license file. ```ruby class User < ApplicationRecord has_one_attached :license encrypts_attached :license end ``` -------------------------------- ### Encrypt with Padding to Conceal Plaintext Length Source: https://context7.com/ankane/lockbox/llms.txt Encrypt data using padding to ensure all short values produce the same ciphertext size, preventing length-based inference attacks. ISO/IEC 7816-4 padding is used by default. ```ruby key = Lockbox.generate_key lockbox = Lockbox.new(key: key) # Without padding — length leaks content lockbox.encrypt("fail").bytesize # => 32 lockbox.encrypt("clear").bytesize # => 33 lockbox.encrypt("consider").bytesize # => 36 # With padding — all short values produce the same ciphertext size padded_box = Lockbox.new(key: key, padding: true) # 16-byte blocks padded_box.encrypt("fail").bytesize # => 44 padded_box.encrypt("clear").bytesize # => 44 padded_box.encrypt("consider").bytesize # => 44 ``` -------------------------------- ### Use Encrypted Attributes in Fixtures Source: https://github.com/ankane/lockbox/blob/master/README.md When using encrypted attributes in fixtures, generate the ciphertext using `User.generate_email_ciphertext` and ensure you append `.inspect` for proper YAML encoding. ```yaml test_user: email_ciphertext: <%= User.generate_email_ciphertext("secret").inspect %> ``` -------------------------------- ### Add rbnacl to Gemfile Source: https://github.com/ankane/lockbox/blob/master/README.md Include the rbnacl gem in your application's Gemfile to use XSalsa20 encryption. ```ruby gem "rbnacl" ``` -------------------------------- ### Configure PaperTrail to Skip Encrypted Attributes Source: https://github.com/ankane/lockbox/blob/master/README.md When using PaperTrail with encrypted attributes, specify which attributes to skip to avoid tracking ciphertext changes or to exclude encrypted fields entirely. ```ruby class User < ApplicationRecord # for an encrypted history (still tracks ciphertext changes) has_paper_trail skip: [:email] # for no history (add blind indexes as well) has_paper_trail skip: [:email, :email_ciphertext] end ``` -------------------------------- ### Generate Master Key for Lockbox Source: https://context7.com/ankane/lockbox/llms.txt Generates a random 64-character hex string for use as a master key. Recommended to store in Rails credentials or environment variables. ```ruby # Generate a master key master_key = Lockbox.generate_key # => "a3f2c1e8b4d7..." (64 hex chars) # Store in Rails credentials (recommended) # rails credentials:edit --environment production # lockbox: # master_key: "a3f2c1e8b4d7..." # Or set via environment variable ENV["LOCKBOX_MASTER_KEY"] = master_key # Or configure explicitly in an initializer # config/initializers/lockbox.rb Lockbox.master_key = Rails.application.credentials.lockbox[:master_key] ``` -------------------------------- ### Set Master Key in Rails Credentials Source: https://github.com/ankane/lockbox/blob/master/README.md Alternatively, store the master key within your Rails credentials for each environment. This is a secure way to manage secrets for different deployment environments. ```yaml lockbox: master_key: "0000000000000000000000000000000000000000000000000000000000000000" ``` -------------------------------- ### Migration from attr_encrypted to Lockbox Source: https://github.com/ankane/lockbox/blob/master/README.md Facilitate migration from other encryption libraries like attr_encrypted without downtime. This involves creating new columns, enabling the `migrating` option, running the migration, and then cleaning up old columns and code. ```ruby class User < ApplicationRecord attr_encrypted :name, key: key attr_encrypted :email, key: key end ``` ```ruby class MigrateToLockbox < ActiveRecord::Migration[8.1] def change add_column :users, :name_ciphertext, :text add_column :users, :email_ciphertext, :text end end ``` ```ruby class User < ApplicationRecord has_encrypted :name, :email, migrating: true end ``` ```ruby Lockbox.migrate(User) ``` ```ruby class User < ApplicationRecord has_encrypted :name, :email end ``` ```ruby class RemovePreviousEncryptedColumns < ActiveRecord::Migration[8.1] def change remove_column :users, :encrypted_name, :text remove_column :users, :encrypted_name_iv, :text remove_column :users, :encrypted_email, :text remove_column :users, :encrypted_email_iv, :text end end ``` -------------------------------- ### Rename Table for Key Derivation Source: https://github.com/ankane/lockbox/blob/master/README.md Specify a different table name for key derivation if the table has been renamed. ```ruby class User < ApplicationRecord has_encrypted :email, key_table: "original_table" end ``` -------------------------------- ### Decrypt with Rust AES-256-GCM Source: https://github.com/ankane/lockbox/blob/master/docs/Compatibility.md Decrypt ciphertext using Rust's aes-gcm crate. Add dependencies to Cargo.toml. Key should be hex decoded and ciphertext Base64 decoded. Skip Base64 decoding for files. ```toml [dependencies] aes-gcm = "0.10.3" base64 = "0.22.1" hex = "0.4.3" ``` ```rust use aes_gcm::aead::{generic_array::GenericArray, Aead}; use aes_gcm::{Aes256Gcm, Key, KeyInit}; use base64::prelude::*; fn main() { let key = hex::decode("61e6ba4a3a2498e3a8fdcd047eff0cd9864016f2c83c34599a3257a57ce6f7fb").expect("decode failure!"); let ciphertext = BASE64_STANDARD.decode("Uv/+Sgar0kM216AvVlBH5Gt8vIwtQGfPysl539WY2DER62AoJg==").expect("decode failure!"); let key = Key::::from_slice(&key); let aead = Aes256Gcm::new(key); let nonce = GenericArray::from_slice(&ciphertext[..12]); let plaintext_bytes = aead.decrypt(nonce, &ciphertext[12..]).expect("decryption failure!"); let plaintext = String::from_utf8(plaintext_bytes).expect("utf8 failure!"); println!("{:?}", plaintext); } ``` -------------------------------- ### Generate Hybrid Encryption Key Pair Source: https://context7.com/ankane/lockbox/llms.txt Generate an X25519 key pair for hybrid encryption. This key pair should be generated once and stored securely. The `encryption_key` and `decryption_key` are returned. ```ruby # gem "rbnacl" in Gemfile; brew install libsodium # Generate a key pair (do this once, store securely) pair = Lockbox.generate_key_pair # => { encryption_key: "...", decryption_key: "..." } ``` -------------------------------- ### Active Record: Encrypt Database Fields Source: https://context7.com/ankane/lockbox/llms.txt Defines model attributes to be stored encrypted in a paired `*_ciphertext` column. Reads and writes use the plain attribute name. Ensure a migration adds the corresponding ciphertext columns. ```ruby # Migration class AddEmailCiphertextToUsers < ActiveRecord::Migration[8.1] def change add_column :users, :email_ciphertext, :text # always :text, regardless of type add_column :users, :birthday_ciphertext, :text add_column :users, :salary_ciphertext, :text add_column :users, :settings_ciphertext, :text add_column :users, :properties_ciphertext, :text end end ``` ```ruby # Model class User < ApplicationRecord has_encrypted :email # string (default) has_encrypted :birthday, type: :date has_encrypted :signed_at, type: :datetime has_encrypted :active, type: :boolean has_encrypted :salary, type: :integer has_encrypted :latitude, type: :float has_encrypted :properties, type: :json # serialized automatically has_encrypted :settings, type: :hash has_encrypted :tags, type: :array has_encrypted :ip, type: :inet end ``` ```ruby # Usage — identical to unencrypted attributes user = User.create!(email: "hi@example.org", salary: 95_000, active: true) user.email # => "hi@example.org" (decrypted on access) user.salary # => 95000 user.active # => true # Decrypt ciphertext outside the model User.decrypt_email_ciphertext(user.email_ciphertext) # => "hi@example.org" # Use in fixtures # test/fixtures/users.yml # test_user: # email_ciphertext: <%= User.generate_email_ciphertext("secret").inspect %> ``` -------------------------------- ### Using Associated Data with ActiveRecord Source: https://github.com/ankane/lockbox/blob/master/README.md Associate encrypted fields with dynamic context, such as a user's code, for enhanced security. ```ruby class User < ApplicationRecord has_encrypted :email, associated_data: -> { code } end ``` -------------------------------- ### Record Audit Event in Controller Source: https://context7.com/ankane/lockbox/llms.txt Log an audit event when sensitive data is accessed in a controller action. This involves creating a `LockboxAudit` record with details about the subject, viewer, data, context, and IP address. ```ruby # Record an audit event in your controller class UsersController < ApplicationController def show @user = User.find(params[:id]) LockboxAudit.create!( subject: @user, viewer: current_user, data: ["email", "phone", "ssn"], context: "#{controller_name}##{action_name}", ip: request.remote_ip ) end end ``` -------------------------------- ### Update Active Storage Model After Migration Source: https://github.com/ankane/lockbox/blob/master/README.md After migrating Active Storage files, update the model to remove the `migrating: true` option. ```ruby class User < ApplicationRecord encrypts_attached :license end ``` -------------------------------- ### Query Recent Audit Events Source: https://context7.com/ankane/lockbox/llms.txt Query the `LockboxAudit` model to retrieve recent audit events or filter them based on specific criteria, such as the viewer or creation timestamp. ```ruby # Query recent audits LockboxAudit.last(100) LockboxAudit.where(viewer: current_user).order(created_at: :desc) ``` -------------------------------- ### Active Storage: Encrypt File Attachments Source: https://context7.com/ankane/lockbox/llms.txt Adds transparent encryption to Active Storage attachments. Files are encrypted before upload and decrypted on download. Serve files through a controller action. ```ruby class User < ApplicationRecord has_one_attached :license has_many_attached :documents encrypts_attached :license encrypts_attached :documents end # Upload — no code change required user.license.attach(io: File.open("license.pdf"), filename: "license.pdf", content_type: "application/pdf") # Download via controller (required — cannot serve direct URLs for encrypted files) class UsersController < ApplicationController def license user = User.find(params[:id]) send_data user.license.download, type: user.license.content_type, filename: user.license.filename.to_s, disposition: "inline" end end ``` -------------------------------- ### Rotate Encryption Keys for Active Record & Mongoid Source: https://github.com/ankane/lockbox/blob/master/README.md Use the `Lockbox.rotate` class method to rotate encryption keys for specified attributes on Active Record and Mongoid records. ```ruby Lockbox.rotate(User, attributes: [:email]) ``` -------------------------------- ### Decrypt with Java AES-256-GCM Source: https://github.com/ankane/lockbox/blob/master/docs/Compatibility.md Decrypt ciphertext using Java's JCE API. Key should be hex decoded and ciphertext Base64 decoded. Skip Base64 decoding for files. ```java import java.util.Base64; import java.util.HexFormat; import javax.crypto.Cipher; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; public class Example { public static void main(String[] args) throws Exception { String key = "61e6ba4a3a2498e3a8fdcd047eff0cd9864016f2c83c34599a3257a57ce6f7fb"; String ciphertext = "Uv/+Sgar0kM216AvVlBH5Gt8vIwtQGfPysl539WY2DER62AoJg=="; byte[] keyBytes = HexFormat.of().parseHex(key); byte[] ciphertextBytes = Base64.getDecoder().decode(ciphertext); Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyBytes, "AES"), new GCMParameterSpec(128, ciphertextBytes, 0, 12)); byte[] plaintextBytes = cipher.doFinal(ciphertextBytes, 12, ciphertextBytes.length - 12); String plaintext = new String(plaintextBytes); System.out.println(plaintext); } } ``` -------------------------------- ### Configure Mongoid User Model for Encryption Source: https://github.com/ankane/lockbox/blob/master/README.md Add a `email_ciphertext` field to your Mongoid model and use `has_encrypted :email` to enable encryption for the email attribute. ```ruby class User field :email_ciphertext, type: String has_encrypted :email end ``` -------------------------------- ### Rotate Encryption for CarrierWave Files Source: https://github.com/ankane/lockbox/blob/master/README.md Iterate through records and call `rotate_encryption!` on each file attachment. This method can be called on single files or collections of files. ```ruby User.find_each do |user| user.license.rotate_encryption! # or for multiple files user.licenses.map(&:rotate_encryption!) end ``` -------------------------------- ### Configure Associated Data with has_encrypted Source: https://context7.com/ankane/lockbox/llms.txt Configure associated data for `has_encrypted` using a proc that is evaluated per record. This binds the encrypted attribute to a context derived from the record's attributes. ```ruby # With has_encrypted (proc evaluated per record) class User < ApplicationRecord has_encrypted :notes, associated_data: -> { "user:#{id}:notes" } end ``` -------------------------------- ### Decrypt with PHP AES-256-GCM Source: https://github.com/ankane/lockbox/blob/master/docs/Compatibility.md Decrypt ciphertext using PHP's openssl_decrypt function. Key should be hex decoded and ciphertext Base64 decoded. Skip Base64 decoding for files. ```php $key = "61e6ba4a3a2498e3a8fdcd047eff0cd9864016f2c83c34599a3257a57ce6f7fb"; $ciphertext = "Uv/+Sgar0kM216AvVlBH5Gt8vIwtQGfPysl539WY2DER62AoJg=="; $key = hex2bin($key); $ciphertext = base64_decode($ciphertext, true); $nonce = substr($ciphertext, 0, 12); $tag = substr($ciphertext, -16); $ciphertext = substr($ciphertext, 12, -16); $plaintext = openssl_decrypt($ciphertext, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $nonce, $tag); echo $plaintext . "\n"; ``` -------------------------------- ### Update User Model After Data Migration Source: https://github.com/ankane/lockbox/blob/master/README.md After migrating data, update the User model to use the encrypted attribute and ignore the original unencrypted column. Remove the `ignored_columns` line once the original column is dropped. ```ruby class User < ApplicationRecord has_encrypted :email # remove this line after dropping email column self.ignored_columns += ["email"] end ``` -------------------------------- ### Decrypt with Elixir AES-256-GCM Source: https://github.com/ankane/lockbox/blob/master/docs/Compatibility.md Decrypt ciphertext using Elixir's :crypto module. Key should be hex decoded and ciphertext Base64 decoded. Skip Base64 decoding for files. ```elixir {:ok, key} = Base.decode16("61e6ba4a3a2498e3a8fdcd047eff0cd9864016f2c83c34599a3257a57ce6f7fb", case: :lower) {:ok, ciphertext} = Base.decode64("Uv/+Sgar0kM216AvVlBH5Gt8vIwtQGfPysl539WY2DER62AoJg==") data_size = byte_size(ciphertext) - 28 <> = ciphertext plaintext = :crypto.crypto_one_time_aead(:aes_256_gcm, key, nonce, data, "", tag, false) IO.puts(plaintext) ``` -------------------------------- ### Derive Attribute Key with Lockbox Source: https://context7.com/ankane/lockbox/llms.txt `Lockbox.attribute_key` derives a unique key for a table+attribute from the master key using HKDF-SHA384. This is used for per-field `Lockbox::Encryptor` instances. ```ruby key = Lockbox.attribute_key(table: "users", attribute: "email") ``` -------------------------------- ### Mongoid: Encrypt Document Fields Source: https://context7.com/ankane/lockbox/llms.txt Works similarly to ActiveRecord. Declare the ciphertext field explicitly, then use `has_encrypted` for the virtual plaintext attribute. ```ruby class User include Mongoid::Document field :email_ciphertext, type: String has_encrypted :email end user = User.create!(email: "hi@example.org") user.email # => "hi@example.org" ``` -------------------------------- ### Encrypting with Padding to Prevent Data Leakage Source: https://github.com/ankane/lockbox/blob/master/README.md Use padding to conceal the exact length of encrypted messages. The block size for padding is 16 bytes by default and follows ISO/IEC 7816-4. ```ruby lockbox = Lockbox.new(key: key) lockbox.encrypt("fail").bytesize # 32 lockbox.encrypt("clear").bytesize # 33 lockbox.encrypt("consider").bytesize # 36 ``` ```ruby lockbox = Lockbox.new(key: key, padding: true) lockbox.encrypt("fail").bytesize # 44 lockbox.encrypt("clear").bytesize # 44 lockbox.encrypt("consider").bytesize # 44 ``` ```ruby box.encrypt("length15status!").bytesize # 44 box.encrypt("length16status!!").bytesize # 60 ``` -------------------------------- ### Decrypt with Node.js AES-256-GCM Source: https://github.com/ankane/lockbox/blob/master/docs/Compatibility.md Decrypt ciphertext using Node.js crypto module. Ensure the key is in hex format and ciphertext is Base64 encoded. Skip Base64 decoding for files. ```javascript const crypto = require('crypto') let key = '61e6ba4a3a2498e3a8fdcd047eff0cd9864016f2c83c34599a3257a57ce6f7fb' let ciphertext = 'Uv/+Sgar0kM216AvVlBH5Gt8vIwtQGfPysl539WY2DER62AoJg==' key = Buffer.from(key, 'hex') ciphertext = Buffer.from(ciphertext, 'base64') // skip for files let nonce = ciphertext.slice(0, 12) let auth_tag = ciphertext.slice(-16) ciphertext = ciphertext.slice(12, -16) let aesgcm = crypto.createDecipheriv('aes-256-gcm', key, nonce) aesgcm.setAuthTag(auth_tag) let plaintext = aesgcm.update(ciphertext) + aesgcm.final() console.log(plaintext) ```