lib/encryptor.rb in encryptor-1.3.0 vs lib/encryptor.rb in encryptor-2.0.0
- old
+ new
@@ -1,58 +1,66 @@
require 'openssl'
-require 'encryptor/string'
+require 'encryptor/version'
-String.send(:include, Encryptor::String)
-
# A simple wrapper for the standard OpenSSL library
module Encryptor
- autoload :Version, 'encryptor/version'
extend self
# The default options to use when calling the <tt>encrypt</tt> and <tt>decrypt</tt> methods
#
- # Defaults to { :algorithm => 'aes-256-cbc' }
+ # Defaults to { algorithm: 'aes-256-gcm',
+ # auth_data: '',
+ # insecure_mode: false,
+ # hmac_iterations: 2000 }
#
# Run 'openssl list-cipher-commands' in your terminal to view a list all cipher algorithms that are supported on your platform
def default_options
- @default_options ||= { :algorithm => 'aes-256-cbc' }
+ @default_options ||= { algorithm: 'aes-256-gcm',
+ auth_data: '',
+ insecure_mode: false,
+ hmac_iterations: 2000 }
end
- # Encrypts a <tt>:value</tt> with a specified <tt>:key</tt>
+ # Encrypts a <tt>:value</tt> with a specified <tt>:key</tt> and <tt>:iv</tt>.
#
- # Optionally accepts <tt>:iv</tt> and <tt>:algorithm</tt> options
+ # Optionally accepts <tt>:salt</tt>, <tt>:auth_data</tt>, <tt>:algorithm</tt>, <tt>:hmac_iterations</tt>, and <tt>:insecure_mode</tt> options.
#
# Example
#
- # encrypted_value = Encryptor.encrypt(:value => 'some string to encrypt', :key => 'some secret key')
+ # encrypted_value = Encryptor.encrypt(value: 'some string to encrypt', key: 'some secret key', iv: 'some unique value', salt: 'another unique value')
# # or
- # encrypted_value = Encryptor.encrypt('some string to encrypt', :key => 'some secret key')
+ # encrypted_value = Encryptor.encrypt('some string to encrypt', key: 'some secret key', iv: 'some unique value', salt: 'another unique value')
def encrypt(*args, &block)
crypt :encrypt, *args, &block
end
- # Decrypts a <tt>:value</tt> with a specified <tt>:key</tt>
+ # Decrypts a <tt>:value</tt> with a specified <tt>:key</tt> and <tt>:iv</tt>.
#
- # Optionally accepts <tt>:iv</tt> and <tt>:algorithm</tt> options
+ # Optionally accepts <tt>:salt</tt>, <tt>:auth_data</tt>, <tt>:algorithm</tt>, <tt>:hmac_iterations</tt>, and <tt>:insecure_mode</tt> options.
#
# Example
#
- # decrypted_value = Encryptor.decrypt(:value => 'some encrypted string', :key => 'some secret key')
+ # decrypted_value = Encryptor.decrypt(value: 'some encrypted string', key: 'some secret key', iv: 'some unique value', salt: 'another unique value')
# # or
- # decrypted_value = Encryptor.decrypt('some encrypted string', :key => 'some secret key')
+ # decrypted_value = Encryptor.decrypt('some encrypted string', key: 'some secret key', iv: 'some unique value', salt: 'another unique value')
def decrypt(*args, &block)
crypt :decrypt, *args, &block
end
protected
def crypt(cipher_method, *args) #:nodoc:
- options = default_options.merge(:value => args.first).merge(args.last.is_a?(Hash) ? args.last : {})
- raise ArgumentError.new('must specify a :key') if options[:key].to_s.empty?
- cipher = OpenSSL::Cipher::Cipher.new(options[:algorithm])
+ options = default_options.merge(value: args.first).merge(args.last.is_a?(Hash) ? args.last : {})
+ raise ArgumentError.new('must specify a key') if options[:key].to_s.empty?
+ cipher = OpenSSL::Cipher.new(options[:algorithm])
cipher.send(cipher_method)
+ unless options[:insecure_mode]
+ raise ArgumentError.new("key must be #{cipher.key_len} bytes or longer") if options[:key].bytesize < cipher.key_len
+ raise ArgumentError.new('must specify an iv') if options[:iv].to_s.empty?
+ raise ArgumentError.new("iv must be #{cipher.iv_len} bytes or longer") if options[:iv].bytesize < cipher.iv_len
+ end
if options[:iv]
cipher.iv = options[:iv]
if options[:salt].nil?
# Use a non-salted cipher.
# This behaviour is retained for backwards compatibility. This mode
@@ -61,15 +69,42 @@
cipher.key = options[:key]
else
# Use an explicit salt (which can be persisted into a database on a
# per-column basis, for example). This is the preferred (and more
# secure) mode of operation.
- cipher.key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(options[:key], options[:salt], 2000, cipher.key_len)
+ cipher.key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(options[:key], options[:salt], options[:hmac_iterations], cipher.key_len)
end
else
+ # This is deprecated and needs to be changed.
cipher.pkcs5_keyivgen(options[:key])
end
yield cipher, options if block_given?
- result = cipher.update(options[:value])
+ value = options[:value]
+ if cipher.authenticated?
+ if encryption?(cipher_method)
+ cipher.auth_data = options[:auth_data]
+ else
+ value = extract_cipher_text(options[:value])
+ cipher.auth_tag = extract_auth_tag(options[:value])
+ # auth_data must be set after auth_tag has been set when decrypting
+ # See http://ruby-doc.org/stdlib-2.0.0/libdoc/openssl/rdoc/OpenSSL/Cipher.html#method-i-auth_data-3D
+ cipher.auth_data = options[:auth_data]
+ end
+ end
+ result = cipher.update(value)
result << cipher.final
+ result << cipher.auth_tag if cipher.authenticated? && encryption?(cipher_method)
+ result
+ end
+
+ def encryption?(cipher_method)
+ cipher_method == :encrypt
+ end
+
+ def extract_cipher_text(value)
+ value[0..-17]
+ end
+
+ def extract_auth_tag(value)
+ value[-16..-1]
end
end