block_io 3.0.0 → 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c7a878068a7ce224442ab3544657e2fb5de30dc68c43b00fd443c7c8ea35a61a
4
- data.tar.gz: '09ad2302125ca6d86427637023439ffac4f8c1e7d6f477d098aeb6955d0f3be2'
3
+ metadata.gz: 42de3243361e4c2a75e0357745ac507c381332d927453d6b35f81b63f30ea723
4
+ data.tar.gz: f2416231c346fe92b0f39b665390be3c511aab2b62d9c8c8e2f07c580c12e30e
5
5
  SHA512:
6
- metadata.gz: bb922e78d5c23c2e64b8259a688189eab1c20e654fa63389cc919d28a68030a0650101df2a32aecafbabb6ff639552e2698def07cd405638f0aea6bed32e7995
7
- data.tar.gz: 940ff69d78033e939b150af65fc89944a8fd99a4296bab9c8314b5988508e2d093ec1e6c1295df37ebcbac4d31110fc70e3749d02a58da25585039594b872de6
6
+ metadata.gz: bdd5177bade408ffcb526b09d62a177ebee4d6cef0352267c1d1578c1223bd598f7900012bfa27c0ae1cb71e9202f862180ecad91ea70b4b077a44f4d06fe080
7
+ data.tar.gz: ff6f0db6e6bc5b485de84487d805970317fa4f936d015d4fce994302a9e48fd3e37dd076ffb0da8a8fb41e1f988040397905d6b389ca7995ed0e6b88b23544bd
data/README.md CHANGED
@@ -14,9 +14,10 @@ And then execute:
14
14
 
15
15
  Or install it yourself as:
16
16
 
17
- $ gem install block_io -v=3.0.0
17
+ $ gem install block_io
18
18
 
19
19
  ## Changelog
20
+ *06/09/21*: Version 3.0.1 implements use of dynamic decryption algorithms.
20
21
  *04/14/21*: BREAKING CHANGES. Version 3.0.0. Remove support for Ruby < 2.4.0, and Windows. Behavior and interfaces have changed. By upgrading you'll need to revise your code and tests.
21
22
 
22
23
  ## Important Notes
@@ -16,7 +16,7 @@ module BlockIo
16
16
  raise "Must provide an API Key." unless args.key?(:api_key) and args[:api_key].to_s.size > 0
17
17
 
18
18
  @api_key = args[:api_key]
19
- @encryption_key = Helper.pinToAesKey(args[:pin] || "") if args.key?(:pin)
19
+ @pin = args[:pin]
20
20
  @version = args[:version] || 2
21
21
  @hostname = args[:hostname] || "block.io"
22
22
  @proxy = args[:proxy] || {}
@@ -131,9 +131,10 @@ module BlockIo
131
131
  if !encrypted_key.nil? and !@keys.key?(encrypted_key['public_key']) then
132
132
  # decrypt the key with PIN
133
133
 
134
- raise Exception.new("PIN not set and no keys provided. Cannot sign transaction.") unless @encryption_key or @keys.size > 0
135
-
136
- key = Helper.extractKey(encrypted_key['encrypted_passphrase'], @encryption_key)
134
+ raise Exception.new("PIN not set and no keys provided. Cannot sign transaction.") unless !@pin.nil? or @keys.size > 0
135
+
136
+ key = Helper.dynamicExtractKey(encrypted_key, @pin)
137
+
137
138
  raise Exception.new("Public key mismatch for requested signer and ourselves. Invalid Secret PIN detected.") unless key.public_key_hex.eql?(encrypted_key["public_key"])
138
139
 
139
140
  # store this key for later use
@@ -2,6 +2,18 @@ module BlockIo
2
2
 
3
3
  class Helper
4
4
 
5
+ LEGACY_DECRYPTION_ALGORITHM = {
6
+ :pbkdf2_salt => "",
7
+ :pbkdf2_iterations => 2048,
8
+ :pbkdf2_hash_function => "SHA256",
9
+ :pbkdf2_phase1_key_length => 16,
10
+ :pbkdf2_phase2_key_length => 32,
11
+ :aes_iv => nil,
12
+ :aes_cipher => "AES-256-ECB",
13
+ :aes_auth_tag => nil,
14
+ :aes_auth_data => nil
15
+ }
16
+
5
17
  def self.allSignaturesPresent?(tx, inputs, signatures, input_address_data)
6
18
  # returns true if transaction has all signatures present
7
19
 
@@ -152,6 +164,44 @@ module BlockIo
152
164
  sighash
153
165
 
154
166
  end
167
+
168
+ def self.getDecryptionAlgorithm(user_key_algorithm = nil)
169
+ # mainly used so existing unit tests do not break
170
+
171
+ algorithm = ({}).merge(LEGACY_DECRYPTION_ALGORITHM)
172
+
173
+ if !user_key_algorithm.nil? then
174
+ algorithm[:pbkdf2_salt] = user_key_algorithm['pbkdf2_salt']
175
+ algorithm[:pbkdf2_iterations] = user_key_algorithm['pbkdf2_iterations']
176
+ algorithm[:pbkdf2_hash_function] = user_key_algorithm['pbkdf2_hash_function']
177
+ algorithm[:pbkdf2_phase1_key_length] = user_key_algorithm['pbkdf2_phase1_key_length']
178
+ algorithm[:pbkdf2_phase2_key_length] = user_key_algorithm['pbkdf2_phase2_key_length']
179
+ algorithm[:aes_iv] = user_key_algorithm['aes_iv']
180
+ algorithm[:aes_cipher] = user_key_algorithm['aes_cipher']
181
+ algorithm[:aes_auth_tag] = user_key_algorithm['aes_auth_tag']
182
+ algorithm[:aes_auth_data] = user_key_algorithm['aes_auth_data']
183
+ end
184
+
185
+ algorithm
186
+
187
+ end
188
+
189
+ def self.dynamicExtractKey(user_key, pin)
190
+ # user_key object contains the encrypted user key and decryption algorithm
191
+
192
+ algorithm = self.getDecryptionAlgorithm(user_key['algorithm'])
193
+
194
+ aes_key = self.pinToAesKey(pin, algorithm[:pbkdf2_iterations],
195
+ algorithm[:pbkdf2_salt],
196
+ algorithm[:pbkdf2_hash_function],
197
+ algorithm[:pbkdf2_phase1_key_length],
198
+ algorithm[:pbkdf2_phase2_key_length])
199
+
200
+ decrypted = self.decrypt(user_key['encrypted_passphrase'], aes_key, algorithm[:aes_iv], algorithm[:aes_cipher], algorithm[:aes_auth_tag], algorithm[:aes_auth_data])
201
+
202
+ Key.from_passphrase(decrypted)
203
+
204
+ end
155
205
 
156
206
  def self.extractKey(encrypted_data, b64_enc_key)
157
207
  # passphrase is in plain text
@@ -169,24 +219,25 @@ module BlockIo
169
219
  OpenSSL::Digest::SHA256.digest(value).unpack("H*")[0]
170
220
  end
171
221
 
172
- def self.pinToAesKey(secret_pin, iterations = 2048)
222
+ def self.pinToAesKey(secret_pin, iterations = 2048, salt = "", hash_function = "SHA256", pbkdf2_phase1_key_length = 16, pbkdf2_phase2_key_length = 32)
173
223
  # converts the pincode string to PBKDF2
174
224
  # returns a base64 version of PBKDF2 pincode
175
- salt = ""
176
225
 
226
+ raise Exception.new("Unknown hash function specified. Are you using current version of this library?") unless hash_function == "SHA256"
227
+
177
228
  part1 = OpenSSL::PKCS5.pbkdf2_hmac(
178
229
  secret_pin,
179
- "",
180
- 1024,
181
- 128/8,
230
+ salt,
231
+ iterations/2,
232
+ pbkdf2_phase1_key_length,
182
233
  OpenSSL::Digest::SHA256.new
183
234
  ).unpack("H*")[0]
184
235
 
185
236
  part2 = OpenSSL::PKCS5.pbkdf2_hmac(
186
237
  part1,
187
- "",
188
- 1024,
189
- 256/8,
238
+ salt,
239
+ iterations/2,
240
+ pbkdf2_phase2_key_length,
190
241
  OpenSSL::Digest::SHA256.new
191
242
  ) # binary
192
243
 
@@ -195,15 +246,19 @@ module BlockIo
195
246
  end
196
247
 
197
248
  # Decrypts a block of data (encrypted_data) given an encryption key
198
- def self.decrypt(encrypted_data, b64_enc_key, iv = nil, cipher_type = "AES-256-ECB")
249
+ def self.decrypt(encrypted_data, b64_enc_key, iv = nil, cipher_type = "AES-256-ECB", auth_tag = nil, auth_data = nil)
250
+
251
+ raise Exception.new("Auth tag must be 16 bytes exactly.") unless auth_tag.nil? or auth_tag.size == 32
199
252
 
200
253
  response = nil
201
254
 
202
255
  begin
203
- aes = OpenSSL::Cipher.new(cipher_type)
256
+ aes = OpenSSL::Cipher.new(cipher_type.downcase)
204
257
  aes.decrypt
205
258
  aes.key = b64_enc_key.unpack("m0")[0]
206
- aes.iv = iv unless iv.nil?
259
+ aes.iv = [iv].pack("H*") unless iv.nil?
260
+ aes.auth_tag = [auth_tag].pack("H*") unless auth_tag.nil?
261
+ aes.auth_data = [auth_data].pack("H*") unless auth_data.nil?
207
262
  response = aes.update(encrypted_data.unpack("m0")[0]) << aes.final
208
263
  rescue Exception => e
209
264
  # decryption failed, must be an invalid Secret PIN
@@ -214,12 +269,17 @@ module BlockIo
214
269
  end
215
270
 
216
271
  # Encrypts a block of data given an encryption key
217
- def self.encrypt(data, b64_enc_key, iv = nil, cipher_type = "AES-256-ECB")
218
- aes = OpenSSL::Cipher.new(cipher_type)
272
+ def self.encrypt(data, b64_enc_key, iv = nil, cipher_type = "AES-256-ECB", auth_data = nil)
273
+ aes = OpenSSL::Cipher.new(cipher_type.downcase)
219
274
  aes.encrypt
220
275
  aes.key = b64_enc_key.unpack("m0")[0]
221
- aes.iv = iv unless iv.nil?
222
- [aes.update(data) << aes.final].pack("m0")
276
+ aes.iv = [iv].pack("H*") unless iv.nil?
277
+ aes.auth_data = [auth_data].pack("H*") unless auth_data.nil?
278
+ result = [aes.update(data) << aes.final].pack("m0")
279
+ auth_tag = (cipher_type.end_with?("-GCM") ? aes.auth_tag.unpack("H*")[0] : nil)
280
+
281
+ {:aes_auth_tag => auth_tag, :aes_cipher_text => result, :aes_iv => iv, :aes_cipher => cipher_type, :aes_auth_data => auth_data}
282
+
223
283
  end
224
284
 
225
285
  # courtesy bitcoin-ruby
@@ -1,3 +1,3 @@
1
1
  module BlockIo
2
- VERSION = "3.0.0"
2
+ VERSION = "3.0.1"
3
3
  end
data/spec/helper_spec.rb CHANGED
@@ -6,24 +6,31 @@ describe "Helper.sha256" do
6
6
  end
7
7
 
8
8
  describe "Helper.pinToAesKey" do
9
- it "deadbeef" do
10
- expect(BlockIo::Helper.pinToAesKey("deadbeef", false)).to eq([["b87ddac3d84865782a0edbc21b5786d56795dd52bab0fe49270b3726372a83fe"].pack("H*")].pack("m0"))
9
+ describe "no_salt" do
10
+ it "deadbeef" do
11
+ expect(BlockIo::Helper.pinToAesKey("deadbeef")).to eq([["b87ddac3d84865782a0edbc21b5786d56795dd52bab0fe49270b3726372a83fe"].pack("H*")].pack("m0"))
12
+ end
13
+ end
14
+ describe "salt" do
15
+ it "922445847c173e90667a19d90729e1fb" do
16
+ expect(BlockIo::Helper.pinToAesKey("deadbeef", 500000, "922445847c173e90667a19d90729e1fb")).to eq([["f206403c6bad20e1c8cb1f3318e17cec5b2da0560ed6c7b26826867452534172"].pack("H*")].pack("m0"))
17
+ end
11
18
  end
12
19
  end
13
20
 
14
- describe "Helper.encrypt" do
21
+ describe "Helper.encrypt_aes256ecb" do
15
22
  before(:each) do
16
23
  @encryption_key = BlockIo::Helper.pinToAesKey("deadbeef")
17
24
  @encrypted_data = BlockIo::Helper.encrypt("beadbeef", @encryption_key)
18
25
  end
19
26
 
20
27
  it "beadbeef" do
21
- expect(@encrypted_data).to eq("3wIJtPoC8KO6S7x6LtrN0g==")
28
+ expect(@encrypted_data[:aes_cipher_text]).to eq("3wIJtPoC8KO6S7x6LtrN0g==")
22
29
  end
23
30
 
24
31
  end
25
32
 
26
- describe "Helper.decrypt" do
33
+ describe "Helper.decrypt_aes256ecb" do
27
34
  before(:each) do
28
35
  @encryption_key = BlockIo::Helper.pinToAesKey("deadbeef")
29
36
  @bad_encryption_key = BlockIo::Helper.pinToAesKey(SecureRandom.hex(8))
@@ -31,7 +38,7 @@ describe "Helper.decrypt" do
31
38
  end
32
39
 
33
40
  it "encryption_key" do
34
- @decrypted_data = BlockIo::Helper.decrypt(@encrypted_data, @encryption_key)
41
+ @decrypted_data = BlockIo::Helper.decrypt(@encrypted_data[:aes_cipher_text], @encryption_key)
35
42
  expect(@decrypted_data).to eq("beadbeef")
36
43
  end
37
44
 
@@ -42,3 +49,106 @@ describe "Helper.decrypt" do
42
49
  end
43
50
 
44
51
  end
52
+
53
+ describe "Helper.encrypt_aes256cbc" do
54
+ before(:each) do
55
+ @encryption_key = BlockIo::Helper.pinToAesKey("deadbeef", 500000, "922445847c173e90667a19d90729e1fb")
56
+ @encrypted_data = BlockIo::Helper.encrypt("beadbeef", @encryption_key, "11bc22166c8cf8560e5fa7e5c622bb0f", "AES-256-CBC")
57
+ end
58
+
59
+ it "beadbeef" do
60
+ expect(@encrypted_data[:aes_cipher_text]).to eq("LExu1rUAtIBOekslc328Lw==")
61
+ end
62
+
63
+ end
64
+
65
+ describe "Helper.decrypt_aes256cbc" do
66
+ before(:each) do
67
+ @encryption_key = BlockIo::Helper.pinToAesKey("deadbeef", 500000, "922445847c173e90667a19d90729e1fb")
68
+ @bad_encryption_key = BlockIo::Helper.pinToAesKey(SecureRandom.hex(8))
69
+ @encrypted_data = BlockIo::Helper.encrypt("beadbeef", @encryption_key, "11bc22166c8cf8560e5fa7e5c622bb0f", "AES-256-CBC")
70
+ end
71
+
72
+ it "encryption_key" do
73
+ @decrypted_data = BlockIo::Helper.decrypt(@encrypted_data[:aes_cipher_text], @encryption_key, @encrypted_data[:aes_iv], @encrypted_data[:aes_cipher])
74
+ expect(@decrypted_data).to eq("beadbeef")
75
+ end
76
+
77
+ it "bad_encryption_key" do
78
+ expect{
79
+ BlockIo::Helper.decrypt(@encrypted_data[:aes_cipher_text], @bad_encryption_key, @encrypted_data[:aes_iv], @encrypted_data[:aes_cipher])
80
+ }.to raise_error(Exception, "Invalid Secret PIN provided.")
81
+ end
82
+
83
+ end
84
+
85
+ describe "Helper.decrypt_aes256gcm" do
86
+ before(:each) do
87
+ @encryption_key = BlockIo::Helper.pinToAesKey("deadbeef", 500000, "922445847c173e90667a19d90729e1fb")
88
+ @bad_encryption_key = BlockIo::Helper.pinToAesKey(SecureRandom.hex(8))
89
+ @encrypted_data = BlockIo::Helper.encrypt("beadbeef", @encryption_key, "a57414b88b67f977829cbdca", "AES-256-GCM", "")
90
+ end
91
+
92
+ it "encryption_key" do
93
+ @decrypted_data = BlockIo::Helper.decrypt(@encrypted_data[:aes_cipher_text],
94
+ @encryption_key, @encrypted_data[:aes_iv],
95
+ @encrypted_data[:aes_cipher],
96
+ @encrypted_data[:aes_auth_tag],
97
+ @encrypted_data[:aes_auth_data])
98
+ expect(@decrypted_data).to eq("beadbeef")
99
+ end
100
+
101
+ it "encryption_key_bad_auth_tag" do
102
+ expect{
103
+ BlockIo::Helper.decrypt(@encrypted_data[:aes_cipher_text],
104
+ @encryption_key, @encrypted_data[:aes_iv],
105
+ @encrypted_data[:aes_cipher],
106
+ @encrypted_data[:aes_auth_tag][0..30],
107
+ @encrypted_data[:aes_auth_data])
108
+ }.to raise_error(Exception, "Auth tag must be 16 bytes exactly.")
109
+ end
110
+
111
+ it "bad_encryption_key" do
112
+ expect{
113
+ BlockIo::Helper.decrypt(@encrypted_data[:aes_cipher_text], @bad_encryption_key, @encrypted_data[:aes_iv], @encrypted_data[:aes_cipher], @encrypted_data[:aes_auth_tag],
114
+ @encrypted_data[:aes_auth_data])
115
+ }.to raise_error(Exception, "Invalid Secret PIN provided.")
116
+ end
117
+
118
+ end
119
+
120
+ describe "Helper.encrypt_aes256gcm" do
121
+ before(:each) do
122
+ @encryption_key = BlockIo::Helper.pinToAesKey("deadbeef", 500000, "922445847c173e90667a19d90729e1fb")
123
+ @encrypted_data = BlockIo::Helper.encrypt("beadbeef", @encryption_key, "a57414b88b67f977829cbdca", "AES-256-GCM", "")
124
+ end
125
+
126
+ it "beadbeef" do
127
+ expect(@encrypted_data[:aes_cipher_text]).to eq("ELV56Z57KoA=")
128
+ expect(@encrypted_data[:aes_auth_tag]).to eq("adeb7dfe53027bdda5824dc524d5e55a")
129
+ end
130
+
131
+ end
132
+
133
+ describe "Helper.dynamicExtractKey" do
134
+
135
+ before(:each) do
136
+ @aes256cbc_user_key = Oj.safe_load('{"encrypted_passphrase":"LExu1rUAtIBOekslc328Lw==","public_key":"02f87f787bffb30396984cb6b3a9d6830f32d5b656b3e39b0abe4f3b3c35d99323","algorithm":{"pbkdf2_salt":"922445847c173e90667a19d90729e1fb","pbkdf2_iterations":500000,"pbkdf2_hash_function":"SHA256","pbkdf2_phase1_key_length":16,"pbkdf2_phase2_key_length":32,"aes_iv":"11bc22166c8cf8560e5fa7e5c622bb0f","aes_cipher":"AES-256-CBC","aes_auth_tag":null,"aes_auth_data":null}}')
137
+ @aes256gcm_user_key = Oj.safe_load('{"encrypted_passphrase":"ELV56Z57KoA=","public_key":"02f87f787bffb30396984cb6b3a9d6830f32d5b656b3e39b0abe4f3b3c35d99323","algorithm":{"pbkdf2_salt":"922445847c173e90667a19d90729e1fb","pbkdf2_iterations":500000,"pbkdf2_hash_function":"SHA256","pbkdf2_phase1_key_length":16,"pbkdf2_phase2_key_length":32,"aes_iv":"a57414b88b67f977829cbdca","aes_cipher":"AES-256-GCM","aes_auth_tag":"adeb7dfe53027bdda5824dc524d5e55a","aes_auth_data":""}}')
138
+ @aes256ecb_user_key = Oj.safe_load('{"encrypted_passphrase":"3wIJtPoC8KO6S7x6LtrN0g==","public_key":"02f87f787bffb30396984cb6b3a9d6830f32d5b656b3e39b0abe4f3b3c35d99323","algorithm":{"pbkdf2_salt":"","pbkdf2_iterations":2048,"pbkdf2_hash_function":"SHA256","pbkdf2_phase1_key_length":16,"pbkdf2_phase2_key_length":32,"aes_iv":null,"aes_cipher":"AES-256-ECB","aes_auth_tag":null,"aes_auth_data":null}}')
139
+ @pin = "deadbeef"
140
+ end
141
+
142
+ it "aes256ecb_success" do
143
+ expect(BlockIo::Helper.dynamicExtractKey(@aes256ecb_user_key, @pin).public_key_hex).to eq(BlockIo::Key.from_passphrase("beadbeef").public_key_hex)
144
+ end
145
+
146
+ it "aes256cbc_success" do
147
+ expect(BlockIo::Helper.dynamicExtractKey(@aes256cbc_user_key, @pin).public_key_hex).to eq(BlockIo::Key.from_passphrase("beadbeef").public_key_hex)
148
+ end
149
+
150
+ it "aes256gcm_success" do
151
+ expect(BlockIo::Helper.dynamicExtractKey(@aes256gcm_user_key, @pin).public_key_hex).to eq(BlockIo::Key.from_passphrase("beadbeef").public_key_hex)
152
+ end
153
+
154
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: block_io
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0
4
+ version: 3.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Atif Nazir
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-26 00:00:00.000000000 Z
11
+ date: 2021-06-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler