Class: SymmetricEncryption::Writer

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

Overview

Write to encrypted files and other IO streams

Features:

  • Encryption on the fly whilst writing files.

  • Large file support by only buffering small amounts of data in memory

  • Underlying buffering to ensure that encrypted data fits into the Symmetric Encryption Cipher block size Only the last block in the file will be padded if it is less than the block size

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(ios, options = {}) ⇒ Writer

Encrypt data before writing to the supplied stream


117
118
119
120
121
122
123
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
# File 'lib/symmetric_encryption/writer.rb', line 117

def initialize(ios,options={})
  @ios        = ios
  header      = options.fetch(:header, true)
  random_key  = options.fetch(:random_key, true)
  random_iv   = options.fetch(:random_iv, random_key)
  raise "When :random_key is true, :random_iv must also be true" if random_key && !random_iv
  # Compress is only used at this point for setting the flag in the header
  compress    = options.fetch(:compress, false)
  version     = options[:version]
  cipher_name = options[:cipher_name]
  raise "Cannot supply a :cipher_name unless both :random_key and :random_iv are true" if cipher_name && !random_key && !random_iv

  # Force header if compressed or using random iv, key
  header = true if compress || random_key || random_iv

  # Cipher to encrypt the random_key, or the entire file
  cipher = SymmetricEncryption.cipher(version)
  raise "Cipher with version:#{version} not found in any of the configured SymmetricEncryption ciphers" unless cipher

  @stream_cipher = ::OpenSSL::Cipher.new(cipher_name || cipher.cipher_name)
  @stream_cipher.encrypt

  key = random_key ? @stream_cipher.random_key : cipher.send(:key)
  iv = random_iv ? @stream_cipher.random_iv : cipher.send(:iv)

  @stream_cipher.key = key
  @stream_cipher.iv = iv if iv

  # Write the Encryption header including the random iv, key, and cipher
  if header
    @ios.write(Cipher.magic_header(
        cipher.version,
        compress,
        random_iv  ? iv : nil,
        random_key ? key : nil,
        cipher_name))
  end
  @size = 0
end

Instance Attribute Details

#sizeObject (readonly)

Returns [Integer] the number of unencrypted and uncompressed bytes written to the file so far


209
210
211
# File 'lib/symmetric_encryption/writer.rb', line 209

def size
  @size
end

Class Method Details

.open(filename_or_stream, options = {}, &block) ⇒ Object

Open a file for writing, or use the supplied IO Stream

Parameters:

filename_or_stream:
  The filename to open if a string, otherwise the stream to use
  The file or stream will be closed on completion, use .initialize to
  avoid having the stream closed automatically

options:
  :compress [true|false]
       Uses Zlib to compress the data before it is encrypted and
       written to the file
       If true, it forces header to true.
       Default: false

  :random_key [true|false]
       Generates a new random key for every new file or stream
       If true, it forces header to true. Version below then has no effect
       The Random key will be written to the file/stream in encrypted
       form as part of the header
       The key is encrypted using the global key
       Default: true
       Recommended: true.
         Setting to false will eventually expose the
         encryption key since too much data will be encrypted using the
         same encryption key

  :random_iv [true|false]
       Generates a new random iv for every new file or stream
       If true, it forces header to true.
       The Random iv will be written to the file/stream in encrypted
       form as part of the header
       Default: Value supplied above for :random_key
       Recommended: true. Setting to false will eventually expose the
         encryption key since too much data will be encrypted using the
         same encryption key

  :header [true|false]
       Whether to include the magic header that indicates the file
       is encrypted and whether its contents are compressed

       The header contains:
          Version of the encryption key used to encrypt the file
          Indicator if the data was compressed
       Default: true

  :version
       When random_key is true, the version of the encryption key to use
       when encrypting the header portion of the file

       When random_key is false, the version of the encryption key to use
       to encrypt the entire file
       Default: SymmetricEncryption.cipher

  :mode
       See File.open for open modes
       Default: 'w'

  :cipher_name
       The name of the cipher to use only if both :random_key and
       :random_iv are true.
       Default: SymmetricEncryption.cipher.cipher_name

Note: Compression occurs before encryption

# Example: Encrypt and write data to a file SymmetricEncryption::Writer.open('test_file') do |file|

file.write "Hello World\n"
file.write "Keep this secret"

end

# Example: Compress, Encrypt and write data to a file SymmetricEncryption::Writer.open('encrypted_compressed.zip', :compress => true) do |file|

file.write "Hello World\n"
file.write "Compress this\n"
file.write "Keep this safe and secure\n"

end

# Example: Writing to a CSV file

require 'fastercsv'
begin
  # Must supply :row_sep for FasterCSV otherwise it will attempt to read from and then rewind the file
  csv = FasterCSV.new(SymmetricEncryption::Writer.open('csv_encrypted'), :row_sep => "\n")
  csv << [1,2,3,4,5]
ensure
  csv.close if csv
end

101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/symmetric_encryption/writer.rb', line 101

def self.open(filename_or_stream, options={}, &block)
  raise "options must be a hash" unless options.respond_to?(:each_pair)
  mode = options.fetch(:mode, 'wb')
  compress = options.fetch(:compress, false)
  ios = filename_or_stream.is_a?(String) ? ::File.open(filename_or_stream, mode) : filename_or_stream

  begin
    file = self.new(ios, options)
    file = Zlib::GzipWriter.new(file) if compress
    block ? block.call(file) : file
  ensure
    file.close if block && file
  end
end

Instance Method Details

#<<(data) ⇒ Object

Write to the IO Stream as encrypted data Returns self

Example:

file << "Hello.\n" << "This is Jack"

194
195
196
197
# File 'lib/symmetric_encryption/writer.rb', line 194

def <<(data)
  write(data)
  self
end

#close(close_child_stream = true) ⇒ Object

Close the IO Stream Flushes any unwritten data

Note: Once an EncryptionWriter has been closed a new instance must be

created before writing again

Note: Also closes the passed in io stream or file Note: This method must be called before the supplied stream is closed

It is recommended to call Symmetric::EncryptedStream.open rather than creating an instance of Symmetric::EncryptedStream directly to ensure that the encrypted stream is closed before the stream itself is closed


169
170
171
172
173
174
175
# File 'lib/symmetric_encryption/writer.rb', line 169

def close(close_child_stream = true)
  if size > 0
    final = @stream_cipher.final
    @ios.write(final) if final.length > 0
  end
  @ios.close if close_child_stream
end

#flushObject

Flush the output stream Does not flush internal buffers since encryption requires all data to be written following the encryption block size

Needed by XLS gem

203
204
205
# File 'lib/symmetric_encryption/writer.rb', line 203

def flush
  @ios.flush
end

#write(data) ⇒ Object

Write to the IO Stream as encrypted data Returns the number of bytes written


179
180
181
182
183
184
185
186
187
# File 'lib/symmetric_encryption/writer.rb', line 179

def write(data)
  return unless data

  bytes = data.to_s
  @size += bytes.size
  partial = @stream_cipher.update(bytes)
  @ios.write(partial) if partial.length > 0
  data.length
end