Module: AceMQ::AMQP::Keys

Defined in:
lib/acemq/amqp/crypto.rb

Overview

Getting an AES key from the shapes one usually arrives in.

Keys are plain binary strings here rather than a wrapper class, because that is what OpenSSL wants and what a secret store hands back. The wrapper worth having is EncryptionKey, which pairs a key with the name messages will refer to it by.

Constant Summary collapse

SIZES =

The three lengths AES takes. Anything else is refused rather than padded or hashed into shape, because both would make a weak key look like a strong one.

[16, 24, 32].freeze

Class Method Summary collapse

Class Method Details

.from_base64(text) ⇒ String

The usual way a key arrives from a secret store or the environment.

Parameters:

  • text (String) —

    key material, Base64 encoded

Returns:

  • (String) —

    the key



66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/acemq/amqp/crypto.rb', line 66

def self.from_base64(text)
  decoded =
    begin
      text.to_s.strip.unpack1("m0")
    rescue ArgumentError => e
      # Deliberately without the value. Whatever that string was, it was
      # meant to be a key, and a key does not belong in a message that
      # will be logged.
      raise ArgumentError, "could not read a key from Base64: #{e.message}"
    end
  from_bytes(decoded)
end

.from_bytes(bytes) ⇒ String

Returns the same bytes, checked.

Parameters:

  • bytes (String) —

    16, 24 or 32 bytes of key material

Returns:

  • (String) —

    the same bytes, checked

Raises:

  • (ArgumentError)


52
53
54
55
56
57
58
59
60
# File 'lib/acemq/amqp/crypto.rb', line 52

def self.from_bytes(bytes)
  material = bytes.to_s.dup.force_encoding(Encoding::BINARY)
  return material if SIZES.include?(material.bytesize)

  raise ArgumentError,
        "an AES key is 16, 24 or 32 bytes and this is #{material.bytesize}. If it came " \
        "from a passphrase it needs a key derivation function such as PBKDF2 or Argon2 " \
        "rather than being used as it stands."
end

.generate(bits: 256) ⇒ String

A new key from the platform's secure random.

Parameters:

  • bits (Integer) (defaults to: 256) —

    128, 192 or 256

Returns:

  • (String) —

    binary key material



41
42
43
44
45
46
47
48
# File 'lib/acemq/amqp/crypto.rb', line 41

def self.generate(bits: 256)
  bytes = bits / 8
  unless SIZES.include?(bytes)
    raise ArgumentError, "an AES key is 128, 192 or 256 bits and this asked for #{bits}"
  end

  OpenSSL::Random.random_bytes(bytes)
end

.to_base64(key) ⇒ String

For writing a generated key into a secret store.

Parameters:

  • key (String) —

    binary key material

Returns:

  • (String) —

    Base64, no line breaks



83
# File 'lib/acemq/amqp/crypto.rb', line 83

def self.to_base64(key) = [key.to_s].pack("m0")