class SimplyAES::Cipher

The Cipher is the heart of SimplyAES, and can be used to load ciphertext or to dump a string’s ciphertext with a given key or a securely-generated one.

@see SimplyAES::Cipher

Constants

DumpError
LoadError

Public Class Methods

new(*args) click to toggle source

@overload initialize(options) @overload initialize(key, options)

@param key [String] a 32-byte (256-bit) string
  If not provided, a secure random key will be generated
@param options [Hash{Symbol=>Object}]
@option options [Symbol, SimplyAES::Format] (:bytes)
  The format is used to load provided data, including the given key,
  and as a default encoder/decoder of encrypted data; this can be
  overridden in the #load and #dump methods.
@raise [SimplyAES::Cipher::Error]

rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity

# File lib/simply-aes/cipher.rb, line 23
def initialize(*args)
  options = (args.last.is_a?(Hash) ? args.pop.dup : {})

  @format = Format[options.delete(:format) { :bytes }]

  # extract the given key, or securely generate one
  @key   = format.load(args.pop) unless args.empty?
  @key ||= native_cipher.random_key

  # validate initialisation
  fail(ArgumentError, 'invalid key length') unless @key.bytesize == 32
  fail(ArgumentError, 'wrong number of arguments') unless args.empty?
  fail(ArgumentError, "unknown options: #{options}") unless options.empty?
rescue => err
  raise Error, "failed to initialize #{self.class.name} (#{err})"
end

Public Instance Methods

decrypt(iv_ciphertext, options = {})
Alias for: load
dump(plaintext, options = {}) click to toggle source

@param plaintext [String] @param options [Hash{Symbol=>Object}] @option options [String] :iv (default: secure random iv)

up to 16 bytes, used as an initialisation vector

@option options [Symbol] :format (default: self.format) @return iv_ciphertext [String] binary string @raise SimplyAES::Cipher::DumpError

# File lib/simply-aes/cipher.rb, line 55
def dump(plaintext, options = {})
  encipher = native_cipher(:encrypt)

  # ensure a 16-byte initialisation vector
  iv = options.fetch(:iv) { encipher.random_iv }
  fail(ArgumentError, 'iv must be 16 bytes') unless iv.bytesize == 16
  encipher.iv = iv

  ciphertext = encipher.update(plaintext) + encipher.final

  format(options).dump(iv + ciphertext)
rescue => err
  raise DumpError, err.message
end
Also aliased as: encrypt
encrypt(plaintext, options = {})
Alias for: dump
format(options = {}) click to toggle source

@api private @return [SimplyAES::Format]

# File lib/simply-aes/cipher.rb, line 102
def format(options = {})
  Format[options.fetch(:format) { @format }]
end
inspect() click to toggle source

@api private @return [String]

# File lib/simply-aes/cipher.rb, line 96
def inspect
  "<#{self.class.name}:#{__id__}>"
end
key(options = {}) click to toggle source

@param options [Hash{Symbol=>Object}] @option options [Symbol] :format (default: self.format) @return [String] formatted string

# File lib/simply-aes/cipher.rb, line 44
def key(options = {})
  format(options).dump(@key.dup)
end
load(iv_ciphertext, options = {}) click to toggle source

@param iv_ciphertext [String] @option options [Symbol] :format (default: self.format) @return plaintext [String] @raise SimplyAES::Cipher::LoadError

# File lib/simply-aes/cipher.rb, line 75
def load(iv_ciphertext, options = {})
  @key || fail(ArgumentError, 'key not provided!')

  # if the IV is given as an argument, inject it to the ciphertext
  given_iv = options[:iv]
  given_iv && (iv_ciphertext = given_iv + iv_ciphertext)

  # shift the 16-byte initialisation vector from the front
  iv, ciphertext = format(options).load(iv_ciphertext).unpack('a16a*')

  decipher = native_cipher(:decrypt)
  decipher.iv = iv

  decipher.update(ciphertext) + decipher.final
rescue => err
  raise LoadError, err.message
end
Also aliased as: decrypt

Private Instance Methods

native_cipher(mode = nil) click to toggle source

Returns an AES-256-CBC OpenSSL::Cipher object pre-configured with the requested mode and our key; used internally in initialize, load, and dump.

@api private @param mode [:encode, :decode] @return [OpenSSL::Cipher]

# File lib/simply-aes/cipher.rb, line 115
def native_cipher(mode = nil)
  ::OpenSSL::Cipher.new('AES-256-CBC').tap do |cipher|
    mode && cipher.public_send(mode)
    @key && cipher.key = @key
  end
end