kdbx 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4f1066ac2625d013aa37082c0e66ba15c59a35ad
4
+ data.tar.gz: 3143044e49a5ba3299b56a3741acaaeba990a5b0
5
+ SHA512:
6
+ metadata.gz: bcc4f6b347839ec701f234fefe47fde2311f3f79b71be7c28525f1a711b4f8e354d05724c77a8054f90b61146718dd8dea246edc7316d3ca7e49e133b59f1444
7
+ data.tar.gz: 679881649d8cd3a479f3774b111a26ee24aa9279fecfb840d0d3ec0877e506d5ab35336f3a4cc24248d554f13fa2219273706539ebfd618529382de7495c5a75
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 rumtid
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,19 @@
1
+ # kdbx.rb
2
+
3
+ Yet another library for kdbx file.
4
+
5
+ ## Installation
6
+
7
+ ```
8
+ $ gem install kdbx
9
+ ```
10
+
11
+ ## Development
12
+
13
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
14
+
15
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
16
+
17
+ ## License
18
+
19
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,39 @@
1
+ require "kdbx/version"
2
+ require "kdbx/attributes"
3
+ require "kdbx/encryption"
4
+ require "kdbx/wrapper"
5
+ require "kdbx/header"
6
+
7
+ class Kdbx
8
+ include Attributes
9
+ include Encryption
10
+
11
+ def initialize(filename = nil, **opts)
12
+ super()
13
+ if filename == nil
14
+ @header = Header.new
15
+ @content = String.new
16
+ else
17
+ self.filename = filename
18
+ self.password = opts[:password] if opts.has_key? :password
19
+ self.keyfile = opts[:keyfile] if opts.has_key? :keyfile
20
+ load
21
+ end
22
+ end
23
+
24
+ def load
25
+ File.open filename, "rb" do |file|
26
+ @header = Header.load file
27
+ decode_content file.read
28
+ end
29
+ self
30
+ end
31
+
32
+ def save
33
+ File.open filename, "wb" do |file|
34
+ @header.save file
35
+ encode_content file
36
+ end
37
+ self
38
+ end
39
+ end
@@ -0,0 +1,90 @@
1
+ require "base64"
2
+
3
+ class Kdbx
4
+ module Attributes
5
+ attr_reader :filename
6
+ def filename=(name)
7
+ @filename = File.absolute_path name
8
+ end
9
+
10
+ attr_reader :password
11
+ def password=(str)
12
+ @password = str == nil ? "" : sha256(str)
13
+ end
14
+
15
+ attr_reader :keyfile
16
+ def keyfile=(str)
17
+ @keyfile = File.absolute_path str
18
+ end
19
+
20
+ def credential
21
+ secrets = String.new
22
+ secrets << password if password
23
+ if keyfile
24
+ data = File.read keyfile
25
+ if [32, 64].include? data.bytesize
26
+ secrets << data
27
+ else
28
+ begin
29
+ doc = REXML::Document.new data
30
+ ele = doc.elements["/KeyFile/Key/Data"]
31
+ secrets << Base64.decode64(ele.text)
32
+ rescue REXML::ParseException
33
+ secrets << sha256(data)
34
+ end
35
+ end
36
+ end
37
+ secrets
38
+ end
39
+
40
+ def compressionflags
41
+ @header[3].unpack("L").first
42
+ end
43
+
44
+ def compressionflags=(flag)
45
+ @header[3] = [flag].pack("L")
46
+ end
47
+
48
+ def zipped?
49
+ compressionflags == 1
50
+ end
51
+
52
+ def masterseed
53
+ @header[4]
54
+ end
55
+
56
+ def transformseed
57
+ @header[5]
58
+ end
59
+
60
+ def transformrounds
61
+ @header[6].unpack("Q").first
62
+ end
63
+
64
+ def transformrounds=(num)
65
+ @header[6] = [num].pack("Q")
66
+ end
67
+
68
+ def encryptioniv
69
+ @header[7]
70
+ end
71
+
72
+ def protectedstreamkey
73
+ @header[8]
74
+ end
75
+
76
+ def streamstartbytes
77
+ @header[9]
78
+ end
79
+
80
+ def innerrandomstreamid
81
+ @header[10].unpack("L").first
82
+ end
83
+
84
+ def innerrandomstreamid=(id)
85
+ @header[10] = [id].pack("L")
86
+ end
87
+
88
+ attr_accessor :content
89
+ end
90
+ end
@@ -0,0 +1,62 @@
1
+ require "zlib"
2
+ require "openssl"
3
+ require "salsa20"
4
+
5
+ class Kdbx
6
+ module Encryption
7
+ private
8
+
9
+ def sha256(data)
10
+ OpenSSL::Digest::SHA256.digest data
11
+ end
12
+
13
+ def salsa20
14
+ key = sha256 protectedstreamkey
15
+ Salsa20.new key, "\xE8\x30\x09\x4B\x97\x20\x5D\x2A"
16
+ end
17
+
18
+ def masterkey
19
+ cipher = OpenSSL::Cipher.new("AES-256-ECB").encrypt
20
+ cipher.key, data = transformseed, sha256(credential)
21
+ transformrounds.times { data = cipher.update data }
22
+ sha256(masterseed + sha256(data))
23
+ end
24
+
25
+ def encrypt(data)
26
+ cipher = OpenSSL::Cipher.new("AES-256-CBC").encrypt
27
+ cipher.iv, cipher.key = encryptioniv, masterkey
28
+ cipher.update(streamstartbytes + data) + cipher.final
29
+ end
30
+
31
+ def decrypt(data)
32
+ cipher = OpenSSL::Cipher.new("AES-256-CBC").decrypt
33
+ cipher.iv, cipher.key = encryptioniv, masterkey
34
+ data = cipher.update(data) + cipher.final
35
+ unless data.start_with? streamstartbytes
36
+ fail "InvalidKey"
37
+ end
38
+ size = streamstartbytes.bytesize
39
+ return data.byteslice size..-1
40
+ end
41
+
42
+ def encode_content(file)
43
+ data = Wrapper.protect @content do |block|
44
+ salsa20.encrypt block
45
+ end if innerrandomstreamid == 2
46
+ data = Zlib.gzip data if zipped?
47
+ data = Wrapper.wrap data
48
+ data = encrypt data
49
+ file.write data
50
+ end
51
+
52
+ def decode_content(data)
53
+ data = decrypt data
54
+ data = Wrapper.unwrap data
55
+ data = Zlib.gunzip data if zipped?
56
+ data = Wrapper.expose data do |block|
57
+ salsa20.decrypt block
58
+ end if innerrandomstreamid == 2
59
+ @content = data
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,74 @@
1
+ require "openssl"
2
+ require "forwardable"
3
+
4
+ class Kdbx::Header
5
+ def self.load(stream)
6
+ fields = {
7
+ :pid => stream.readpartial(4),
8
+ :sid => stream.readpartial(4),
9
+ :ver => stream.readpartial(4)
10
+ }
11
+ loop do
12
+ id = stream.readbyte
13
+ sz = stream.readpartial 2
14
+ sz = sz.unpack("S").first
15
+ fields[id] = stream.readpartial sz
16
+ break if id == 0
17
+ end
18
+ new.merge! fields
19
+ end
20
+
21
+ extend Forwardable
22
+ def_delegators :@fields, :[], :[]=, :has_key?
23
+
24
+ def initialize
25
+ @fields = {}
26
+ end
27
+
28
+ def initialize_copy(other)
29
+ super
30
+ @fields = other.instance_variable_get(:@fields).clone
31
+ end
32
+
33
+ def merge(hash)
34
+ clone.merge! hash
35
+ end
36
+
37
+ def merge!(hash)
38
+ @fields.merge! hash
39
+ self
40
+ end
41
+
42
+ def save(stream)
43
+ set_defaults
44
+ stream.write @fields.fetch :pid
45
+ stream.write @fields.fetch :sid
46
+ stream.write @fields.fetch :ver
47
+ @fields.each do |key, val|
48
+ next if !(key.is_a? Integer) || key == 0
49
+ stream.write [key, val.bytesize].pack("CS") + val
50
+ end
51
+ val = @fields.fetch 0
52
+ stream.write [0, val.bytesize].pack("CS") + val
53
+ end
54
+
55
+ private
56
+
57
+ def set_defaults
58
+ @fields.merge!({
59
+ :pid => "\x03\xD9\xA2\x9A",
60
+ :sid => "\x67\xFB\x4B\xB5",
61
+ :ver => "\x01\x00\x03\x00",
62
+ 0 => "\x00\xD0\xAD\x0A",
63
+ 2 => "\x31\xC1\xF2\xE6\xBF\x71\x43\x50\xBE\x58\x05\x21\x6A\xFC\x5A\xFF",
64
+ 3 => "\x01\x00\x00\x00",
65
+ 4 => OpenSSL::Random.random_bytes(32),
66
+ 5 => OpenSSL::Random.random_bytes(32),
67
+ 6 => "\x70\x17\x00\x00\x00\x00\x00\x00",
68
+ 7 => OpenSSL::Random.random_bytes(16),
69
+ 8 => OpenSSL::Random.random_bytes(32),
70
+ 9 => OpenSSL::Random.random_bytes(32),
71
+ 10 => "\x02\x00\x00\x00"
72
+ }) { |k, v1, v2| v1 }
73
+ end
74
+ end
@@ -0,0 +1,3 @@
1
+ class Kdbx
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,65 @@
1
+ require "base64"
2
+ require "openssl"
3
+ require "rexml/document"
4
+
5
+ module Kdbx::Wrapper
6
+ XPATH = "//Value[@Protected='True']"
7
+
8
+ module_function
9
+
10
+ def unwrap(data)
11
+ stream = StringIO.new data
12
+ payload = String.new
13
+ loop do
14
+ head = stream.readpartial 40
15
+ id, hash, size = head.unpack "La32L"
16
+ break if size == 0
17
+ part = stream.readpartial size
18
+ payload << part
19
+ end
20
+ payload
21
+ end
22
+
23
+ def wrap(payload)
24
+ data = String.new
25
+ data << "\x00\x00\x00\x00"
26
+ data << OpenSSL::Digest::SHA256.digest(payload)
27
+ data << [payload.bytesize].pack("L") << payload
28
+ data << "\x01\x00\x00\x00"
29
+ data << "\x00" * 36
30
+ data
31
+ end
32
+
33
+ def pieces(doc)
34
+ doc.elements.to_a(XPATH).map(&:text).compact
35
+ end
36
+
37
+ def expose(data)
38
+ doc = REXML::Document.new data
39
+ ciphertext = pieces(doc).map do |t|
40
+ Base64.decode64 t
41
+ end
42
+ plaintext = yield ciphertext.join
43
+ doc.elements.each XPATH do |e|
44
+ next if e.text == nil
45
+ size = ciphertext.shift.bytesize
46
+ e.text = plaintext.byteslice 0, size
47
+ plaintext = plaintext.byteslice size..-1
48
+ end
49
+ doc.to_s
50
+ end
51
+
52
+ def protect(data)
53
+ doc = REXML::Document.new data
54
+ plaintext = pieces doc
55
+ ciphertext = yield plaintext.join
56
+ doc.elements.each XPATH do |e|
57
+ next if e.text == nil
58
+ size = plaintext.shift.bytesize
59
+ text = ciphertext.byteslice 0, size
60
+ ciphertext = ciphertext.byteslice size..-1
61
+ e.text = Base64.encode64 text
62
+ end
63
+ doc.to_s
64
+ end
65
+ end
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kdbx
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - rumtid
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-03-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: salsa20
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 0.1.2
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 0.1.2
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.5'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.5'
41
+ description:
42
+ email:
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - LICENSE.txt
48
+ - README.md
49
+ - lib/kdbx.rb
50
+ - lib/kdbx/attributes.rb
51
+ - lib/kdbx/encryption.rb
52
+ - lib/kdbx/header.rb
53
+ - lib/kdbx/version.rb
54
+ - lib/kdbx/wrapper.rb
55
+ homepage: https://github.com/rumtid/kdbx.rb
56
+ licenses:
57
+ - MIT
58
+ metadata: {}
59
+ post_install_message:
60
+ rdoc_options: []
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ requirements: []
74
+ rubyforge_project:
75
+ rubygems_version: 2.6.8
76
+ signing_key:
77
+ specification_version: 4
78
+ summary: A kdbx library to access kdbx file format
79
+ test_files: []