class Keepassx::Header

Constants

ENCRYPTION_FLAGS
SIGNATURES

Attributes

content_hash[RW]
encryption_iv[R]
entries_count[RW]
groups_count[RW]

Public Class Methods

new(header_bytes = nil) click to toggle source

rubocop:disable Metrics/MethodLength

# File lib/keepassx/header.rb, line 58
def initialize(header_bytes = nil)
  if header_bytes.nil?
    @signature1    = SIGNATURES[0]
    @signature2    = SIGNATURES[1]
    @flags         = 3 # SHA2 hashing, AES encryption
    @version       = 0x30002
    @master_seed   = SecureRandom.random_bytes(16)
    @encryption_iv = SecureRandom.random_bytes(16)
    @groups_count  = 0
    @entries_count = 0
    @master_seed2  = SecureRandom.random_bytes(32)
    @rounds        = 50_000
  else
    header_bytes   = StringIO.new(header_bytes)
    @signature1    = header_bytes.read(4).unpack1('L*')
    @signature2    = header_bytes.read(4).unpack1('L*')
    @flags         = header_bytes.read(4).unpack1('L*')
    @version       = header_bytes.read(4).unpack1('L*')
    @master_seed   = header_bytes.read(16)
    @encryption_iv = header_bytes.read(16)
    @groups_count  = header_bytes.read(4).unpack1('L*')
    @entries_count = header_bytes.read(4).unpack1('L*')
    @content_hash  = header_bytes.read(32)
    @master_seed2  = header_bytes.read(32)
    @rounds        = header_bytes.read(4).unpack1('L*')
  end
end

Public Instance Methods

encode() click to toggle source

Return encoded header

@return [String] Encoded header representation.

# File lib/keepassx/header.rb, line 129
def encode
  [@signature1].pack('L*')      <<
    [@signature2].pack('L*')    <<
    [@flags].pack('L*')         <<
    [@version].pack('L*')       <<
    @master_seed                <<
    @encryption_iv              <<
    [@groups_count].pack('L*')  <<
    [@entries_count].pack('L*') <<
    @content_hash               <<
    @master_seed2               <<
    [@rounds].pack('L*')
end
encryption_type() click to toggle source
# File lib/keepassx/header.rb, line 93
def encryption_type
  ENCRYPTION_FLAGS.each do |(flag_mask, encryption_type)|
    return encryption_type if @flags & flag_mask
  end
  'Unknown'
end
final_key(master_key, keyfile_data = nil) click to toggle source

rubocop:disable Metrics/MethodLength

# File lib/keepassx/header.rb, line 102
def final_key(master_key, keyfile_data = nil)
  key = Digest::SHA2.new.update(master_key).digest

  if keyfile_data
    keyfile_hash = extract_keyfile_hash(keyfile_data)
    key = master_key == '' ? keyfile_hash : Digest::SHA2.new.update(key + keyfile_hash).digest
  end

  aes = OpenSSL::Cipher.new('AES-256-ECB')
  aes.encrypt
  aes.key = @master_seed2
  aes.padding = 0

  @rounds.times do
    key = aes.update(key) + aes.final
  end

  key = Digest::SHA2.new.update(key).digest
  key = Digest::SHA2.new.update(@master_seed + key).digest
  key
end
valid?() click to toggle source

rubocop:enable Metrics/MethodLength

# File lib/keepassx/header.rb, line 88
def valid?
  @signature1 == SIGNATURES[0] && @signature2 == SIGNATURES[1]
end

Private Instance Methods

extract_keyfile_hash(keyfile_data) click to toggle source
# File lib/keepassx/header.rb, line 147
def extract_keyfile_hash(keyfile_data)
  # Hex encoded key
  if keyfile_data.size == 64
    [keyfile_data].pack('H*')

  # Raw key
  elsif keyfile_data.size == 32
    keyfile_data

  else
    Digest::SHA2.new.update(keyfile_data).digest
  end
end