{"id":13394877,"url":"https://github.com/ankane/lockbox","last_synced_at":"2025-11-17T14:02:20.190Z","repository":{"id":38008813,"uuid":"163880425","full_name":"ankane/lockbox","owner":"ankane","description":"Modern encryption for Ruby and Rails","archived":false,"fork":false,"pushed_at":"2025-04-20T23:13:25.000Z","size":700,"stargazers_count":1493,"open_issues_count":3,"forks_count":72,"subscribers_count":12,"default_branch":"master","last_synced_at":"2025-04-23T03:06:17.506Z","etag":null,"topics":["activerecord","activestorage","carrierwave","encryption","libsodium","mongoid"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ankane.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2019-01-02T19:21:24.000Z","updated_at":"2025-04-22T22:57:08.000Z","dependencies_parsed_at":"2023-02-17T11:01:14.631Z","dependency_job_id":"ba4b9596-dd49-472f-ba7c-458997bc647d","html_url":"https://github.com/ankane/lockbox","commit_stats":{"total_commits":867,"total_committers":14,"mean_commits":61.92857142857143,"dds":"0.30334486735870814","last_synced_commit":"9eb21af666bf07756e559748fbcdfac83d74bb40"},"previous_names":[],"tags_count":49,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ankane%2Flockbox","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ankane%2Flockbox/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ankane%2Flockbox/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ankane%2Flockbox/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ankane","download_url":"https://codeload.github.com/ankane/lockbox/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253554062,"owners_count":21926609,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["activerecord","activestorage","carrierwave","encryption","libsodium","mongoid"],"created_at":"2024-07-30T17:01:34.848Z","updated_at":"2025-11-17T14:02:20.154Z","avatar_url":"https://github.com/ankane.png","language":"Ruby","funding_links":[],"categories":["Ruby","Gems","others","Encryption"],"sub_categories":["Articles","Caching and Indexing"],"readme":"# Lockbox\n\n:package: Modern encryption for Ruby and Rails\n\n- Works with database fields, files, and strings\n- Maximizes compatibility with existing code and libraries\n- Makes migrating existing data and key rotation easy\n- Has zero dependencies and many integrations\n\nLearn [the principles behind it](https://ankane.org/modern-encryption-rails), [how to secure emails with Devise](https://ankane.org/securing-user-emails-lockbox), and [how to secure sensitive data in Rails](https://ankane.org/sensitive-data-rails).\n\n[![Build Status](https://github.com/ankane/lockbox/actions/workflows/build.yml/badge.svg)](https://github.com/ankane/lockbox/actions)\n\n## Installation\n\nAdd this line to your application’s Gemfile:\n\n```ruby\ngem \"lockbox\"\n```\n\n## Key Generation\n\nGenerate a key\n\n```ruby\nLockbox.generate_key\n```\n\nStore the key with your other secrets. This is typically Rails credentials or an environment variable ([dotenv](https://github.com/bkeepers/dotenv) is great for this). Be sure to use different keys in development and production.\n\nSet the following environment variable with your key (you can use this one in development)\n\n```sh\nLOCKBOX_MASTER_KEY=0000000000000000000000000000000000000000000000000000000000000000\n```\n\nor add it to your credentials for each environment (`rails credentials:edit --environment \u003cenv\u003e`)\n\n```yml\nlockbox:\n  master_key: \"0000000000000000000000000000000000000000000000000000000000000000\"\n```\n\nor create `config/initializers/lockbox.rb` with something like\n\n```ruby\nLockbox.master_key = Rails.application.credentials.lockbox[:master_key]\n```\n\nThen follow the instructions below for the data you want to encrypt.\n\n#### Database Fields\n\n- [Active Record](#active-record)\n- [Action Text](#action-text)\n- [Mongoid](#mongoid)\n\n#### Files\n\n- [Active Storage](#active-storage)\n- [CarrierWave](#carrierwave)\n- [Shrine](#shrine)\n- [Local Files](#local-files)\n\n#### Other\n\n- [Strings](#strings)\n\n## Active Record\n\nCreate a migration with:\n\n```ruby\nclass AddEmailCiphertextToUsers \u003c ActiveRecord::Migration[8.1]\n  def change\n    add_column :users, :email_ciphertext, :text\n  end\nend\n```\n\nAdd to your model:\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_encrypted :email\nend\n```\n\nYou can use `email` just like any other attribute.\n\n```ruby\nUser.create!(email: \"hi@example.org\")\n```\n\nIf you need to query encrypted fields, check out [Blind Index](https://github.com/ankane/blind_index).\n\n#### Multiple Fields\n\nYou can specify multiple fields in single line.\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_encrypted :email, :phone, :city\nend\n```\n\n#### Types\n\nFields are strings by default. Specify the type of a field with:\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_encrypted :birthday, type: :date\n  has_encrypted :signed_at, type: :datetime\n  has_encrypted :opens_at, type: :time\n  has_encrypted :active, type: :boolean\n  has_encrypted :salary, type: :integer\n  has_encrypted :latitude, type: :float\n  has_encrypted :longitude, type: :decimal\n  has_encrypted :video, type: :binary\n  has_encrypted :properties, type: :json\n  has_encrypted :settings, type: :hash\n  has_encrypted :messages, type: :array\n  has_encrypted :ip, type: :inet\nend\n```\n\n**Note:** Use a `text` column for the ciphertext in migrations, regardless of the type\n\nLockbox automatically works with serialized fields for maximum compatibility with existing code and libraries.\n\n```ruby\nclass User \u003c ApplicationRecord\n  serialize :properties, JSON\n  store :settings, accessors: [:color, :homepage]\n  attribute :configuration, CustomType.new\n\n  has_encrypted :properties, :settings, :configuration\nend\n```\n\nFor [Active Record Store](https://api.rubyonrails.org/classes/ActiveRecord/Store.html), encrypt the column rather than individual accessors.\n\nFor [StoreModel](https://github.com/DmitryTsepelev/store_model), use:\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_encrypted :configuration, type: Configuration.to_type\n\n  after_initialize do\n    self.configuration ||= {}\n  end\nend\n```\n\n#### Validations\n\nValidations work as expected with the exception of uniqueness. Uniqueness validations require a [blind index](https://github.com/ankane/blind_index).\n\n#### Fixtures\n\nYou can use encrypted attributes in fixtures with:\n\n```yml\ntest_user:\n  email_ciphertext: \u003c%= User.generate_email_ciphertext(\"secret\").inspect %\u003e\n```\n\nBe sure to include the `inspect` at the end or it won’t be encoded properly in YAML.\n\n#### Migrating Existing Data\n\nLockbox makes it easy to encrypt an existing column without downtime.\n\nAdd a new column for the ciphertext, then add to your model:\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_encrypted :email, migrating: true\nend\n```\n\nBackfill the data in the Rails console:\n\n```ruby\nLockbox.migrate(User)\n```\n\nThen update the model to the desired state:\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_encrypted :email\n\n  # remove this line after dropping email column\n  self.ignored_columns += [\"email\"]\nend\n```\n\nFinally, drop the unencrypted column.\n\nIf adding blind indexes, mark them as `migrating` during this process as well.\n\n```ruby\nclass User \u003c ApplicationRecord\n  blind_index :email, migrating: true\nend\n```\n\n#### Model Changes\n\nIf tracking changes to model attributes, be sure to remove or redact encrypted attributes.\n\nPaperTrail\n\n```ruby\nclass User \u003c ApplicationRecord\n  # for an encrypted history (still tracks ciphertext changes)\n  has_paper_trail skip: [:email]\n\n  # for no history (add blind indexes as well)\n  has_paper_trail skip: [:email, :email_ciphertext]\nend\n```\n\nAudited\n\n```ruby\nclass User \u003c ApplicationRecord\n  # for an encrypted history (still tracks ciphertext changes)\n  audited except: [:email]\n\n  # for no history (add blind indexes as well)\n  audited except: [:email, :email_ciphertext]\nend\n```\n\n#### Decryption\n\nTo decrypt data outside the model, use:\n\n```ruby\nUser.decrypt_email_ciphertext(user.email_ciphertext)\n```\n\n## Action Text\n\n**Note:** Action Text uses direct uploads for files, which cannot be encrypted with application-level encryption like Lockbox. This only encrypts the database field.\n\nCreate a migration with:\n\n```ruby\nclass AddBodyCiphertextToRichTexts \u003c ActiveRecord::Migration[8.1]\n  def change\n    add_column :action_text_rich_texts, :body_ciphertext, :text\n  end\nend\n```\n\nCreate `config/initializers/lockbox.rb` with:\n\n```ruby\nLockbox.encrypts_action_text_body(migrating: true)\n```\n\nMigrate existing data:\n\n```ruby\nLockbox.migrate(ActionText::RichText)\n```\n\nUpdate the initializer:\n\n```ruby\nLockbox.encrypts_action_text_body\n```\n\nAnd drop the unencrypted column.\n\n#### Options\n\nYou can pass any Lockbox options to the `encrypts_action_text_body` method.\n\n## Mongoid\n\nAdd to your model:\n\n```ruby\nclass User\n  field :email_ciphertext, type: String\n\n  has_encrypted :email\nend\n```\n\nYou can use `email` just like any other attribute.\n\n```ruby\nUser.create!(email: \"hi@example.org\")\n```\n\nIf you need to query encrypted fields, check out [Blind Index](https://github.com/ankane/blind_index).\n\nYou can [migrate existing data](#migrating-existing-data) similarly to Active Record.\n\n## Active Storage\n\nAdd to your model:\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_one_attached :license\n  encrypts_attached :license\nend\n```\n\nWorks with multiple attachments as well.\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_many_attached :documents\n  encrypts_attached :documents\nend\n```\n\nThere are a few limitations to be aware of:\n\n- Variants and previews aren’t supported when encrypted\n- Metadata like image width and height aren’t extracted when encrypted\n- Direct uploads can’t be encrypted with application-level encryption like Lockbox, but can use server-side encryption\n\nTo serve encrypted files, use a controller action.\n\n```ruby\ndef license\n  user = User.find(params[:id])\n  send_data user.license.download, type: user.license.content_type\nend\n```\n\nUse `filename` to specify a filename or `disposition: \"inline\"` to show inline.\n\n#### Migrating Existing Files\n\nLockbox makes it easy to encrypt existing files without downtime.\n\nAdd to your model:\n\n```ruby\nclass User \u003c ApplicationRecord\n  encrypts_attached :license, migrating: true\nend\n```\n\nMigrate existing files:\n\n```ruby\nLockbox.migrate(User)\n```\n\nThen update the model to the desired state:\n\n```ruby\nclass User \u003c ApplicationRecord\n  encrypts_attached :license\nend\n```\n\n## CarrierWave\n\nAdd to your uploader:\n\n```ruby\nclass LicenseUploader \u003c CarrierWave::Uploader::Base\n  encrypt\nend\n```\n\nEncryption is applied to all versions after processing.\n\nYou can mount the uploader [as normal](https://github.com/carrierwaveuploader/carrierwave#activerecord). With Active Record, this involves creating a migration:\n\n```ruby\nclass AddLicenseToUsers \u003c ActiveRecord::Migration[8.1]\n  def change\n    add_column :users, :license, :string\n  end\nend\n```\n\nAnd updating the model:\n\n```ruby\nclass User \u003c ApplicationRecord\n  mount_uploader :license, LicenseUploader\nend\n```\n\nTo serve encrypted files, use a controller action.\n\n```ruby\ndef license\n  user = User.find(params[:id])\n  send_data user.license.read, type: user.license.content_type\nend\n```\n\nUse `filename` to specify a filename or `disposition: \"inline\"` to show inline.\n\n#### Migrating Existing Files\n\nEncrypt existing files without downtime. Create a new encrypted uploader:\n\n```ruby\nclass LicenseV2Uploader \u003c CarrierWave::Uploader::Base\n  encrypt key: Lockbox.attribute_key(table: \"users\", attribute: \"license\")\nend\n```\n\nAdd a new column for the uploader, then add to your model:\n\n```ruby\nclass User \u003c ApplicationRecord\n  mount_uploader :license_v2, LicenseV2Uploader\n\n  before_save :migrate_license, if: :license_changed?\n\n  def migrate_license\n    self.license_v2 = license\n  end\nend\n```\n\nMigrate existing files:\n\n```ruby\nUser.find_each do |user|\n  if user.license? \u0026\u0026 !user.license_v2?\n    user.migrate_license\n    user.save!\n  end\nend\n```\n\nThen update the model to the desired state:\n\n```ruby\nclass User \u003c ApplicationRecord\n  mount_uploader :license, LicenseV2Uploader, mount_on: :license_v2\nend\n```\n\nFinally, delete the unencrypted files and drop the column for the original uploader. You can also remove the `key` option from the uploader.\n\n## Shrine\n\n#### Models\n\nInclude the attachment as normal:\n\n```ruby\nclass User \u003c ApplicationRecord\n  include LicenseUploader::Attachment(:license)\nend\n```\n\nAnd encrypt in a controller (or background job, etc) with:\n\n```ruby\nlicense = params.require(:user).fetch(:license)\nlockbox = Lockbox.new(key: Lockbox.attribute_key(table: \"users\", attribute: \"license\"))\nuser.license = lockbox.encrypt_io(license)\n```\n\nTo serve encrypted files, use a controller action.\n\n```ruby\ndef license\n  user = User.find(params[:id])\n  lockbox = Lockbox.new(key: Lockbox.attribute_key(table: \"users\", attribute: \"license\"))\n  send_data lockbox.decrypt(user.license.read), type: user.license.mime_type\nend\n```\n\nUse `filename` to specify a filename or `disposition: \"inline\"` to show inline.\n\n#### Non-Models\n\nGenerate a key\n\n```ruby\nkey = Lockbox.generate_key\n```\n\nCreate a lockbox\n\n```ruby\nlockbox = Lockbox.new(key: key)\n```\n\nEncrypt files before passing them to Shrine\n\n```ruby\nLicenseUploader.upload(lockbox.encrypt_io(file), :store)\n```\n\nAnd decrypt them after reading\n\n```ruby\nlockbox.decrypt(uploaded_file.read)\n```\n\n## Local Files\n\nGenerate a key\n\n```ruby\nkey = Lockbox.generate_key\n```\n\nCreate a lockbox\n\n```ruby\nlockbox = Lockbox.new(key: key)\n```\n\nEncrypt\n\n```ruby\nciphertext = lockbox.encrypt(File.binread(\"file.txt\"))\n```\n\nDecrypt\n\n```ruby\nlockbox.decrypt(ciphertext)\n```\n\n## Strings\n\nGenerate a key\n\n```ruby\nkey = Lockbox.generate_key\n```\n\nCreate a lockbox\n\n```ruby\nlockbox = Lockbox.new(key: key, encode: true)\n```\n\nEncrypt\n\n```ruby\nciphertext = lockbox.encrypt(\"hello\")\n```\n\nDecrypt\n\n```ruby\nlockbox.decrypt(ciphertext)\n```\n\nUse `decrypt_str` get the value as UTF-8\n\n## Key Rotation\n\nTo make key rotation easy, you can pass previous versions of keys that can decrypt.\n\nCreate `config/initializers/lockbox.rb` with:\n\n```ruby\nLockbox.default_options[:previous_versions] = [{master_key: previous_key}]\n```\n\nTo rotate existing Active Record \u0026 Mongoid records, use:\n\n```ruby\nLockbox.rotate(User, attributes: [:email])\n```\n\nTo rotate existing Action Text records, use:\n\n```ruby\nLockbox.rotate(ActionText::RichText, attributes: [:body])\n```\n\nTo rotate existing Active Storage files, use:\n\n```ruby\nUser.with_attached_license.find_each do |user|\n  user.license.rotate_encryption!\nend\n```\n\nTo rotate existing CarrierWave files, use:\n\n```ruby\nUser.find_each do |user|\n  user.license.rotate_encryption!\n  # or for multiple files\n  user.licenses.map(\u0026:rotate_encryption!)\nend\n```\n\nOnce everything is rotated, you can remove `previous_versions` from the initializer.\n\n### Individual Fields \u0026 Files\n\nYou can also pass previous versions to individual fields and files.\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_encrypted :email, previous_versions: [{master_key: previous_key}]\nend\n```\n\n### Local Files \u0026 Strings\n\nTo rotate local files and strings, use:\n\n```ruby\nLockbox.new(key: key, previous_versions: [{key: previous_key}])\n```\n\n## Auditing\n\nIt’s a good idea to track user and employee access to sensitive data. Lockbox provides a convenient way to do this with Active Record, but you can use a similar pattern to write audits to any location.\n\n```sh\nrails generate lockbox:audits\nrails db:migrate\n```\n\nThen create an audit wherever a user can view data:\n\n```ruby\nclass UsersController \u003c ApplicationController\n  def show\n    @user = User.find(params[:id])\n\n    LockboxAudit.create!(\n      subject: @user,\n      viewer: current_user,\n      data: [\"name\", \"email\"],\n      context: \"#{controller_name}##{action_name}\",\n      ip: request.remote_ip\n    )\n  end\nend\n```\n\nQuery audits with:\n\n```ruby\nLockboxAudit.last(100)\n```\n\n**Note:** This approach is not intended to be used in the event of a breach or insider attack, as it’s trivial for someone with access to your infrastructure to bypass.\n\n## Algorithms\n\n### AES-GCM\n\nThis is the default algorithm. It’s:\n\n- well-studied\n- NIST recommended\n- an IETF standard\n- fast thanks to a [dedicated instruction set](https://en.wikipedia.org/wiki/AES_instruction_set)\n\nLockbox uses 256-bit keys.\n\n**For users who do a lot of encryptions:** You should rotate an individual key after 2 billion encryptions to minimize the chance of a [nonce collision](https://www.cryptologie.net/article/402/is-symmetric-security-solved/), which will expose the authentication key. Each database field and file uploader use a different key (derived from the master key) to extend this window.\n\n### XSalsa20\n\nYou can also use XSalsa20, which uses an extended nonce so you don’t have to worry about nonce collisions. First, [install Libsodium](https://github.com/crypto-rb/rbnacl/wiki/Installing-libsodium). It comes preinstalled on [Heroku](https://devcenter.heroku.com/articles/stack-packages). For Homebrew, use:\n\n```sh\nbrew install libsodium\n```\n\nAnd for Ubuntu, use:\n\n```sh\nsudo apt-get install libsodium23\n```\n\nThen add to your Gemfile:\n\n```ruby\ngem \"rbnacl\"\n```\n\nAnd add to your model:\n\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_encrypted :email, algorithm: \"xsalsa20\"\nend\n```\n\nMake it the default with:\n\n```ruby\nLockbox.default_options[:algorithm] = \"xsalsa20\"\n```\n\nYou can also pass an algorithm to `previous_versions` for key rotation.\n\n## Hybrid Cryptography\n\n[Hybrid cryptography](https://en.wikipedia.org/wiki/Hybrid_cryptosystem) allows servers to encrypt data without being able to decrypt it.\n\nFollow the instructions above for installing Libsodium and including `rbnacl` in your Gemfile.\n\nGenerate a key pair with:\n\n```ruby\nLockbox.generate_key_pair\n```\n\nStore the keys with your other secrets. Then use:\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_encrypted :email, algorithm: \"hybrid\", encryption_key: encryption_key, decryption_key: decryption_key\nend\n```\n\nMake sure `decryption_key` is `nil` on servers that shouldn’t decrypt.\n\nThis uses X25519 for key exchange and XSalsa20 for encryption.\n\n## Key Configuration\n\nLockbox supports a few different ways to set keys for database fields and files.\n\n1. Master key\n2. Per field/uploader\n3. Per record\n\n### Master Key\n\nBy default, the master key is used to generate unique keys for each field/uploader. This technique comes from [CipherSweet](https://ciphersweet.paragonie.com/internals/key-hierarchy). The table name and column/uploader name are both used in this process.\n\nYou can get an individual key with:\n\n```ruby\nLockbox.attribute_key(table: \"users\", attribute: \"email_ciphertext\")\n```\n\nTo rename a table with encrypted columns/uploaders, use:\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_encrypted :email, key_table: \"original_table\"\nend\n```\n\nTo rename an encrypted column itself, use:\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_encrypted :email, key_attribute: \"original_column\"\nend\n```\n\n### Per Field/Uploader\n\nTo set a key for an individual field/uploader, use a string:\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_encrypted :email, key: ENV[\"USER_EMAIL_ENCRYPTION_KEY\"]\nend\n```\n\nOr a proc:\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_encrypted :email, key: -\u003e { code }\nend\n```\n\n### Per Record\n\nTo use a different key for each record, use a symbol:\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_encrypted :email, key: :some_method\nend\n```\n\nOr a proc:\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_encrypted :email, key: -\u003e { some_method }\nend\n```\n\n## Key Management\n\nYou can use a key management service to manage your keys with [KMS Encrypted](https://github.com/ankane/kms_encrypted).\n\nFor Active Record and Mongoid, use:\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_encrypted :email, key: :kms_key\nend\n```\n\nFor Action Text, use:\n\n```ruby\nActiveSupport.on_load(:action_text_rich_text) do\n  ActionText::RichText.has_kms_key\nend\n\nLockbox.encrypts_action_text_body(key: :kms_key)\n```\n\nFor Active Storage, use:\n\n```ruby\nclass User \u003c ApplicationRecord\n  encrypts_attached :license, key: :kms_key\nend\n```\n\nFor CarrierWave, use:\n\n```ruby\nclass LicenseUploader \u003c CarrierWave::Uploader::Base\n  encrypt key: -\u003e { model.kms_key }\nend\n```\n\n**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.\n\n## Data Leakage\n\nWhile encryption hides the content of a message, an attacker can still get the length of the message (since the length of the ciphertext is the length of the message plus a constant number of bytes).\n\nLet’s say you want to encrypt the status of a candidate’s background check. Valid statuses are `clear`, `consider`, and `fail`. Even with the data encrypted, it’s trivial to map the ciphertext to a status.\n\n```ruby\nlockbox = Lockbox.new(key: key)\nlockbox.encrypt(\"fail\").bytesize      # 32\nlockbox.encrypt(\"clear\").bytesize     # 33\nlockbox.encrypt(\"consider\").bytesize  # 36\n```\n\nAdd padding to conceal the exact length of messages.\n\n```ruby\nlockbox = Lockbox.new(key: key, padding: true)\nlockbox.encrypt(\"fail\").bytesize      # 44\nlockbox.encrypt(\"clear\").bytesize     # 44\nlockbox.encrypt(\"consider\").bytesize  # 44\n```\n\nThe block size for padding is 16 bytes by default. Lockbox uses [ISO/IEC 7816-4](https://en.wikipedia.org/wiki/Padding_(cryptography)#ISO/IEC_7816-4) padding, which uses at least one byte, so if we have a status larger than 15 bytes, it will have a different length than the others.\n\n```ruby\nbox.encrypt(\"length15status!\").bytesize   # 44\nbox.encrypt(\"length16status!!\").bytesize  # 60\n```\n\nChange the block size with:\n\n```ruby\nLockbox.new(padding: 32) # bytes\n```\n\n## Associated Data\n\nYou can pass extra context during encryption to make sure encrypted data isn’t moved to a different context.\n\n```ruby\nlockbox = Lockbox.new(key: key)\nciphertext = lockbox.encrypt(message, associated_data: \"somecontext\")\n```\n\nWithout the same context, decryption will fail.\n\n```ruby\nlockbox.decrypt(ciphertext, associated_data: \"somecontext\")  # success\nlockbox.decrypt(ciphertext, associated_data: \"othercontext\") # fails\n```\n\nYou can also use it with database fields and files.\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_encrypted :email, associated_data: -\u003e { code }\nend\n```\n\n## Binary Columns\n\nYou can use `binary` columns for the ciphertext instead of `text` columns.\n\n```ruby\nclass AddEmailCiphertextToUsers \u003c ActiveRecord::Migration[8.1]\n  def change\n    add_column :users, :email_ciphertext, :binary\n  end\nend\n```\n\nDisable Base64 encoding to save space.\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_encrypted :email, encode: false\nend\n```\n\nor set it globally:\n\n```ruby\nLockbox.encode_attributes = false\n```\n\n## Compatibility\n\nIt’s easy to read encrypted data in another language if needed.\n\nFor AES-GCM, the format is:\n\n- nonce (IV) - 12 bytes\n- ciphertext - variable length\n- authentication tag - 16 bytes\n\nHere are [some examples](docs/Compatibility.md).\n\nFor XSalsa20, use the appropriate [Libsodium library](https://libsodium.gitbook.io/doc/bindings_for_other_languages).\n\n## Migrating from Another Library\n\nLockbox makes it easy to migrate from another library without downtime. The example below uses `attr_encrypted` but the same approach should work for any library.\n\nLet’s suppose your model looks like this:\n\n```ruby\nclass User \u003c ApplicationRecord\n  attr_encrypted :name, key: key\n  attr_encrypted :email, key: key\nend\n```\n\nCreate a migration with:\n\n```ruby\nclass MigrateToLockbox \u003c ActiveRecord::Migration[8.1]\n  def change\n    add_column :users, :name_ciphertext, :text\n    add_column :users, :email_ciphertext, :text\n  end\nend\n```\n\nAnd add `has_encrypted` to your model with the `migrating` option:\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_encrypted :name, :email, migrating: true\nend\n```\n\nThen run:\n\n```ruby\nLockbox.migrate(User)\n```\n\nOnce all records are migrated, remove the `migrating` option and the previous model code (the `attr_encrypted` methods in this example).\n\n```ruby\nclass User \u003c ApplicationRecord\n  has_encrypted :name, :email\nend\n```\n\nThen remove the previous gem from your Gemfile and drop its columns.\n\n```ruby\nclass RemovePreviousEncryptedColumns \u003c ActiveRecord::Migration[8.1]\n  def change\n    remove_column :users, :encrypted_name, :text\n    remove_column :users, :encrypted_name_iv, :text\n    remove_column :users, :encrypted_email, :text\n    remove_column :users, :encrypted_email_iv, :text\n  end\nend\n```\n\n## History\n\nView the [changelog](https://github.com/ankane/lockbox/blob/master/CHANGELOG.md)\n\n## Contributing\n\nEveryone is encouraged to help improve this project. Here are a few ways you can help:\n\n- [Report bugs](https://github.com/ankane/lockbox/issues)\n- Fix bugs and [submit pull requests](https://github.com/ankane/lockbox/pulls)\n- Write, clarify, or fix documentation\n- Suggest or add new features\n\nTo get started with development, [install Libsodium](https://github.com/crypto-rb/rbnacl/wiki/Installing-libsodium) and run:\n\n```sh\ngit clone https://github.com/ankane/lockbox.git\ncd lockbox\nbundle install\nbundle exec rake test\n```\n\nFor security issues, send an email to the address on [this page](https://github.com/ankane).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fankane%2Flockbox","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fankane%2Flockbox","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fankane%2Flockbox/lists"}