lib/keepassx/header.rb in keepassx-0.1.0 vs lib/keepassx/header.rb in keepassx-1.0.0
- old
+ new
@@ -1,5 +1,7 @@
+# frozen_string_literal: true
+
# The keepass file header.
#
# From the KeePass doc:
#
# Database header: [DBHDR]
@@ -30,58 +32,131 @@
# - aEncryptionIV is the initialization vector used by AES/Twofish for
# encrypting/decrypting the database data.
# - aContentsHash: "plain contents" refers to the database file, minus the
# database header, decrypted by FinalKey.
# * PlainContents = Decrypt_with_FinalKey(DatabaseFile - DatabaseHeader)
+
module Keepassx
class Header
ENCRYPTION_FLAGS = [
- [1 , 'SHA2' ],
- [2 , 'Rijndael'],
- [2 , 'AES' ],
- [4 , 'ArcFour' ],
- [8 , 'TwoFish' ]
- ]
+ [1, 'SHA2'],
+ [2, 'Rijndael'],
+ [2, 'AES'],
+ [4, 'ArcFour'],
+ [8, 'TwoFish'],
+ ].freeze
- attr_reader :encryption_iv
- attr_reader :ngroups, :nentries
+ SIGNATURES = [0x9AA2D903, 0xB54BFB65].freeze
- def initialize(header_bytes)
- @signature1 = header_bytes[0..4].unpack('L*').first
- @signature2 = header_bytes[4..8].unpack('L*').first
- @flags = header_bytes[8..12].unpack('L*').first
- @version = header_bytes[12..16].unpack('L*').first
- @master_seed = header_bytes[16...32]
- @encryption_iv = header_bytes[32...48]
- @ngroups = header_bytes[48..52].unpack('L*').first
- @nentries = header_bytes[52..56].unpack('L*').first
- @contents_hash = header_bytes[56..88]
- @master_seed2 = header_bytes[88...120]
- @rounds = header_bytes[120..-1].unpack('L*').first
+ attr_reader :encryption_iv
+ attr_accessor :groups_count
+ attr_accessor :entries_count
+ attr_accessor :content_hash
+
+
+ # rubocop:disable Metrics/MethodLength
+ 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).unpack('L*').first
+ @signature2 = header_bytes.read(4).unpack('L*').first
+ @flags = header_bytes.read(4).unpack('L*').first
+ @version = header_bytes.read(4).unpack('L*').first
+ @master_seed = header_bytes.read(16)
+ @encryption_iv = header_bytes.read(16)
+ @groups_count = header_bytes.read(4).unpack('L*').first
+ @entries_count = header_bytes.read(4).unpack('L*').first
+ @content_hash = header_bytes.read(32)
+ @master_seed2 = header_bytes.read(32)
+ @rounds = header_bytes.read(4).unpack('L*').first
+ end
end
+ # rubocop:enable Metrics/MethodLength
+
def valid?
- @signature1 == 0x9AA2D903 && @signature2 == 0xB54BFB65
+ @signature1 == SIGNATURES[0] && @signature2 == SIGNATURES[1]
end
+
def encryption_type
ENCRYPTION_FLAGS.each do |(flag_mask, encryption_type)|
return encryption_type if @flags & flag_mask
end
'Unknown'
end
- def final_key(master_key)
+
+ # rubocop:disable Metrics/MethodLength
+ def final_key(master_key, keyfile_data = nil)
key = Digest::SHA2.new.update(master_key).digest
- aes = FastAES.new(@master_seed2)
- @rounds.times do |i|
- key = aes.encrypt(key)
+ 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
+ # rubocop:enable Metrics/MethodLength
+
+
+ # Return encoded header
+ #
+ # @return [String] Encoded header representation.
+ 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
+
+
+ private
+
+
+ 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
+
end
end