shamir-secret-sharing 0.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 +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: []
|