block_io 3.0.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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