shamir-secret-sharing 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.rdoc +75 -0
- data/Rakefile +14 -0
- data/lib/shamir-secret-sharing.rb +380 -0
- data/shamir-secret-sharing.gemspec +22 -0
- metadata +47 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d07e642746c89a96dde0b5f5d66fa254b4cfe3d3
|
4
|
+
data.tar.gz: 613b4c2987524ee3fd2ee1a927dc49eb0ba98867
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b12215854edfb42ed511d29f5e58548faef927cf785a5328690161de11ea948ee82d1232eb19831eead3e6ccb073b1e9fa1788de810c6e7677238efcf6fabc98
|
7
|
+
data.tar.gz: db8dae71854f87544628d1d78f4e76601c873e304c3398d84a889867dbb5c45ffae1fe1b1311e8dd877bddd7d3765d6fc962a6758875883d55c5e307d81119e2
|
data/README.rdoc
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
= shamir-secret-sharing
|
2
|
+
|
3
|
+
This is a ruby library for Shamir's Secret Sharing. ( http://en.wikipedia.org/wiki/Shamir's_Secret_Sharing )
|
4
|
+
|
5
|
+
== Compatible with...
|
6
|
+
|
7
|
+
* ruby 1.9.3
|
8
|
+
* ruby 2.0.0
|
9
|
+
|
10
|
+
|
11
|
+
== Installation
|
12
|
+
|
13
|
+
We assume you already have a ruby 1.9 or 2.0 compatible interpreter and rubygems environment.
|
14
|
+
|
15
|
+
git clone https://github.com/lian/shamir-secret-sharing.git; cd shamir-secret-sharing
|
16
|
+
irb -Ilib -rshamir-secret-sharing
|
17
|
+
|
18
|
+
If you want to have it available system-wide, just build the gem and install it:
|
19
|
+
|
20
|
+
gem build shamir-secret-sharing.gemspec && gem install shamir-secret-sharing-*.gem
|
21
|
+
|
22
|
+
== Tests
|
23
|
+
|
24
|
+
The tests can be run with
|
25
|
+
|
26
|
+
rake
|
27
|
+
|
28
|
+
If you make changes to the code or add functionality, please also add tests.
|
29
|
+
|
30
|
+
= Example
|
31
|
+
|
32
|
+
== Split and Combine
|
33
|
+
|
34
|
+
# split a secret into 4 shares. 3 of them are needed to combine the secret again.
|
35
|
+
shares = ShamirSecretSharing::Base58.split(secret="hello", available=4, needed=3)
|
36
|
+
shares #=> ["FPGPdS98vRUCy9", "VmTjT28nES7Pck", "k9eRjVVpWP7uzj", "zXqDJNRpPttPjR"]
|
37
|
+
|
38
|
+
# combine the secret again using at least 3 shares
|
39
|
+
ShamirSecretSharing::Base58.combine( ["VmTjT28nES7Pck", "zXqDJNRpPttPjR", "k9eRjVVpWP7uzj"] )
|
40
|
+
#=> "hello"
|
41
|
+
|
42
|
+
# not enough shares
|
43
|
+
ShamirSecretSharing::Base58.combine( ["VmTjT28nES7Pck", "zXqDJNRpPttPjR"] )
|
44
|
+
#=> false
|
45
|
+
|
46
|
+
# wrong shares
|
47
|
+
ShamirSecretSharing::Base58.combine( ["VmTjT28nES7Pck", "zXqDJNRpPttPjR", "some-wrong-share"] )
|
48
|
+
#=> false
|
49
|
+
|
50
|
+
== Encypt and Decrypt
|
51
|
+
|
52
|
+
Split and combine only work with a secret length below 512 bytes and the resulting share lengths are equivalent to the secret length. That means a 128 byte secret has a very long (128 byte) share length.
|
53
|
+
Encrypt and decrypt are used to encrypt a large piece of data while keeping the share lengths to a fixed and reasonable size. It does that by AES-256-CBC encrypting the data with a random key and only then splitting that fixed size key into shares.
|
54
|
+
|
55
|
+
# encrypt the data and create 4 shares. Later 3 shares and the encrypted data are needed to decrypt it again. This example uses a 96 bit random key for encryption.
|
56
|
+
shares, encrypted = ShamirSecretSharing::Base58.encrypt(data="Lorem ipsum dolor sit amet, consectetur adipiscing elit.", 4, 3, 96)
|
57
|
+
shares #=> ["3QZNRFYZHKUpnv96cE6eVwPP", "5p611mjtxXBgPDx1m2SRwCKr", "8DcdGMD9i3kWGrYEVGtQLSWA", "Ad9NLPDVSnHfbqVhnmNn8YK4"]
|
58
|
+
encrypted #=> "4PHcPwzzk48EjmusQz27t3X1rS7hAi9kwKgZJ7UMDR99rJgi19aHM8e7syh6uVrJ6HbcbgRmEUcR7hmkDvrYaJXH"
|
59
|
+
|
60
|
+
# combine the shares and decrypt the encrypted data
|
61
|
+
ShamirSecretSharing::Base58.decrypt(["3QZNRFYZHKUpnv96cE6eVwPP", "8DcdGMD9i3kWGrYEVGtQLSWA", "Ad9NLPDVSnHfbqVhnmNn8YK4"], "4PHcPwzzk48EjmusQz27t3X1rS7hAi9kwKgZJ7UMDR99rJgi19aHM8e7syh6uVrJ6HbcbgRmEUcR7hmkDvrYaJXH")
|
62
|
+
#=> "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
63
|
+
|
64
|
+
# not enough shares
|
65
|
+
ShamirSecretSharing::Base58.decrypt(["3QZNRFYZHKUpnv96cE6eVwPP", "8DcdGMD9i3kWGrYEVGtQLSWA"], "4PHcPwzzk48EjmusQz27t3X1rS7hAi9kwKgZJ7UMDR99rJgi19aHM8e7syh6uVrJ6HbcbgRmEUcR7hmkDvrYaJXH")
|
66
|
+
#=> false
|
67
|
+
|
68
|
+
# wrong shares
|
69
|
+
ShamirSecretSharing::Base58.decrypt(["3QZNRFYZHKUpnv96cE6eVwPP", "8DcdGMD9i3kWGrYEVGtQLSWA", "some-wrong-share"], "4PHcPwzzk48EjmusQz27t3X1rS7hAi9kwKgZJ7UMDR99rJgi19aHM8e7syh6uVrJ6HbcbgRmEUcR7hmkDvrYaJXH")
|
70
|
+
#=> false
|
71
|
+
|
72
|
+
# wrong encrypted data
|
73
|
+
ShamirSecretSharing::Base58.decrypt(["3QZNRFYZHKUpnv96cE6eVwPP", "8DcdGMD9i3kWGrYEVGtQLSWA", "Ad9NLPDVSnHfbqVhnmNn8YK4"], "wrong-encrypted-data")
|
74
|
+
#=> false
|
75
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/gem_tasks'
|
3
|
+
rescue LoadError
|
4
|
+
end
|
5
|
+
|
6
|
+
RUBY = 'ruby' unless defined?(RUBY)
|
7
|
+
|
8
|
+
task :default => :tests
|
9
|
+
|
10
|
+
# test runner [0/61]
|
11
|
+
desc 'Run all bacon specs with pretty output'
|
12
|
+
task :tests do
|
13
|
+
sh RUBY, 'lib/shamir-secret-sharing.rb'
|
14
|
+
end
|
@@ -0,0 +1,380 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'digest/sha1'
|
3
|
+
|
4
|
+
class ShamirSecretSharing
|
5
|
+
VERSION = '0.0.1'
|
6
|
+
|
7
|
+
def self.pack(shares); shares; end
|
8
|
+
def self.unpack(shares); shares; end
|
9
|
+
def self.encode(string); string; end
|
10
|
+
def self.decode(string); string; end
|
11
|
+
|
12
|
+
def self.smallest_prime_of_bytelength(bytelength)
|
13
|
+
n = OpenSSL::BN.new((2**(bytelength*8)+1).to_s)
|
14
|
+
loop{ break if n.prime_fasttest?(20); n += 2 }
|
15
|
+
n
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.split(secret, available, needed, do_data_checksum=true)
|
19
|
+
raise ArgumentError, "needed must be <= available" unless needed <= available
|
20
|
+
raise ArgumentError, "needed must be >= 2" unless needed >= 2
|
21
|
+
raise ArgumentError, "available must be <= 250" unless available <= 250
|
22
|
+
|
23
|
+
if do_data_checksum
|
24
|
+
checksum = Digest::SHA512.digest(secret)[0...2]
|
25
|
+
num_bytes = secret.bytesize+2
|
26
|
+
secret = OpenSSL::BN.new((checksum + secret).unpack("H*")[0], 16) rescue OpenSSL::BN.new("0")
|
27
|
+
#num_bytes = secret.to_s(0).unpack("N")[0]
|
28
|
+
raise ArgumentError, "bytelength of secret must be >= 1" if num_bytes < 3
|
29
|
+
raise ArgumentError, "bytelength of secret must be <= 512" if num_bytes > 513
|
30
|
+
else
|
31
|
+
num_bytes = secret.bytesize
|
32
|
+
secret = OpenSSL::BN.new(secret.unpack("H*")[0], 16) rescue OpenSSL::BN.new("0") # without checksum
|
33
|
+
raise ArgumentError, "bytelength of secret must be >= 1" if num_bytes < 1
|
34
|
+
raise ArgumentError, "bytelength of secret must be <= 512" if num_bytes > 512
|
35
|
+
end
|
36
|
+
|
37
|
+
prime = smallest_prime_of_bytelength(num_bytes)
|
38
|
+
coef = [ secret ] + Array.new(needed-1){ OpenSSL::BN.rand(num_bytes * 8) }
|
39
|
+
|
40
|
+
shares = (1..available).map{|x|
|
41
|
+
x = OpenSSL::BN.new(x.to_s)
|
42
|
+
y = coef.each_with_index.inject(OpenSSL::BN.new("0")){|acc, (c, idx)|
|
43
|
+
acc + c * x.mod_exp(idx, prime)
|
44
|
+
} % prime
|
45
|
+
[x, num_bytes, y]
|
46
|
+
}
|
47
|
+
pack(shares)
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.combine(shares, do_raise=false, do_data_checksum=true)
|
51
|
+
return false if shares.size < 2
|
52
|
+
shares = unpack(shares)
|
53
|
+
num_bytes = shares[0][1]
|
54
|
+
prime = smallest_prime_of_bytelength(num_bytes)
|
55
|
+
|
56
|
+
secret = shares.inject(OpenSSL::BN.new("0")){|secret,(x,num_bytes,y)|
|
57
|
+
l_x = l(x, shares, prime)
|
58
|
+
summand = OpenSSL::BN.new(y.to_s).mod_mul(l_x, prime)
|
59
|
+
secret = (secret + summand) % prime
|
60
|
+
}
|
61
|
+
if do_data_checksum
|
62
|
+
checksum, secret = [ secret.to_s(16).rjust(num_bytes*2, '0') ].pack("H*").unpack("a2a*")
|
63
|
+
checksum == Digest::SHA512.digest(secret)[0...2] ? secret : false
|
64
|
+
else
|
65
|
+
secret = [ secret.to_s(16).rjust(num_bytes*2, '0') ].pack("H*")
|
66
|
+
end
|
67
|
+
rescue ShareChecksumError, ShareDecodeError => ex
|
68
|
+
raise if do_raise
|
69
|
+
false
|
70
|
+
end
|
71
|
+
|
72
|
+
# Part of the Lagrange interpolation.
|
73
|
+
# This is l_j(0), i.e. # \prod_{x_j \neq x_i} \frac{-x_i}{x_j - x_i}
|
74
|
+
# for more information compare Wikipedia: # http://en.wikipedia.org/wiki/Lagrange_form
|
75
|
+
def self.l(current_x, shares, prime)
|
76
|
+
shares.select{|x,num_bytes,y| x != current_x }.map{|x,num_bytes,y|
|
77
|
+
minus_xi = OpenSSL::BN.new((-x).to_s)
|
78
|
+
one_over_xj_minus_xi = OpenSSL::BN.new((current_x - x).to_s).mod_inverse(prime)
|
79
|
+
minus_xi.mod_mul(one_over_xj_minus_xi, prime)
|
80
|
+
}.inject{|p,f| p.mod_mul(f, prime) }
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.encrypt(data, available, needed, key_bit_length=128)
|
84
|
+
key = key_bit_length.is_a?(String) ? key_bit_length : [ OpenSSL::BN.rand(key_bit_length).to_s(16) ].pack("H*")
|
85
|
+
c = OpenSSL::Cipher.new('aes-256-cbc').encrypt
|
86
|
+
c.key, c.iv = Digest::SHA512.digest(key).unpack("a32a16")
|
87
|
+
encrypted = c.update(data) << c.final
|
88
|
+
[ split(key, available, needed), encode(encrypted) ]
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.decrypt(shares, encrypted, do_raise=false)
|
92
|
+
key = combine(shares, do_raise)
|
93
|
+
return false unless key
|
94
|
+
|
95
|
+
encrypted_decoded = decode(encrypted) rescue nil
|
96
|
+
raise ShareDecodeError, "encrypted_data: #{encrypted}" unless encrypted_decoded
|
97
|
+
|
98
|
+
return false unless encrypted and key
|
99
|
+
c = OpenSSL::Cipher.new('aes-256-cbc').decrypt
|
100
|
+
c.key, c.iv = Digest::SHA512.digest(key).unpack("a32a16")
|
101
|
+
data = c.update(encrypted_decoded) << c.final
|
102
|
+
data
|
103
|
+
rescue OpenSSL::Cipher::CipherError, ShareDecodeError
|
104
|
+
raise if do_raise
|
105
|
+
false
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.split_with_sanity_check(secret, available, needed, do_data_checksum=true)
|
109
|
+
shares = split(secret, available, needed, do_data_checksum)
|
110
|
+
success = true
|
111
|
+
needed.upto(available).each{|n| shares.permutation(n).each{|shares| success = false if combine(shares) != secret } }
|
112
|
+
(needed-1).downto(2).each{|n| shares.permutation(n).each{|shares| success = false if combine(shares) != false } }
|
113
|
+
raise ShareSanityCheckError if success != true
|
114
|
+
shares
|
115
|
+
rescue ShareSanityCheckError
|
116
|
+
retry
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.encrypt_with_sanity_check(data, available, needed, key_bit_length=128)
|
120
|
+
shares, encrypted = encrypt(data, available, needed, key_bit_length)
|
121
|
+
success = true
|
122
|
+
needed.upto(available).each{|n| shares.permutation(n).each{|shares| success = false if decrypt(shares, encrypted) != data } }
|
123
|
+
(needed-1).downto(2).each{|n| shares.permutation(n).each{|shares| success = false if decrypt(shares, encrypted) != false } }
|
124
|
+
raise ShareSanityCheckError if success != true
|
125
|
+
[shares, encrypted]
|
126
|
+
rescue ShareSanityCheckError
|
127
|
+
retry
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
class Number < ShamirSecretSharing
|
132
|
+
def self.split(secret, available, needed)
|
133
|
+
num = OpenSSL::BN.new(secret.to_s)
|
134
|
+
raise ArgumentError, "available must be <= 9" unless available <= 9
|
135
|
+
raise ArgumentError, "num too large. bytelength must be <= 9" unless num.num_bytes <= 9
|
136
|
+
shares = ShamirSecretSharing.split([num.to_s(16)].pack("H*"), available, needed, do_data_checksum=nil)
|
137
|
+
shares.map{|i| i.join.to_i }
|
138
|
+
end
|
139
|
+
|
140
|
+
def self.combine(shares)
|
141
|
+
shares = shares.map{|i| i.to_s.match(/(\d)(\d)(\d+)/); [$1.to_i, $2.to_i, $3.to_i] }
|
142
|
+
ShamirSecretSharing.combine(shares, do_raise=false, do_data_checksum=nil).unpack("H*")[0].to_i(16)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
class ShareChecksumError < ::StandardError; end
|
147
|
+
class ShareDecodeError < ::StandardError; end
|
148
|
+
class ShareSanityCheckError < ::StandardError; end
|
149
|
+
|
150
|
+
class Packed < ShamirSecretSharing # packing format and checkum
|
151
|
+
def self.pack(shares)
|
152
|
+
shares.map{|x,num_bytes,y|
|
153
|
+
buf = [ x, num_bytes, y.to_s(16) ].pack("CnH*")
|
154
|
+
checksum = Digest::SHA512.digest(buf)[0...2]
|
155
|
+
encode(checksum << buf)
|
156
|
+
}
|
157
|
+
end
|
158
|
+
def self.unpack(shares)
|
159
|
+
shares.map{|i|
|
160
|
+
buf = decode(i) rescue nil
|
161
|
+
raise ShareDecodeError, "share: #{i}" unless buf
|
162
|
+
checksum, buf = buf.unpack("a2a*")
|
163
|
+
raise ShareChecksumError, "share: #{i}" unless checksum == Digest::SHA512.digest(buf)[0...2]
|
164
|
+
i = buf.unpack("CnH*"); [ i[0], i[1], i[2].to_i(16) ]
|
165
|
+
}
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
class Base58 < Packed
|
170
|
+
def self.encode(string)
|
171
|
+
string = string.unpack("H*")[0]
|
172
|
+
leading_zero_bytes = (string.match(/^([0]+)/) ? $1 : '').size / 2
|
173
|
+
("1"*leading_zero_bytes) + int_to_base58( string.to_i(16) )
|
174
|
+
end
|
175
|
+
def self.decode(string)
|
176
|
+
leading_zero_bytes = (string.match(/^([1]+)/) ? $1 : '').size
|
177
|
+
buf = base58_to_int(string).to_s(16); buf = (buf.bytesize.odd? ? '0'+buf : buf)
|
178
|
+
[ ("00"*leading_zero_bytes) + buf ].pack("H*")
|
179
|
+
end
|
180
|
+
def self.int_to_base58(int_val, leading_zero_bytes=0)
|
181
|
+
alpha, base58_val, base = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz", "", 58
|
182
|
+
while int_val > 0
|
183
|
+
int_val, remainder = int_val.divmod(base)
|
184
|
+
base58_val = alpha[remainder] + base58_val
|
185
|
+
end; base58_val
|
186
|
+
end
|
187
|
+
|
188
|
+
def self.base58_to_int(base58_val)
|
189
|
+
alpha, base = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz", 58
|
190
|
+
base58_val.reverse.each_char.with_index.inject(0) do |int_val, (char,index)|
|
191
|
+
raise ArgumentError, 'Value not a valid Base58 String.' unless char_index = alpha.index(char)
|
192
|
+
int_val + char_index*(base**index)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
class Base64 < Packed
|
198
|
+
def self.encode(string); [string].pack("m0"); end
|
199
|
+
def self.decode(string); string.unpack("m0")[0]; end
|
200
|
+
end
|
201
|
+
|
202
|
+
class Hex < Packed
|
203
|
+
def self.encode(string); string.unpack("H*")[0]; end
|
204
|
+
def self.decode(string); [string].pack("H*"); end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
|
209
|
+
|
210
|
+
|
211
|
+
if $0 == __FILE__
|
212
|
+
require "minitest/autorun"
|
213
|
+
|
214
|
+
class MiniTest::Unit::TestCase
|
215
|
+
def assert_raises_and_message(klass, msg, &blk)
|
216
|
+
err = assert_raises(klass, &blk); assert_equal msg, err.message
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
|
221
|
+
class TestShamirSecretSharing < MiniTest::Unit::TestCase
|
222
|
+
|
223
|
+
def helper(&b)
|
224
|
+
[ [6,3], [10, 2], [3,2], [100, 30] ].each{|available,needed| b.call(available, needed) }
|
225
|
+
end
|
226
|
+
|
227
|
+
|
228
|
+
def test_shamir_base58
|
229
|
+
secret = "hello"
|
230
|
+
helper{|available,needed|
|
231
|
+
shares = ShamirSecretSharing::Base58.split(secret, available, needed)
|
232
|
+
assert_equal secret, ShamirSecretSharing::Base58.combine(shares.shuffle[0...needed])
|
233
|
+
}
|
234
|
+
|
235
|
+
shares = ShamirSecretSharing::Base58.split_with_sanity_check(secret, available=3, needed=2)
|
236
|
+
assert_equal secret, ShamirSecretSharing::Base58.combine(shares.shuffle[0...needed])
|
237
|
+
end
|
238
|
+
|
239
|
+
def test_shamir_base64
|
240
|
+
secret = "hello"
|
241
|
+
helper{|available,needed|
|
242
|
+
shares = ShamirSecretSharing::Base64.split(secret, available, needed)
|
243
|
+
assert_equal secret, ShamirSecretSharing::Base64.combine(shares.shuffle[0...needed])
|
244
|
+
}
|
245
|
+
end
|
246
|
+
|
247
|
+
def test_shamir_hex
|
248
|
+
secret = "hello"
|
249
|
+
helper{|available,needed|
|
250
|
+
shares = ShamirSecretSharing::Hex.split(secret, available, needed)
|
251
|
+
assert_equal secret, ShamirSecretSharing::Hex.combine(shares.shuffle[0...needed])
|
252
|
+
}
|
253
|
+
end
|
254
|
+
|
255
|
+
def test_shamir_number
|
256
|
+
secret = 123
|
257
|
+
shares = ShamirSecretSharing::Number.split(secret, 6, 3)
|
258
|
+
assert_equal secret, ShamirSecretSharing::Number.combine(shares.shuffle[0...3])
|
259
|
+
end
|
260
|
+
|
261
|
+
def test_shamir_base58_encrypt
|
262
|
+
text = "A"*32
|
263
|
+
helper{|available,needed|
|
264
|
+
shares, encrypted = ShamirSecretSharing::Base58.encrypt(text, available, needed, 96)
|
265
|
+
assert_equal text, ShamirSecretSharing::Base58.decrypt(shares.shuffle[0...needed], encrypted)
|
266
|
+
}
|
267
|
+
|
268
|
+
shares, encrypted = ShamirSecretSharing::Base58.encrypt_with_sanity_check(text, available=3, needed=2)
|
269
|
+
assert_equal text, ShamirSecretSharing::Base58.decrypt(shares.shuffle[0...needed], encrypted)
|
270
|
+
end
|
271
|
+
|
272
|
+
def test_shamir_base58_encrypt_sanity_checks
|
273
|
+
klass = ShamirSecretSharing::Base58
|
274
|
+
checks, success = 5, true
|
275
|
+
[
|
276
|
+
[2,3], [2,4], [3,5]
|
277
|
+
].each{|needed,available|
|
278
|
+
checks.times{
|
279
|
+
data = "A"*32
|
280
|
+
shares, encrypted = klass.encrypt(data, available, needed, 96)
|
281
|
+
needed.upto(available).each{|n|
|
282
|
+
shares.permutation(n).each{|shares| success = false if klass.decrypt(shares, encrypted, true) != data }
|
283
|
+
}
|
284
|
+
(needed-1).downto(2).each{|n|
|
285
|
+
shares.permutation(n).each{|shares| success = false if klass.decrypt(shares, encrypted, true) != false }
|
286
|
+
}
|
287
|
+
break unless success
|
288
|
+
}
|
289
|
+
}
|
290
|
+
assert_equal true, success
|
291
|
+
end
|
292
|
+
|
293
|
+
def test_shamir_base64_encrypt
|
294
|
+
text = "A"*32
|
295
|
+
helper{|available,needed|
|
296
|
+
shares, encrypted = ShamirSecretSharing::Base64.encrypt(text, available, needed, 96)
|
297
|
+
assert_equal text, ShamirSecretSharing::Base64.decrypt(shares.shuffle[0...needed], encrypted)
|
298
|
+
}
|
299
|
+
end
|
300
|
+
|
301
|
+
def test_shamir_hex_encrypt
|
302
|
+
text = "A"*32
|
303
|
+
helper{|available,needed|
|
304
|
+
shares, encrypted = ShamirSecretSharing::Hex.encrypt(text, available, needed, 96)
|
305
|
+
assert_equal text, ShamirSecretSharing::Hex.decrypt(shares.shuffle[0...needed], encrypted)
|
306
|
+
}
|
307
|
+
end
|
308
|
+
|
309
|
+
def test_shamir_with_broken_share_checksum
|
310
|
+
secret = "hello"
|
311
|
+
share_with_broken_checksum = ShamirSecretSharing::Base58.encode("foobar")
|
312
|
+
share_with_broken_encoding = "1Il"
|
313
|
+
shares = ShamirSecretSharing::Base58.split(secret, 3, 2)
|
314
|
+
assert_equal false, ShamirSecretSharing::Base58.combine( [shares.shuffle.first, share_with_broken_checksum])
|
315
|
+
assert_equal false, ShamirSecretSharing::Base58.combine( [shares.shuffle.first, share_with_broken_encoding])
|
316
|
+
|
317
|
+
do_raise = true
|
318
|
+
err = assert_raises(ShamirSecretSharing::ShareChecksumError){ ShamirSecretSharing::Base58.combine( [shares.shuffle.first, share_with_broken_checksum], do_raise) }
|
319
|
+
assert_match /share: /, err.message
|
320
|
+
assert_raises(ShamirSecretSharing::ShareDecodeError){ ShamirSecretSharing::Base58.combine( [shares.shuffle.first, share_with_broken_encoding], do_raise) }
|
321
|
+
assert_match /share: /, err.message
|
322
|
+
end
|
323
|
+
|
324
|
+
def test_shamir_encrypt_with_broken_encypted_data
|
325
|
+
text = "A"*32
|
326
|
+
broken_encrypted_data = ShamirSecretSharing::Base58.encode("foobar")
|
327
|
+
broken_encrypted_data_encoding = "1Il"
|
328
|
+
share_with_broken_encoding = "1Il"
|
329
|
+
shares, encrypted = ShamirSecretSharing::Base58.encrypt(text, 3, 2, 96)
|
330
|
+
assert_equal false, ShamirSecretSharing::Base58.decrypt(shares.shuffle[0...2], broken_encrypted_data)
|
331
|
+
assert_equal false, ShamirSecretSharing::Base58.decrypt(shares.shuffle[0...2], broken_encrypted_data_encoding)
|
332
|
+
|
333
|
+
do_raise = true
|
334
|
+
assert_raises(OpenSSL::Cipher::CipherError) { ShamirSecretSharing::Base58.decrypt(shares.shuffle[0...2], broken_encrypted_data, do_raise) }
|
335
|
+
err = assert_raises(ShamirSecretSharing::ShareDecodeError){ ShamirSecretSharing::Base58.decrypt( [shares.shuffle.first, share_with_broken_encoding], encrypted, do_raise) }
|
336
|
+
assert_match /share: /, err.message
|
337
|
+
err = assert_raises(ShamirSecretSharing::ShareDecodeError){ ShamirSecretSharing::Base58.decrypt(shares.shuffle[0...2], broken_encrypted_data_encoding, do_raise) }
|
338
|
+
assert_match /encrypted_data: /, err.message
|
339
|
+
end
|
340
|
+
|
341
|
+
def test_shamir_split_argument_errors
|
342
|
+
assert_raises_and_message(ArgumentError, "needed must be <= available") { ShamirSecretSharing::Base58.split("foobar", 2, 3) }
|
343
|
+
assert_raises_and_message(ArgumentError, "needed must be >= 2") { ShamirSecretSharing::Base58.split("foobar", 3, 1) }
|
344
|
+
assert_raises_and_message(ArgumentError, "available must be <= 250") { ShamirSecretSharing::Base58.split("foobar", 251, 2) }
|
345
|
+
assert_raises_and_message(ArgumentError, "bytelength of secret must be >= 1") { ShamirSecretSharing::Base58.split("", 3, 2) }
|
346
|
+
assert_raises_and_message(ArgumentError, "bytelength of secret must be <= 512") { ShamirSecretSharing::Base58.split("A"*513, 3, 2) }
|
347
|
+
end
|
348
|
+
|
349
|
+
end
|
350
|
+
|
351
|
+
=begin
|
352
|
+
require 'pp'
|
353
|
+
|
354
|
+
pp shares = ShamirSecretSharing::Base58.split("hello", 6, 3)
|
355
|
+
pp ShamirSecretSharing::Base58.combine(shares[0...3])
|
356
|
+
|
357
|
+
pp shares = ShamirSecretSharing::Base64.split("hello", 6, 3)
|
358
|
+
pp ShamirSecretSharing::Base64.combine(shares[0...3])
|
359
|
+
|
360
|
+
pp shares = ShamirSecretSharing::Hex.split("hello", 6, 3)
|
361
|
+
pp ShamirSecretSharing::Hex.combine(shares[0...3])
|
362
|
+
|
363
|
+
pp shares = ShamirSecretSharing::Number.split(123, 6, 3)
|
364
|
+
pp ShamirSecretSharing::Number.combine(shares[0...3])
|
365
|
+
|
366
|
+
|
367
|
+
shares, encrypted = ShamirSecretSharing::Base58.encrypt("A"*32, 6, 3, 96)
|
368
|
+
pp [shares, encrypted]
|
369
|
+
p ShamirSecretSharing::Base58.decrypt(shares.shuffle[0...3], encrypted)
|
370
|
+
|
371
|
+
shares, encrypted = ShamirSecretSharing::Base64.encrypt("A"*32, 6, 3, 96)
|
372
|
+
pp [shares, encrypted]
|
373
|
+
p ShamirSecretSharing::Base64.decrypt(shares.shuffle[0...3], encrypted)
|
374
|
+
|
375
|
+
shares, encrypted = ShamirSecretSharing::Hex.encrypt("A"*32, 6, 3, 96)
|
376
|
+
pp [shares, encrypted]
|
377
|
+
p ShamirSecretSharing::Hex.decrypt(shares.shuffle[0...3], encrypted)
|
378
|
+
=end
|
379
|
+
|
380
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "shamir-secret-sharing"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "shamir-secret-sharing"
|
7
|
+
s.version = ShamirSecretSharing::VERSION
|
8
|
+
s.authors = ["lian"]
|
9
|
+
s.email = ["meta.rb@gmail.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Gem for Shamir's Secret Sharing}
|
12
|
+
s.description = %q{Gem for Shamir's Secret Sharing}
|
13
|
+
|
14
|
+
s.rubyforge_project = "shamir-secret-sharing"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.required_rubygems_version = ">= 1.3.6"
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: shamir-secret-sharing
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- lian
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-01-21 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Gem for Shamir's Secret Sharing
|
14
|
+
email:
|
15
|
+
- meta.rb@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- README.rdoc
|
21
|
+
- Rakefile
|
22
|
+
- lib/shamir-secret-sharing.rb
|
23
|
+
- shamir-secret-sharing.gemspec
|
24
|
+
homepage: ''
|
25
|
+
licenses: []
|
26
|
+
metadata: {}
|
27
|
+
post_install_message:
|
28
|
+
rdoc_options: []
|
29
|
+
require_paths:
|
30
|
+
- lib
|
31
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
32
|
+
requirements:
|
33
|
+
- - ">="
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: '0'
|
36
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.3.6
|
41
|
+
requirements: []
|
42
|
+
rubyforge_project: shamir-secret-sharing
|
43
|
+
rubygems_version: 2.2.0
|
44
|
+
signing_key:
|
45
|
+
specification_version: 4
|
46
|
+
summary: Gem for Shamir's Secret Sharing
|
47
|
+
test_files: []
|