Skip to content

Commit 30f91d5

Browse files
committed
use ruby-based pbkdf2
2 parents 5aa7fdd + 720aadf commit 30f91d5

File tree

4 files changed

+75
-13
lines changed

4 files changed

+75
-13
lines changed

README.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,12 @@ And then execute:
1414

1515
Or install it yourself as:
1616

17-
$ gem install block_io -v=1.0.2
17+
$ gem install block_io -v=1.0.4
1818

1919
## Changelog
2020

21-
*09/27/14*: Now supporting client-side signatures. API v2 recommended.
22-
23-
*07/01/14*: Forcing TLSv1 usage since Block.io does not support SSLv3 due to its vulnerable nature. Fixed:
24-
HTTPClient.new.ssl_config.ssl_version = :TLSv1
21+
*11/03/14*: Reduce dependence on OpenSSL. PBKDF2 function is now Ruby-based. Should work well with Heroku's libraries.
22+
*10/18/14*: Now using deterministic signatures (RFC6979), and BIP62 to hinder transaction malleability.
2523

2624

2725
## Usage

block_io.gemspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ Gem::Specification.new do |spec|
2424
spec.add_runtime_dependency "httpclient", "~> 2.4", '>= 2.4.0'
2525
spec.add_runtime_dependency "json", "~> 1.8", '>= 1.8.1'
2626
spec.add_runtime_dependency "connection_pool", "~> 2.0", '>= 2.0.0'
27+
spec.add_runtime_dependency "pbkdf2-ruby", '~> 0.2', '>= 0.2.1'
2728
end

lib/block_io.rb

Lines changed: 70 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
require 'ecdsa'
66
require 'openssl'
77
require 'digest'
8+
require 'pbkdf2'
89
require 'securerandom'
910
require 'base64'
1011

@@ -160,14 +161,20 @@ def public_key
160161
def sign(data)
161162
# signed the given hexadecimal string
162163

163-
nonce = 1 + SecureRandom.random_number(@group.order - 1) # nonce, can be made deterministic TODO
164+
nonce = deterministicGenerateK([data].pack("H*"), @private_key) # RFC6979
164165

165-
signature = ECDSA.sign(@group, @private_key, [data].pack("H*"), nonce)
166+
signature = ECDSA.sign(@group, @private_key, data.to_i(16), nonce)
166167

167-
# DER encode this, and return it in hex form
168+
# BIP0062 -- use lower S values only
169+
r, s = signature.components
168170

169-
return ECDSA::Format::SignatureDerString.encode(signature).unpack("H*")[0]
171+
over_two = @group.order >> 1 # half of what it was
172+
s = @group.order - s if (s > over_two)
170173

174+
signature = ECDSA::Signature.new(r, s)
175+
176+
# DER encode this, and return it in hex form
177+
return ECDSA::Format::SignatureDerString.encode(signature).unpack("H*")[0]
171178
end
172179

173180
def self.from_passphrase(passphrase)
@@ -182,6 +189,60 @@ def self.from_passphrase(passphrase)
182189
return Key.new(hashed_key)
183190
end
184191

192+
def isPositive(i)
193+
sig = "!+-"[i <=> 0]
194+
195+
return sig.eql?("+")
196+
end
197+
198+
def deterministicGenerateK(data, privkey, group = ECDSA::Group::Secp256k1)
199+
# returns a deterministic K -- RFC6979
200+
201+
hash = data.bytes.to_a
202+
203+
x = [privkey.to_s(16)].pack("H*").bytes.to_a
204+
205+
k = []
206+
32.times { k.insert(0, 0) }
207+
208+
v = []
209+
32.times { v.insert(0, 1) }
210+
211+
# step D
212+
k = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), [].concat(v).concat([0]).concat(x).concat(hash).pack("C*")).bytes.to_a
213+
214+
# step E
215+
v = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), v.pack("C*")).bytes.to_a
216+
217+
# puts "E: " + v.pack("C*").unpack("H*")[0]
218+
219+
# step F
220+
k = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), [].concat(v).concat([1]).concat(x).concat(hash).pack("C*")).bytes.to_a
221+
222+
# step G
223+
v = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), v.pack("C*")).bytes.to_a
224+
225+
# step H2b (Step H1/H2a ignored)
226+
v = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), v.pack("C*")).bytes.to_a
227+
228+
h2b = v.pack("C*").unpack("H*")[0]
229+
tNum = h2b.to_i(16)
230+
231+
# step H3
232+
while (!isPositive(tNum) or tNum >= group.order) do
233+
# k = crypto.HmacSHA256(Buffer.concat([v, new Buffer([0])]), k)
234+
k = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), [].concat(v).concat([0]).pack("C*")).bytes.to_a
235+
236+
# v = crypto.HmacSHA256(v, k)
237+
v = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), v.pack("C*")).bytes.to_a
238+
239+
# T = BigInteger.fromBuffer(v)
240+
tNum = v.pack("C*").unpack("H*")[0].to_i(16)
241+
end
242+
243+
return tNum
244+
end
245+
185246
end
186247

187248
module Helper
@@ -207,9 +268,11 @@ def self.pinToAesKey(secret_pin, iterations = 2048)
207268
# converts the pincode string to PBKDF2
208269
# returns a base64 version of PBKDF2 pincode
209270
salt = ""
210-
aes_key_bin = OpenSSL::PKCS5.pbkdf2_hmac(secret_pin, salt, iterations/2, 16, OpenSSL::Digest::SHA256.new)
211-
aes_key_bin = OpenSSL::PKCS5.pbkdf2_hmac(aes_key_bin.unpack("H*")[0], salt, iterations/2, 32, OpenSSL::Digest::SHA256.new)
212-
271+
272+
# pbkdf2-ruby gem uses SHA256 as the default hash function
273+
aes_key_bin = PBKDF2.new(:password => secret_pin, :salt => salt, :iterations => iterations/2, :key_length => 128/8).value
274+
aes_key_bin = PBKDF2.new(:password => aes_key_bin.unpack("H*")[0], :salt => salt, :iterations => iterations/2, :key_length => 256/8).value
275+
213276
return Base64.strict_encode64(aes_key_bin) # the base64 encryption key
214277
end
215278

lib/block_io/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module BlockIo
2-
VERSION = "1.0.2"
2+
VERSION = "1.0.4"
33
end

0 commit comments

Comments
 (0)