lib/blind_index.rb in blind_index-0.3.5 vs lib/blind_index.rb in blind_index-1.0.0
- old
+ new
@@ -1,105 +1,132 @@
# dependencies
require "active_support"
+require "openssl"
+require "argon2"
# modules
+require "blind_index/key_generator"
require "blind_index/model"
require "blind_index/version"
module BlindIndex
class Error < StandardError; end
class << self
attr_accessor :default_options
+ attr_writer :master_key
end
self.default_options = {}
+ def self.master_key
+ @master_key ||= ENV["BLIND_INDEX_MASTER_KEY"]
+ end
+
def self.generate_bidx(value, key:, **options)
options = {
- iterations: 10000,
- algorithm: :pbkdf2_sha256,
- insecure_key: false,
- encode: true,
- cost: {}
+ encode: true
}.merge(default_options).merge(options)
# apply expression
value = options[:expression].call(value) if options[:expression]
unless value.nil?
- algorithm = options[:algorithm].to_sym
+ algorithm = (options[:algorithm] || (options[:legacy] ? :pbkdf2_sha256 : :argon2id)).to_sym
algorithm = :pbkdf2_sha256 if algorithm == :pbkdf2_hmac
algorithm = :argon2i if algorithm == :argon2
key = key.call if key.respond_to?(:call)
raise BlindIndex::Error, "Missing key for blind index" unless key
key = key.to_s
unless options[:insecure_key] && algorithm == :pbkdf2_sha256
- # decode hex key
- if key.encoding != Encoding::BINARY && key =~ /\A[0-9a-f]{64}\z/i
- key = [key].pack("H*")
- end
-
- raise BlindIndex::Error, "Key must use binary encoding" if key.encoding != Encoding::BINARY
- raise BlindIndex::Error, "Key must be 32 bytes" if key.bytesize != 32
+ key = decode_key(key)
end
# gist to compare algorithm results
# https://gist.github.com/ankane/fe3ac63fbf1c4550ee12554c664d2b8c
- cost_options = options[:cost]
+ cost_options = options[:cost] || {}
# check size
size = (options[:size] || 32).to_i
raise BlindIndex::Error, "Size must be between 1 and 32" unless (1..32).include?(size)
value = value.to_s
value =
case algorithm
- when :scrypt
- n = cost_options[:n] || 4096
- r = cost_options[:r] || 8
- cp = cost_options[:p] || 1
- SCrypt::Engine.scrypt(value, key, n, r, cp, size)
+ when :argon2id
+ t = (cost_options[:t] || (options[:slow] ? 4 : 3)).to_i
+ # use same bounds as rbnacl
+ raise BlindIndex::Error, "t must be between 3 and 10" if t < 3 || t > 10
+
+ # m is memory in kibibytes (1024 bytes)
+ m = (cost_options[:m] || (options[:slow] ? 15 : 12)).to_i
+ # use same bounds as rbnacl
+ raise BlindIndex::Error, "m must be between 3 and 22" if m < 3 || m > 22
+
+ [Argon2::Engine.hash_argon2id(value, key, t, m, size)].pack("H*")
+ when :pbkdf2_sha256
+ iterations = cost_options[:iterations] || options[:iterations] || (options[:slow] ? 100000 : 10000)
+ OpenSSL::PKCS5.pbkdf2_hmac(value, key, iterations, size, "sha256")
when :argon2i
t = (cost_options[:t] || 3).to_i
# use same bounds as rbnacl
raise BlindIndex::Error, "t must be between 3 and 10" if t < 3 || t > 10
# m is memory in kibibytes (1024 bytes)
m = (cost_options[:m] || 12).to_i
# use same bounds as rbnacl
raise BlindIndex::Error, "m must be between 3 and 22" if m < 3 || m > 22
- # 32 byte digest size is limitation of argon2 gem
- # this is no longer the case on master
- # TODO add conditional check when next version of argon2 is released
- raise BlindIndex::Error, "Size must be 32" unless size == 32
- [Argon2::Engine.hash_argon2i(value, key, t, m)].pack("H*")
- when :pbkdf2_sha256
- iterations = cost_options[:iterations] || options[:iterations]
- OpenSSL::PKCS5.pbkdf2_hmac(value, key, iterations, size, "sha256")
+ [Argon2::Engine.hash_argon2i(value, key, t, m, size)].pack("H*")
+ when :scrypt
+ n = cost_options[:n] || 4096
+ r = cost_options[:r] || 8
+ cp = cost_options[:p] || 1
+ SCrypt::Engine.scrypt(value, key, n, r, cp, size)
else
raise BlindIndex::Error, "Unknown algorithm"
end
encode = options[:encode]
if encode
if encode.respond_to?(:call)
encode.call(value)
else
- [value].pack("m")
+ [value].pack(options[:legacy] ? "m" : "m0")
end
else
value
end
end
end
def self.generate_key
require "securerandom"
- SecureRandom.hex(32)
+ # force encoding to make JRuby consistent with MRI
+ SecureRandom.hex(32).force_encoding(Encoding::US_ASCII)
+ end
+
+ def self.index_key(table:, bidx_attribute:, master_key: nil, encode: true)
+ master_key ||= BlindIndex.master_key
+ raise BlindIndex::Error, "Missing master key" unless master_key
+
+ key = BlindIndex::KeyGenerator.new(master_key).index_key(table: table, bidx_attribute: bidx_attribute)
+ key = key.unpack("H*").first if encode
+ key
+ end
+
+ def self.decode_key(key)
+ # decode hex key
+ if key.encoding != Encoding::BINARY && key =~ /\A[0-9a-f]{64}\z/i
+ key = [key].pack("H*")
+ end
+
+ raise BlindIndex::Error, "Key must use binary encoding" if key.encoding != Encoding::BINARY
+ raise BlindIndex::Error, "Key must be 32 bytes" if key.bytesize != 32
+
+ key
end
end
ActiveSupport.on_load(:active_record) do
require "blind_index/extensions"