README.md in blind_index-0.3.3 vs README.md in blind_index-0.3.4

- old
+ new

@@ -2,61 +2,66 @@ Securely search encrypted database fields Designed for use with [attr_encrypted](https://github.com/attr-encrypted/attr_encrypted) -Here’s a [full example](https://ankane.org/securing-user-emails-in-rails) of how to use it with Devise +Here’s a [full example](https://ankane.org/securing-user-emails-in-rails) of how to use it [![Build Status](https://travis-ci.org/ankane/blind_index.svg?branch=master)](https://travis-ci.org/ankane/blind_index) ## How It Works -We use [this approach](https://www.sitepoint.com/how-to-search-on-securely-encrypted-database-fields/) by Scott Arciszewski. To summarize, we compute a keyed hash of the sensitive data and store it in a column. To query, we apply the keyed hash function (PBKDF2-HMAC-SHA256 by default) to the value we’re searching and then perform a database search. This results in performant queries for equality operations, while keeping the data secure from those without the key. +We use [this approach](https://paragonie.com/blog/2017/05/building-searchable-encrypted-databases-with-php-and-sql) by Scott Arciszewski. To summarize, we compute a keyed hash of the sensitive data and store it in a column. To query, we apply the keyed hash function (PBKDF2-SHA256 by default) to the value we’re searching and then perform a database search. This results in performant queries for equality operations, while keeping the data secure from those without the key. -## Getting Started +## Installation -Add these lines to your application’s Gemfile: +Add this line to your application’s Gemfile: ```ruby -gem 'attr_encrypted' gem 'blind_index' ``` -Add columns for the encrypted data and the blind index +## Getting Started -```ruby -# encrypted data -add_column :users, :encrypted_email, :string -add_column :users, :encrypted_email_iv, :string +> Note: Your model should already be set up with attr_encrypted. The examples are for a `User` model with `attr_encrypted :email`. See the [full example](https://ankane.org/securing-user-emails-in-rails) if needed. -# blind index +Create a migration to add a column for the blind index + +```ruby add_column :users, :encrypted_email_bidx, :string add_index :users, :encrypted_email_bidx ``` And add to your model ```ruby class User < ApplicationRecord - attr_encrypted :email, key: [ENV["EMAIL_ENCRYPTION_KEY"]].pack("H*") blind_index :email, key: [ENV["EMAIL_BLIND_INDEX_KEY"]].pack("H*") end ``` -We use environment variables to store the keys as hex-encoded strings ([dotenv](https://github.com/bkeepers/dotenv) is great for this). [Here’s an explanation](https://ankane.org/encryption-keys) of why `pack` is used. *Do not commit them to source control.* Generate one key for encryption and one key for hashing. You can generate keys in the Rails console with: +We use an environment variable to store the key as a hex-encoded string ([dotenv](https://github.com/bkeepers/dotenv) is great for this). [Here’s an explanation](https://ankane.org/encryption-keys) of why `pack` is used. *Do not commit it to source control.* This should be different than the key you use for encryption. You can generate a key in the Rails console with: ```ruby SecureRandom.hex(32) ``` -For development, you can use these: +For development, you can use this: ```sh -EMAIL_ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000 EMAIL_BLIND_INDEX_KEY=ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff ``` +Backfill existing records + +```ruby +User.find_each do |user| + user.compute_email_bidx + user.save! +end +``` + And query away ```ruby User.where(email: "test@example.org") ``` @@ -90,22 +95,31 @@ ```ruby add_column :users, :encrypted_email_ci_bidx, :string add_index :users, :encrypted_email_ci_bidx ``` -And update your model +Update your model ```ruby class User < ApplicationRecord blind_index :email, ... blind_index :email_ci, attribute: :email, expression: ->(v) { v.downcase } ... end ``` -Search with: +Backfill existing records ```ruby +User.find_each do |user| + user.compute_email_ci_bidx + user.save! +end +``` + +And query away + +```ruby User.where(email_ci: "test@example.org") ``` ## Index Only @@ -126,85 +140,67 @@ ```ruby class User < ApplicationRecord attribute :initials - # must come before blind_index method + # must come before the blind_index method so it runs first before_validation :set_initials, if: -> { changes.key?(:first_name) || changes.key?(:last_name) } + blind_index :initials, ... def set_initials self.initials = "#{first_name[0]}#{last_name[0]}" end end ``` *Requires ActiveRecord 5.1+* -## Fixtures - -You can use encrypted attributes and blind indexes in fixtures with: - -```yml -test_user: - encrypted_email: <%= User.encrypt_email("test@example.org", iv: Base64.decode64("0000000000000000")) %> - encrypted_email_iv: "0000000000000000" - encrypted_email_bidx: <%= User.compute_email_bidx("test@example.org").inspect %> -``` - -Be sure to include the `inspect` at the end, or it won’t be encoded properly in YAML. - ## Algorithms -### PBKDF2-HMAC-SHA256 +### PBKDF2-SHA256 -The default hashing algorithm. [Key stretching](https://en.wikipedia.org/wiki/Key_stretching) increases the amount of time required to compute hashes, which slows down brute-force attacks. You can set the number of iterations with: +The default hashing algorithm. [Key stretching](https://en.wikipedia.org/wiki/Key_stretching) increases the amount of time required to compute hashes, which slows down brute-force attacks. +The default number of iterations is 10,000. For highly sensitive fields, set this to at least 100,000. + ```ruby class User < ApplicationRecord - blind_index :email, iterations: 1000000, ... + blind_index :email, iterations: 100000, ... end ``` -The default is `10000`. Changing this value requires you to recompute the blind index. +> Changing this requires you to recompute the blind index. -### scrypt +### Argon2 -Add [scrypt](https://github.com/pbhogan/scrypt) to your Gemfile and use: +Argon2 is the state-of-the-art algorithm and recommended for best security. +To use it, add [argon2](https://github.com/technion/ruby-argon2) to your Gemfile and set: + ```ruby class User < ApplicationRecord - blind_index :email, algorithm: :scrypt, ... + blind_index :email, algorithm: :argon2, ... end ``` -Set the cost parameters with: +The default cost parameters are `{t: 3, m: 12}`. For highly sensitive fields, set this to at least `{t: 4, m: 15}`. ```ruby class User < ApplicationRecord - blind_index :email, algorithm: :scrypt, cost: {n: 4096, r: 8, p: 1}, ... + blind_index :email, algorithm: :argon2, cost: {t: 4, m: 15}, ... end ``` -### Argon2 +> Changing this requires you to recompute the blind index. -Add [argon2](https://github.com/technion/ruby-argon2) to your Gemfile and use: +The variant used is Argon2i. -```ruby -class User < ApplicationRecord - blind_index :email, algorithm: :argon2, ... -end -``` +### Other -Set the cost parameters with: +scrypt is [also supported](docs/scrypt.md). Unless you have specific reasons to use it, go with Argon2 instead. -```ruby -class User < ApplicationRecord - blind_index :email, algorithm: :argon2, cost: {t: 3, m: 12}, ... -end -``` - ## Key Rotation To rotate keys without downtime, add a new column: ```ruby @@ -241,16 +237,37 @@ end ``` Finally, drop the old column. +## Fixtures + +You can use encrypted attributes and blind indexes in fixtures with: + +```yml +test_user: + encrypted_email: <%= User.encrypt_email("test@example.org", iv: Base64.decode64("0000000000000000")) %> + encrypted_email_iv: "0000000000000000" + encrypted_email_bidx: <%= User.compute_email_bidx("test@example.org").inspect %> +``` + +Be sure to include the `inspect` at the end, or it won’t be encoded properly in YAML. + ## Reference By default, blind indexes are encoded in Base64. Set a different encoding with: ```ruby class User < ApplicationRecord blind_index :email, encode: ->(v) { [v].pack("H*") } +end +``` + +By default, blind indexes are 32 bytes. Set a smaller size with: + +```ruby +class User < ApplicationRecord + blind_index :email, size: 16 end ``` ## Alternatives