Class: KmsTools::EncryptedFile

Inherits:
Object
  • Object
show all
Defined in:
lib/kms-tools/encrypted_file.rb

Overview

Interacts with files and streams for encryption and decryption of large blobs of data

Author:

  • Matt Krieger

Constant Summary collapse

META_SIZE_HEADER_LENGTH =

Number of bytes allocated at the beginning of a KMS file noting metadata size. 7 bytes limits metadata to 9.9 MB, which is way too much. Don’t use that much.

7
DEFAULT_CIPHER =

Default cipher for local encryption

'aes-256-cbc'
KMS_REQUIRED_ELEMENTS =

Minimum required metadata elements for a KMS file to be valid

%i(encrypted_key encrypted_iv cipher original_extension checksum)

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ EncryptedFile

Creats an EncryptedFile object.

Parameters:

  • options (Hash) (defaults to: {})

Options Hash (options):

  • :encrypter (Object)

    existing (Encrypter) object with options set

  • :path (String)

    Path to an encrypted file to load on initialization



23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/kms-tools/encrypted_file.rb', line 23

def initialize(options = {})
  @enc = options[:encrypter] || KmsTools::Encrypter.new
  @dec = KmsTools::Decrypter.new
  @kms_meta = {}

  if options[:path]
    if File.readable?(options[:path])
      load_encrypted_file(options[:path])
    else
      raise "#{options[:path]} is not a readable!"
    end
  end
end

Instance Method Details

#checksumstring

Get the SHA1 checksum of the decrypted data

Returns:

  • (string)

    hex encoded SHA1 checksum



102
103
104
# File 'lib/kms-tools/encrypted_file.rb', line 102

def checksum
  @kms_meta[:checksum]
end

#cipherstring

Get the cipher used for the encrypted file or set the default

Returns:

  • (string)

    OpenSSL cipher



95
96
97
# File 'lib/kms-tools/encrypted_file.rb', line 95

def cipher
  @kms_meta[:cipher] ||= DEFAULT_CIPHER
end

#create_from_file(path) ⇒ Hash

Generate metadata from a plaintext file and prepare for encryption

Parameters:

  • path (String)

    Path to plaintext file

Returns:

  • (Hash)

    KMS file metadata



41
42
43
44
45
46
47
48
# File 'lib/kms-tools/encrypted_file.rb', line 41

def create_from_file(path)
  @decrypted_source = path
  @kms_meta[:arn] = @enc.master_key_arn
  @kms_meta[:checksum] = file_sha(path)
  @kms_meta[:original_extension] = File.extname(path)
  set_up_encryption_params
  @kms_meta
end

#encrypted_dataObject



88
89
90
# File 'lib/kms-tools/encrypted_file.rb', line 88

def encrypted_data
  @kms_meta[:encrypted_data]
end

#encrypted_ivstring

Get the encrypted initialization vector of the current file, generate a new one if not present

Returns:

  • (string)

    Base64 encoded encrypted data key



77
78
79
# File 'lib/kms-tools/encrypted_file.rb', line 77

def encrypted_iv
  @kms_meta[:encrypted_iv] ||= @enc.new_encrypted_key
end

#encrypted_keystring

Get the encrypted key of the current file, generate a new one if not present

Returns:

  • (string)

    Base64 encoded encrypted data key



70
71
72
# File 'lib/kms-tools/encrypted_file.rb', line 70

def encrypted_key
  @kms_meta[:encrypted_key] ||= @enc.new_encrypted_key
end

#file_sha(path) ⇒ String

Calculate SHA of a given file by chunks

Parameters:

  • path (String)

    Path to file

Returns:

  • (String)

    Hex encoded SHA1 hash



172
173
174
175
176
177
178
179
180
181
# File 'lib/kms-tools/encrypted_file.rb', line 172

def file_sha(path)
  sha1 = Digest::SHA1.new
  file = File.open(path,'rb')
  chunk = ""
  while file.read(STREAM_CHUNK_SIZE, chunk)
    sha1.update(chunk)
  end

  sha1.hexdigest
end

#is_valid_kms_file?(yaml) ⇒ Boolean

Verify YAML header of a KMS file to encure necessary information is present to decrypt

Parameters:

  • yaml (Object)

    object containing KMS metadata

Returns:

  • (Boolean)


164
165
166
# File 'lib/kms-tools/encrypted_file.rb', line 164

def is_valid_kms_file?(yaml)
  KMS_REQUIRED_ELEMENTS.all? { |e| yaml.has_key? e }
end

#load_encrypted_file(path) ⇒ Hash

Read a KMS encrypted file and populate object metadata from file headers

Parameters:

  • path (String)

    Path to encrypted file

Returns:

  • (Hash)

    KMS file metadata



54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/kms-tools/encrypted_file.rb', line 54

def load_encrypted_file(path)
  meta_size = IO.binread(path, META_SIZE_HEADER_LENGTH).to_i
  kms_yaml = YAML::load(IO.binread(path, meta_size, META_SIZE_HEADER_LENGTH))
  if is_valid_kms_file?(kms_yaml)
    @kms_meta = kms_yaml
    @encrypted_file_path = path
    @encrypted_data_start = META_SIZE_HEADER_LENGTH + meta_size
  else
    raise "#{path} is not a valid KMS file!"
  end
  @kms_meta
end

#original_extensionstring

Get the original extension of the encrypted file

Returns:

  • (string)

    file extension



84
85
86
# File 'lib/kms-tools/encrypted_file.rb', line 84

def original_extension
  @kms_meta[:original_extension]
end

#save_decrypted(path) ⇒ Boolean

Save decrypted data to the provided path

Parameters:

  • path (String)

    Path to save decrypted file

Returns:

  • (Boolean)

    returns true on success



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/kms-tools/encrypted_file.rb', line 142

def save_decrypted(path)
  path << @kms_meta[:original_extension] unless File.extname(path) == @kms_meta[:original_extension]
  infile = File.open(@encrypted_file_path, 'rb')
  outfile = File.open(path, 'wb+')
  @dec.stream_decrypt_with_data_key({
    in: infile,
    position: @encrypted_data_start,
    out: outfile,
    encrypted_iv: encrypted_iv,
    encrypted_key: encrypted_key,
    cipher: cipher,
    checksum: checksum
    })

  outfile.close
  true
end

#save_encrypted(path) ⇒ Boolean

Save encrypted data with KMS headers to the provided path

Parameters:

  • path (String)

    Path to save encrypted file

Returns:

  • (Boolean)

    returns true on success



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/kms-tools/encrypted_file.rb', line 119

def save_encrypted(path)
  kms_yaml = @kms_meta.to_yaml
  meta_size = kms_yaml.bytesize.to_s.rjust(META_SIZE_HEADER_LENGTH, "0")
  infile = File.open(@decrypted_source, 'rb')
  outfile = File.open(path, 'wb+')
  outfile << meta_size
  outfile << kms_yaml
  @enc.stream_encrypt_with_data_key({
      in: infile,
      out: outfile,
      encrypted_iv: encrypted_iv,
      encrypted_key: encrypted_key,
      cipher: cipher
  })

  outfile.close
  true
end

#set_up_encryption_paramsnil

Generate encryption key, iv, and cipher if they do not exist

Returns:

  • (nil)


108
109
110
111
112
113
# File 'lib/kms-tools/encrypted_file.rb', line 108

def set_up_encryption_params
  encrypted_key
  encrypted_iv
  cipher
  return nil
end