Class: AEAD::Cipher

Inherits:
Object
  • Object
show all
Defined in:
lib/aead/cipher.rb

Overview

Wraps AEAD ciphers in a simplified interface.

Defined Under Namespace

Modules: AES_HMAC Classes: AES_256_CBC_HMAC_SHA_256, AES_256_CTR_HMAC_SHA_256, AES_256_GCM

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.generate_keyString

Returns a securely-generated key of appropriate length for the current Cipher.

Returns:

  • (String)

    a random key



36
37
38
# File 'lib/aead/cipher.rb', line 36

def self.generate_key
  SecureRandom.random_bytes(self.key_len)
end

.generate_nonceString

Returns a unique nonce for the current Cipher.

Returns:

  • (String)

    a random key



45
46
47
# File 'lib/aead/cipher.rb', line 45

def self.generate_nonce
  AEAD::Nonce.generate
end

.new(algorithm) ⇒ Class

Returns a particular Cipher implementation.

Parameters:

  • algorithm (String)

    the AEAD implementation to use

Returns:

  • (Class)

    the cipher implementation



21
22
23
24
25
26
27
28
# File 'lib/aead/cipher.rb', line 21

def self.new(algorithm)
  # run normal Class#new if we're being called from a subclass
  return super unless self == AEAD::Cipher

  # TODO: ciphers should register themselves, as opposed to using a
  # potentiall-unsafe const_get
  self.const_get algorithm.tr('-', '_').upcase
end

.signature_compare(left, right) ⇒ Boolean

Does a constant-time comparison between two strings. Useful to avoid timing attacks when comparing a generated signature against a user-provided signature.

Parameters:

  • left (String)

    any string

  • right (String)

    any string

Returns:

  • (Boolean)

    whether or not the strings are equal



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/aead/cipher.rb', line 58

def self.signature_compare(left, right)
  # short-circuit if the lengths are inequal
  return false if left.to_s.bytesize != right.bytesize

  # Constant-time string comparison algorithm:
  #   1. Break both strings into bytes
  #   2. Subtract the strings from one-another, byte by byte
  #      (any non-equal bytes will subtract to a nonzero value)
  #   3. OR the XOR'd bytes together
  #   4. If the result is nonzero, the strings differed.
  left   = left.bytes.to_a
  right  = right.bytes.to_a
  result = 0

  left.length.times do |i|
    result |= left[i] - right[i]
  end

  result == 0
end

Instance Method Details

#decrypt(nonce, aad, aead) ⇒ String

Decrypts a plaintext using the current Cipher.

Parameters:

  • nonce (String)

    the nonce used when encrypting the AEAD

  • aad (String)

    the additional authentication data used when encrypting the AEAD

  • aead (String)

    the encrypted AEAD

Returns:

  • (String)

    the original plaintext



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/aead/cipher.rb', line 140

def decrypt(nonce, aad, aead)
  nonce.force_encoding('ASCII-8BIT') if
    nonce.respond_to?(:force_encoding)

  aad.force_encoding('ASCII-8BIT') if
    aad.respond_to?(:force_encoding)

  aead.force_encoding('ASCII-8BIT') if
    aead.respond_to?(:force_encoding)

  _verify_nonce_bytesize(nonce, self.nonce_len)

  self._decrypt(
    _pad_nonce(nonce),
    aad,
    _extract_ciphertext(aead, self.tag_len),
    _extract_tag(aead, self.tag_len)
  )
end

#encrypt(nonce, aad, plaintext) ⇒ String

Encrypts a plaintext using the current Cipher.

IMPORTANT: Do not ever encrypt data using the same nonce more than once given a particular secret key. Doing so will violate the security guarantees of the AEAD cipher.

Parameters:

  • nonce (String)

    a unique nonce, never before used with the current encryption key

  • aad (String, nil)

    arbitrary additional authentication data that must later be provided to decrypt the aead

  • plaintext (String)

    arbitrary plaintext

Returns:

  • (String)

    the generated AEAD



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/aead/cipher.rb', line 111

def encrypt(nonce, aad, plaintext)
  nonce.force_encoding('ASCII-8BIT') if
    nonce.respond_to?(:force_encoding)

  aad.force_encoding('ASCII-8BIT') if
    aad.respond_to?(:force_encoding)

  plaintext.force_encoding('ASCII-8BIT') if
    plaintext.respond_to?(:force_encoding)

  _verify_nonce_bytesize(nonce, self.nonce_len)
  _verify_plaintext_presence(plaintext)

  self._encrypt(
     _pad_nonce(nonce),
     aad,
     plaintext
  )
end

#key_lenInteger

The length of keys of encryption keys used by the current Cipher.

Returns:

  • (Integer)

    the length of keys in bytes



84
85
86
# File 'lib/aead/cipher.rb', line 84

def key_len
  self.class.key_len
end

#nonce_lenInteger

The length of nonces used by the current Cipher.

Returns:

  • (Integer)

    the length of nonces in bytes



93
94
95
# File 'lib/aead/cipher.rb', line 93

def nonce_len
  self.class.nonce_len
end