Class: SymmetricEncryption::Header

Inherits:
Object
  • Object
show all
Defined in:
lib/symmetric_encryption/header.rb

Overview

Defines the Header Structure returned when parsing the header.

Note:

  • Header only works against binary encrypted data that has not been decoded.

  • Decode data first before trying to extract its header.

  • Decoding is not required when encoding is set to ‘:none`.

Constant Summary collapse

MAGIC_HEADER =

Encrypted data includes this header prior to encoding when ‘always_add_header` is true.

"@EnC".force_encoding(SymmetricEncryption::BINARY_ENCODING)
MAGIC_HEADER_SIZE =
MAGIC_HEADER.size

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(version: SymmetricEncryption.cipher.version, compress: false, iv: nil, key: nil, cipher_name: nil, auth_tag: nil) ⇒ Header

Returns a magic header for this cipher instance that can be placed at the beginning of a file or stream to indicate how the data was encrypted

Parameters

compress [true|false]
  Whether the data should be compressed before encryption.
  Default: false

iv [String]
  The iv to to put in the header
  Default: nil : Exclude from header

key [String]
  The key to to put in the header.
  The key is encrypted using the global encryption key
  Default: nil : Exclude key from header

version: [Integer (0..255)]
  Version of the global cipher used to encrypt the data,
  or the encryption key if supplied.
  default: The current global encryption cipher version.

cipher_name [String]
  The cipher_name to be used for encrypting the data portion.
  For example 'aes-256-cbc'
  `key` if supplied is encrypted with the cipher name based on the cipher version in this header.
  Intended for use when encrypting large files with a different cipher to the global one.
  Default: nil : Exclude cipher_name name from header


74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/symmetric_encryption/header.rb', line 74

def initialize(version: SymmetricEncryption.cipher.version,
               compress: false,
               iv: nil,
               key: nil,
               cipher_name: nil,
               auth_tag: nil)

  @version     = version
  @compress    = compress
  @iv          = iv
  @key         = key
  @cipher_name = cipher_name
  @auth_tag    = auth_tag
end

Instance Attribute Details

#auth_tagObject

String

Binary auth tag used to encrypt the data.

Usually 16 bytes. Present when using an authenticated encryption mode.



35
36
37
# File 'lib/symmetric_encryption/header.rb', line 35

def auth_tag
  @auth_tag
end

#cipher_nameObject

String

Name of the cipher used.



27
28
29
# File 'lib/symmetric_encryption/header.rb', line 27

def cipher_name
  @cipher_name
end

#compressObject

true|false

Whether to compress the data before encryption.

If supplied in the header.



16
17
18
# File 'lib/symmetric_encryption/header.rb', line 16

def compress
  @compress
end

#ivObject

String

IV used to encrypt the data.

If supplied in the header.



20
21
22
# File 'lib/symmetric_encryption/header.rb', line 20

def iv
  @iv
end

#keyObject

String

Key used to encrypt the data.

If supplied in the header.



24
25
26
# File 'lib/symmetric_encryption/header.rb', line 24

def key
  @key
end

#versionObject

Integer

Version of the cipher used.



30
31
32
# File 'lib/symmetric_encryption/header.rb', line 30

def version
  @version
end

Class Method Details

.present?(buffer) ⇒ Boolean

Returns whether the supplied buffer starts with a symmetric_encryption header Note: The encoding of the supplied buffer is forced to binary if not already binary

Returns:

  • (Boolean)


39
40
41
42
43
44
# File 'lib/symmetric_encryption/header.rb', line 39

def self.present?(buffer)
  return false if buffer.nil? || (buffer == "")

  buffer.force_encoding(SymmetricEncryption::BINARY_ENCODING)
  buffer.start_with?(MAGIC_HEADER)
end

Instance Method Details

#cipherObject

Returns [SymmetricEncryption::Cipher] the cipher used to decrypt or encrypt the key specified in this header, if supplied.



91
92
93
# File 'lib/symmetric_encryption/header.rb', line 91

def cipher
  @cipher ||= SymmetricEncryption.cipher(version)
end

#compressed?Boolean

Returns:

  • (Boolean)


100
101
102
# File 'lib/symmetric_encryption/header.rb', line 100

def compressed?
  @compress
end

#parse(buffer, offset = 0) ⇒ Object

Returns [Integer] the offset within the buffer of the data after the header has been read.

Returns 0 if no header is present



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/symmetric_encryption/header.rb', line 124

def parse(buffer, offset = 0)
  return 0 if buffer.nil? || (buffer == "") || (buffer.length <= MAGIC_HEADER_SIZE + 2)

  # Symmetric Encryption Header
  #
  # Consists of:
  #    4 Bytes: Magic Header Prefix: @Enc
  #    1 Byte:  The version of the cipher used to encrypt the header.
  #    1 Byte:  Flags:
  #       Bit 1: Whether the data is compressed
  #       Bit 2: Whether the IV is included
  #       Bit 3: Whether the Key is included
  #       Bit 4: Whether the Cipher Name is included
  #       Bit 5: Future use
  #       Bit 6: Future use
  #       Bit 7: Future use
  #       Bit 8: Future use
  #    2 Bytes: IV Length (little endian), if included.
  #      IV in binary form.
  #    2 Bytes: Key Length (little endian), if included.
  #      Key in binary form
  #    2 Bytes: Cipher Name Length (little endian), if included.
  #      Cipher name it UTF8 text

  buffer.force_encoding(SymmetricEncryption::BINARY_ENCODING)
  header = buffer.byteslice(offset, MAGIC_HEADER_SIZE)
  return 0 unless header == MAGIC_HEADER

  offset += MAGIC_HEADER_SIZE

  # Remove header and extract flags
  self.version = buffer.getbyte(offset)
  offset += 1

  unless cipher
    raise(
      SymmetricEncryption::CipherError,
      "Cipher with version:#{version.inspect} not found in any of the configured SymmetricEncryption ciphers"
    )
  end

  flags = buffer.getbyte(offset)
  offset += 1

  self.compress = (flags & FLAG_COMPRESSED) != 0

  if (flags & FLAG_IV).zero?
    self.iv = nil
  else
    self.iv, offset = read_string(buffer, offset)
  end

  if (flags & FLAG_KEY).zero?
    self.key = nil
  else
    encrypted_key, offset = read_string(buffer, offset)
    self.key              = cipher.binary_decrypt(encrypted_key)
  end

  if (flags & FLAG_CIPHER_NAME).zero?
    self.cipher_name = nil
  else
    self.cipher_name, offset = read_string(buffer, offset)
  end

  if (flags & FLAG_AUTH_TAG).zero?
    self.auth_tag = nil
  else
    self.auth_tag, offset = read_string(buffer, offset)
  end

  offset
end

#parse!(buffer) ⇒ Object

Returns [String] the encrypted data without header Returns nil if no header is present

The supplied buffer will be updated directly and its header will be stripped if present.

Parameters

buffer
  String to extract the header from


113
114
115
116
117
118
119
# File 'lib/symmetric_encryption/header.rb', line 113

def parse!(buffer)
  offset = parse(buffer)
  return if offset.zero?

  buffer.slice!(0..offset - 1)
  buffer
end

#to_sObject

Returns [String] this header as a string



199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/symmetric_encryption/header.rb', line 199

def to_s
  flags = 0
  flags |= FLAG_COMPRESSED if compressed?
  flags |= FLAG_IV if iv
  flags |= FLAG_KEY if key
  flags |= FLAG_CIPHER_NAME if cipher_name
  flags |= FLAG_AUTH_TAG if auth_tag

  header = "#{MAGIC_HEADER}#{version.chr(SymmetricEncryption::BINARY_ENCODING)}#{flags.chr(SymmetricEncryption::BINARY_ENCODING)}"

  if iv
    header << [iv.length].pack("v")
    header << iv
  end

  if key
    encrypted = cipher.binary_encrypt(key, header: false)
    header << [encrypted.length].pack("v")
    header << encrypted
  end

  if cipher_name
    header << [cipher_name.length].pack("v")
    header << cipher_name
  end

  if auth_tag
    header << [auth_tag.length].pack("v")
    header << auth_tag
  end

  header
end