class PasswordProtectedFile
Constants
- DEFAULT_ITERATIONS
- HASH_FUNCTION
- ITERATION_BYTES
- IV_BYTES
- KEY_BYTES
- SALT_BYTES
Public Class Methods
create(file_name, password, data = '')
click to toggle source
# File lib/password_protected_file.rb, line 20 def self.create(file_name, password, data = '') __send__(:assert_valid_filename, file_name) __send__(:assert_file_does_not_exist, file_name) __send__(:assert_valid_password, password) new(file_name, password).tap { |o| o.__send__(:create_new, data) } end
new(file_name, password)
click to toggle source
# File lib/password_protected_file.rb, line 39 def initialize(file_name, password) @file_name = file_name @password = password end
open(file_name, password)
click to toggle source
# File lib/password_protected_file.rb, line 13 def self.open(file_name, password) __send__(:assert_valid_filename, file_name) __send__(:assert_file_exists, file_name) __send__(:assert_valid_password, password) new(file_name, password).tap { |o| o.__send__(:open_existing) } end
Private Class Methods
assert_file_does_not_exist(file_name)
click to toggle source
# File lib/password_protected_file.rb, line 125 def assert_file_does_not_exist(file_name) fail FilenameNotAvailableError.new("File #{file_name} already exists") if File.exist?(file_name) end
assert_file_exists(file_name)
click to toggle source
# File lib/password_protected_file.rb, line 121 def assert_file_exists(file_name) fail FileNotFoundError.new("File #{file_name} not found") unless File.exist?(file_name) end
assert_valid_filename(file_name)
click to toggle source
# File lib/password_protected_file.rb, line 115 def assert_valid_filename(file_name) unless file_name.is_a?(String) && !file_name.empty? && Dir.exist?(File.dirname(file_name)) fail InvalidFilenameError.new("Invalid filename given: #{file_name.inspect}") end end
assert_valid_password(password)
click to toggle source
# File lib/password_protected_file.rb, line 129 def assert_valid_password(password) fail InvalidPasswordError.new("Invalid password given: #{password.inspect}") unless password.is_a?(String) && password.length > 0 end
Public Instance Methods
data()
click to toggle source
# File lib/password_protected_file.rb, line 27 def data @data.clone end
data=(new_data)
click to toggle source
# File lib/password_protected_file.rb, line 31 def data=(new_data) assert_valid_data(new_data) @data = new_data.clone write_file end
Private Instance Methods
assert_valid_data(d)
click to toggle source
# File lib/password_protected_file.rb, line 108 def assert_valid_data(d) fail InvalidDataError.new("Expected String, got #{d.class}") unless d.instance_of?(String) fail InvalidStringEncodingError.new("Expected utf-8, got #{d.encoding}") unless d.encoding == Encoding::UTF_8 end
create_new(data)
click to toggle source
# File lib/password_protected_file.rb, line 44 def create_new(data) @data = data @iterations = DEFAULT_ITERATIONS write_file end
load_file(file_name, password)
click to toggle source
# File lib/password_protected_file.rb, line 54 def load_file(file_name, password) file = File.binread(file_name).chars pw_salt = file.shift(SALT_BYTES).join pw_hash = file.shift(KEY_BYTES).join aes_pw_salt = file.shift(SALT_BYTES).join aes_iv = file.shift(IV_BYTES).join @iterations = file.shift(ITERATION_BYTES).join.to_i encrypted_data = file.join hashed_pw = pbkdf2(password, pw_salt) fail IncorrectPasswordError unless hashed_pw == pw_hash aes_key = pbkdf2(password, aes_pw_salt) cipher = new_aes256(:decrypt, aes_key, aes_iv) @data = (cipher.update(encrypted_data) + cipher.final)[0..-2] end
new_aes256(mode, key, iv)
click to toggle source
# File lib/password_protected_file.rb, line 96 def new_aes256(mode, key, iv) OpenSSL::Cipher::AES256.new(:CBC).tap do |c| c.__send__(mode) c.key = key c.iv = iv end end
new_salt()
click to toggle source
# File lib/password_protected_file.rb, line 104 def new_salt SecureRandom.random_bytes(SALT_BYTES) end
open_existing()
click to toggle source
# File lib/password_protected_file.rb, line 50 def open_existing load_file(@file_name, @password) end
pbkdf2(password, salt)
click to toggle source
# File lib/password_protected_file.rb, line 92 def pbkdf2(password, salt) OpenSSL::PKCS5::pbkdf2_hmac(password, salt, @iterations, KEY_BYTES, HASH_FUNCTION.new) end
write_file()
click to toggle source
# File lib/password_protected_file.rb, line 71 def write_file pw_salt = new_salt pw_hash = pbkdf2(@password, pw_salt) aes_pw_salt = new_salt aes_iv = SecureRandom.random_bytes(IV_BYTES) aes_key = pbkdf2(@password, aes_pw_salt) cipher = new_aes256(:encrypt, aes_key, aes_iv) encrypted_data = cipher.update(@data + "\0") + cipher.final File.open(@file_name, 'w') do |f| f.print(pw_salt) f.print(pw_hash) f.print(aes_pw_salt) f.print(aes_iv) f.print(@iterations.to_s.rjust(ITERATION_BYTES)) f.print(encrypted_data) end true end