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 +4 -4
- data/README.md +2 -1
- data/lib/block_io/client.rb +5 -4
- data/lib/block_io/helper.rb +75 -15
- data/lib/block_io/version.rb +1 -1
- data/spec/helper_spec.rb +116 -6
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 42de3243361e4c2a75e0357745ac507c381332d927453d6b35f81b63f30ea723
|
4
|
+
data.tar.gz: f2416231c346fe92b0f39b665390be3c511aab2b62d9c8c8e2f07c580c12e30e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
data/lib/block_io/client.rb
CHANGED
@@ -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
|
-
@
|
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
|
135
|
-
|
136
|
-
key = Helper.
|
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
|
data/lib/block_io/helper.rb
CHANGED
@@ -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
|
-
|
181
|
-
|
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
|
-
|
189
|
-
|
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
|
-
|
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
|
data/lib/block_io/version.rb
CHANGED
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
|
-
|
10
|
-
|
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.
|
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.
|
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.
|
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-
|
11
|
+
date: 2021-06-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|