extended-shamir-secret-sharing 0.1.0
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 +382 -0
- data/shamir-secret-sharing.gemspec +22 -0
- metadata +48 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f57b4d7f5a831922c68dc6634e270759cae032494f6666a8fb2c8bfa81c62270
|
4
|
+
data.tar.gz: bfcb8e736a50524d75be8ac0eac525a197749fc39b09a610698912495f0dd167
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 679d1e270c491758465d0c07201896ab0715bd8e55e420b4cbb7ddaf9f1e68044b71d15a1ea2985af236f58fe03ed1e290b75f72a9b58c8e99dccc17ca410796
|
7
|
+
data.tar.gz: 2ef5841a83711e0ab3df32e853733f7ff2da59dc708e7a72632b5697568884d0814b36e0e363fa8fe29e4a12f14646cfdfcf9989f1bc1ce84345af887d27cbda
|
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,382 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'digest/sha1'
|
3
|
+
|
4
|
+
class ShamirSecretSharing
|
5
|
+
VERSION = '0.1.0'
|
6
|
+
|
7
|
+
def self.prime
|
8
|
+
@prime ||= OpenSSL::BN.new('90298080309315418557060368684387169711767062077820047328483946136026783868610231658004529474715593165227045823796809053316421038435080529340276649188231489578413082915429082171276815832832032596987468328852992416302194653059769701301855112417280173119831084529497140275721119033981819754004876481947376857582541086674191309050669339147047177722482742095623467804455161314645495512219242738453400110821615485595951493732348640398317903244696668482247005070591932639428562532268906727083775005769341161737441206890423884947292116895278573352752642105943524899087988610386051326114173719939938357597470028921187928607042904261588275872786996786421015124382796026032641093550978376530319328301152270163150961443743005805156487456518852641096288933759632928527097707771705108593435811210969899009718025806082982491815839959394862199132189412760797205753603085181613915340914509742283721398758884882857717577570891251312844452052745163947127230739899990249592181831046734568185882323553723058313378711522655212295569618792861569553260909021907243424735691066494898818760523978370666490738958317270115402545013981689071761804217481237129399611746232468395365054918893973245564093211165225302041175079585458888691055939901734787886063765286977492815858172765414297704166310822063655804295492269403809136990123946872416301581476512804600437921871062337466733019509934199383005641657881387476608548204828441502666519703299105349809939561047193583345522023506184856674255042110783737263099438319847599648008766818797429771443705028772651586692324064007017187902934084171715785037116043811965267781489643030445117031408644673321057391176558036893857789298122893483373295134914414310225512185182855428723091437135258390607647079002628450028353308872465259623761042459298605601867079931599883341211337021548912929591032567679109586055855175707483614097894547800956575849455311488677165455115982505416964026617179448438285990610826073646616767534962613889972341177404511628350260670301108812261943592601817837534438293861144285248114026494593413438072008145151195558510253887426294107818889183153563906542962676822500733556368475553257951001559608026694402026083311647661603527141723939606662260781541702812518282966559303942883569550595968567624849771779902009005634939910112687785085261271077120292302150188079499520405255241734961072150325600567125813015450184377437377118236641143759546112499433705368329863240052878614034365965400707301060867667467921246288162184422787472727180885468250036866234631784065140572659371113921558324588343588649262665138554305162189719942919492935163152228273161856121456840243572074505539715086267815541055014575779183357826164049356330040472884738976674077057074492894659512026336932025359399208067756553441936121229276677493655969096449480584011430005309053544504304875804843205273883837061928391103734105527700616760751820474231999825032847945883064932406572688251330712502565439000257427471703412233595773324058194221908861303293341560475980198905863935850405064924144381710080649869192412763511460084172591012489581496077540366143109773048609220052015662328678967053922114059228108612468779055218472293622854582726541691332097684877156682496500546767264347154842487600035258894233822255723444907740808744398357482803391100715574412706233117206636112256824018335300077628867404705796199494183312134381338806813000075497747803240234241994282044442903373163715122536646305967943100639503123443493819526136342147706606394282112583884721431928417322993021536731405231184306729967529111955045252260102353296822531279058046816735479320770852433850566679904058973845613063366857995248536709558547947005487959070944758243881165013091044353829893545161327798124166213688912777335160265951849666987641506720154010364630736090984018386925406284136947816502665237274359499520079866122535658013617866311362943581086846194924950315777735026525882487543617430916255273833298323517701745687510934619838104831680724070326313574890013327805200827123185767256005905063632000378392141574495904804421309409555139440835558113898451033592526676935989162452243800981511319774846636555021940628714873179898475129018414822388672074777887836816908866710716135238849926550730803704711315750950458422935489673267976222635275704248820229824996510107205198663605380240651729687971710957296407610255077817146837349063148581060337597269343984345091912700819071375585519821604434603431994464701457318775403702846559037160195794889488908029233286151225725905398606350109962941753161584200144074269884326067022889478112825554120260952696169252329753222034898637234313852548016829410522468275347612080526487056946419000642199642660133237695390182834252238828073436204843551107043444186518737285718064454348783725196174064580015729871611222847895929389800649019254831014513113431655578925679204506338865404876491637286206819918930558503201366328626510435408373432348415238531403287283959561857892591427352842104554959608115380742631785942302014581426310009539709468373835178808015087703244680357716266673938316659211853843914796941131721320498518038272867994107029017543970812956754144964043650615822951424951757570100497007826364422530866395354057037623197190431202946206475807094227419540419168025621836505136395345095391267796526635539815975433875182503787461610057361924233524420270967631328159401123004381979801026089422686648542947429448884700847284807913378433322255357823885663399560901851919616533557061506731352057344807459911094519333689705182500885752770652880878560576235311015954069515891882699932824200620047914931186958185540784216011367774214867867886436031378064197674396368328035634225912051811470896936074219358317351694009621171355985142146081500737')
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.pack(shares); shares; end
|
12
|
+
def self.unpack(shares); shares; end
|
13
|
+
def self.encode(string); string; end
|
14
|
+
def self.decode(string); string; end
|
15
|
+
|
16
|
+
def self.smallest_prime_of_bytelength(bytelength)
|
17
|
+
n = OpenSSL::BN.new((2**(bytelength*8)+1).to_s)
|
18
|
+
loop{ break if n.prime_fasttest?(20); n += 2 }
|
19
|
+
n
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.split(secret, available, needed, do_data_checksum=true)
|
23
|
+
raise ArgumentError, "needed must be <= available" unless needed <= available
|
24
|
+
raise ArgumentError, "needed must be >= 2" unless needed >= 2
|
25
|
+
raise ArgumentError, "available must be <= 250" unless available <= 250
|
26
|
+
|
27
|
+
if do_data_checksum
|
28
|
+
checksum = Digest::SHA512.digest(secret)[0...2]
|
29
|
+
num_bytes = secret.bytesize+2
|
30
|
+
secret = OpenSSL::BN.new((checksum + secret).unpack("H*")[0], 16) rescue OpenSSL::BN.new("0")
|
31
|
+
#num_bytes = secret.to_s(0).unpack("N")[0]
|
32
|
+
raise ArgumentError, "bytelength of secret must be >= 1" if num_bytes < 3
|
33
|
+
raise ArgumentError, "bytelength of secret must be <= 4096" if num_bytes > 4097
|
34
|
+
else
|
35
|
+
num_bytes = secret.bytesize
|
36
|
+
secret = OpenSSL::BN.new(secret.unpack("H*")[0], 16) rescue OpenSSL::BN.new("0") # without checksum
|
37
|
+
raise ArgumentError, "bytelength of secret must be >= 1" if num_bytes < 1
|
38
|
+
raise ArgumentError, "bytelength of secret must be <= 4096" if num_bytes > 4096
|
39
|
+
end
|
40
|
+
|
41
|
+
coef = [ secret ] + Array.new(needed-1){ OpenSSL::BN.rand(num_bytes * 8) }
|
42
|
+
|
43
|
+
shares = (1..available).map{|x|
|
44
|
+
x = OpenSSL::BN.new(x.to_s)
|
45
|
+
y = coef.each_with_index.inject(OpenSSL::BN.new("0")){|acc, (c, idx)|
|
46
|
+
acc + c * x.mod_exp(idx, prime)
|
47
|
+
} % prime
|
48
|
+
[x, num_bytes, y]
|
49
|
+
}
|
50
|
+
pack(shares)
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.combine(shares, do_raise=false, do_data_checksum=true)
|
54
|
+
return false if shares.size < 2
|
55
|
+
shares = unpack(shares)
|
56
|
+
num_bytes = shares[0][1]
|
57
|
+
|
58
|
+
secret = shares.inject(OpenSSL::BN.new("0")){|secret,(x,num_bytes,y)|
|
59
|
+
l_x = l(x, shares, prime)
|
60
|
+
summand = OpenSSL::BN.new(y.to_s).mod_mul(l_x, prime)
|
61
|
+
secret = (secret + summand) % prime
|
62
|
+
}
|
63
|
+
if do_data_checksum
|
64
|
+
checksum, secret = [ secret.to_s(16).rjust(num_bytes*2, '0') ].pack("H*").unpack("a2a*")
|
65
|
+
checksum == Digest::SHA512.digest(secret)[0...2] ? secret : false
|
66
|
+
else
|
67
|
+
secret = [ secret.to_s(16).rjust(num_bytes*2, '0') ].pack("H*")
|
68
|
+
end
|
69
|
+
rescue ShareChecksumError, ShareDecodeError => ex
|
70
|
+
raise if do_raise
|
71
|
+
false
|
72
|
+
end
|
73
|
+
|
74
|
+
# Part of the Lagrange interpolation.
|
75
|
+
# This is l_j(0), i.e. # \prod_{x_j \neq x_i} \frac{-x_i}{x_j - x_i}
|
76
|
+
# for more information compare Wikipedia: # http://en.wikipedia.org/wiki/Lagrange_form
|
77
|
+
def self.l(current_x, shares, prime)
|
78
|
+
shares.select{|x,num_bytes,y| x != current_x }.map{|x,num_bytes,y|
|
79
|
+
minus_xi = OpenSSL::BN.new((-x).to_s)
|
80
|
+
one_over_xj_minus_xi = OpenSSL::BN.new((current_x - x).to_s).mod_inverse(prime)
|
81
|
+
minus_xi.mod_mul(one_over_xj_minus_xi, prime)
|
82
|
+
}.inject{|p,f| p.mod_mul(f, prime) }
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.encrypt(data, available, needed, key_bit_length=128)
|
86
|
+
key = key_bit_length.is_a?(String) ? key_bit_length : [ OpenSSL::BN.rand(key_bit_length).to_s(16) ].pack("H*")
|
87
|
+
c = OpenSSL::Cipher.new('aes-256-cbc').encrypt
|
88
|
+
c.key, c.iv = Digest::SHA512.digest(key).unpack("a32a16")
|
89
|
+
encrypted = c.update(data) << c.final
|
90
|
+
[ split(key, available, needed), encode(encrypted) ]
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.decrypt(shares, encrypted, do_raise=false)
|
94
|
+
key = combine(shares, do_raise)
|
95
|
+
return false unless key
|
96
|
+
|
97
|
+
encrypted_decoded = decode(encrypted) rescue nil
|
98
|
+
raise ShareDecodeError, "encrypted_data: #{encrypted}" unless encrypted_decoded
|
99
|
+
|
100
|
+
return false unless encrypted and key
|
101
|
+
c = OpenSSL::Cipher.new('aes-256-cbc').decrypt
|
102
|
+
c.key, c.iv = Digest::SHA512.digest(key).unpack("a32a16")
|
103
|
+
data = c.update(encrypted_decoded) << c.final
|
104
|
+
data
|
105
|
+
rescue OpenSSL::Cipher::CipherError, ShareDecodeError
|
106
|
+
raise if do_raise
|
107
|
+
false
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.split_with_sanity_check(secret, available, needed, do_data_checksum=true)
|
111
|
+
shares = split(secret, available, needed, do_data_checksum)
|
112
|
+
success = true
|
113
|
+
needed.upto(available).each{|n| shares.permutation(n).each{|shares| success = false if combine(shares) != secret } }
|
114
|
+
(needed-1).downto(2).each{|n| shares.permutation(n).each{|shares| success = false if combine(shares) != false } }
|
115
|
+
raise ShareSanityCheckError if success != true
|
116
|
+
shares
|
117
|
+
rescue ShareSanityCheckError
|
118
|
+
retry
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.encrypt_with_sanity_check(data, available, needed, key_bit_length=128)
|
122
|
+
shares, encrypted = encrypt(data, available, needed, key_bit_length)
|
123
|
+
success = true
|
124
|
+
needed.upto(available).each{|n| shares.permutation(n).each{|shares| success = false if decrypt(shares, encrypted) != data } }
|
125
|
+
(needed-1).downto(2).each{|n| shares.permutation(n).each{|shares| success = false if decrypt(shares, encrypted) != false } }
|
126
|
+
raise ShareSanityCheckError if success != true
|
127
|
+
[shares, encrypted]
|
128
|
+
rescue ShareSanityCheckError
|
129
|
+
retry
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
class Number < ShamirSecretSharing
|
134
|
+
def self.split(secret, available, needed)
|
135
|
+
num = OpenSSL::BN.new(secret.to_s)
|
136
|
+
raise ArgumentError, "available must be <= 9" unless available <= 9
|
137
|
+
raise ArgumentError, "num too large. bytelength must be <= 9" unless num.num_bytes <= 9
|
138
|
+
shares = ShamirSecretSharing.split([num.to_s(16)].pack("H*"), available, needed, do_data_checksum=nil)
|
139
|
+
shares.map{|i| i.join.to_i }
|
140
|
+
end
|
141
|
+
|
142
|
+
def self.combine(shares)
|
143
|
+
shares = shares.map{|i| i.to_s.match(/(\d)(\d)(\d+)/); [$1.to_i, $2.to_i, $3.to_i] }
|
144
|
+
ShamirSecretSharing.combine(shares, do_raise=false, do_data_checksum=nil).unpack("H*")[0].to_i(16)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
class ShareChecksumError < ::StandardError; end
|
149
|
+
class ShareDecodeError < ::StandardError; end
|
150
|
+
class ShareSanityCheckError < ::StandardError; end
|
151
|
+
|
152
|
+
class Packed < ShamirSecretSharing # packing format and checkum
|
153
|
+
def self.pack(shares)
|
154
|
+
shares.map{|x,num_bytes,y|
|
155
|
+
buf = [ x, num_bytes, y.to_s(16) ].pack("CnH*")
|
156
|
+
checksum = Digest::SHA512.digest(buf)[0...2]
|
157
|
+
encode(checksum << buf)
|
158
|
+
}
|
159
|
+
end
|
160
|
+
def self.unpack(shares)
|
161
|
+
shares.map{|i|
|
162
|
+
buf = decode(i) rescue nil
|
163
|
+
raise ShareDecodeError, "share: #{i}" unless buf
|
164
|
+
checksum, buf = buf.unpack("a2a*")
|
165
|
+
raise ShareChecksumError, "share: #{i}" unless checksum == Digest::SHA512.digest(buf)[0...2]
|
166
|
+
i = buf.unpack("CnH*"); [ i[0], i[1], i[2].to_i(16) ]
|
167
|
+
}
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
class Base58 < Packed
|
172
|
+
def self.encode(string)
|
173
|
+
string = string.unpack("H*")[0]
|
174
|
+
leading_zero_bytes = (string.match(/^([0]+)/) ? $1 : '').size / 2
|
175
|
+
("1"*leading_zero_bytes) + int_to_base58( string.to_i(16) )
|
176
|
+
end
|
177
|
+
def self.decode(string)
|
178
|
+
leading_zero_bytes = (string.match(/^([1]+)/) ? $1 : '').size
|
179
|
+
buf = base58_to_int(string).to_s(16); buf = (buf.bytesize.odd? ? '0'+buf : buf)
|
180
|
+
[ ("00"*leading_zero_bytes) + buf ].pack("H*")
|
181
|
+
end
|
182
|
+
def self.int_to_base58(int_val, leading_zero_bytes=0)
|
183
|
+
alpha, base58_val, base = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz", "", 58
|
184
|
+
while int_val > 0
|
185
|
+
int_val, remainder = int_val.divmod(base)
|
186
|
+
base58_val = alpha[remainder] + base58_val
|
187
|
+
end; base58_val
|
188
|
+
end
|
189
|
+
|
190
|
+
def self.base58_to_int(base58_val)
|
191
|
+
alpha, base = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz", 58
|
192
|
+
base58_val.reverse.each_char.with_index.inject(0) do |int_val, (char,index)|
|
193
|
+
raise ArgumentError, 'Value not a valid Base58 String.' unless char_index = alpha.index(char)
|
194
|
+
int_val + char_index*(base**index)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
class Base64 < Packed
|
200
|
+
def self.encode(string); [string].pack("m0"); end
|
201
|
+
def self.decode(string); string.unpack("m0")[0]; end
|
202
|
+
end
|
203
|
+
|
204
|
+
class Hex < Packed
|
205
|
+
def self.encode(string); string.unpack("H*")[0]; end
|
206
|
+
def self.decode(string); [string].pack("H*"); end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
|
211
|
+
|
212
|
+
|
213
|
+
if $0 == __FILE__
|
214
|
+
require "minitest/autorun"
|
215
|
+
|
216
|
+
class MiniTest::Unit::TestCase
|
217
|
+
def assert_raises_and_message(klass, msg, &blk)
|
218
|
+
err = assert_raises(klass, &blk); assert_equal msg, err.message
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
|
223
|
+
class TestShamirSecretSharing < MiniTest::Unit::TestCase
|
224
|
+
|
225
|
+
def helper(&b)
|
226
|
+
[ [6,3], [10, 2], [3,2], [100, 30] ].each{|available,needed| b.call(available, needed) }
|
227
|
+
end
|
228
|
+
|
229
|
+
|
230
|
+
def test_shamir_base58
|
231
|
+
secret = "hello"
|
232
|
+
helper{|available,needed|
|
233
|
+
shares = ShamirSecretSharing::Base58.split(secret, available, needed)
|
234
|
+
assert_equal secret, ShamirSecretSharing::Base58.combine(shares.shuffle[0...needed])
|
235
|
+
}
|
236
|
+
|
237
|
+
shares = ShamirSecretSharing::Base58.split_with_sanity_check(secret, available=3, needed=2)
|
238
|
+
assert_equal secret, ShamirSecretSharing::Base58.combine(shares.shuffle[0...needed])
|
239
|
+
end
|
240
|
+
|
241
|
+
def test_shamir_base64
|
242
|
+
secret = "hello"
|
243
|
+
helper{|available,needed|
|
244
|
+
shares = ShamirSecretSharing::Base64.split(secret, available, needed)
|
245
|
+
assert_equal secret, ShamirSecretSharing::Base64.combine(shares.shuffle[0...needed])
|
246
|
+
}
|
247
|
+
end
|
248
|
+
|
249
|
+
def test_shamir_hex
|
250
|
+
secret = "hello"
|
251
|
+
helper{|available,needed|
|
252
|
+
shares = ShamirSecretSharing::Hex.split(secret, available, needed)
|
253
|
+
assert_equal secret, ShamirSecretSharing::Hex.combine(shares.shuffle[0...needed])
|
254
|
+
}
|
255
|
+
end
|
256
|
+
|
257
|
+
def test_shamir_number
|
258
|
+
secret = 123
|
259
|
+
shares = ShamirSecretSharing::Number.split(secret, 6, 3)
|
260
|
+
assert_equal secret, ShamirSecretSharing::Number.combine(shares.shuffle[0...3])
|
261
|
+
end
|
262
|
+
|
263
|
+
def test_shamir_base58_encrypt
|
264
|
+
text = "A"*32
|
265
|
+
helper{|available,needed|
|
266
|
+
shares, encrypted = ShamirSecretSharing::Base58.encrypt(text, available, needed, 96)
|
267
|
+
assert_equal text, ShamirSecretSharing::Base58.decrypt(shares.shuffle[0...needed], encrypted)
|
268
|
+
}
|
269
|
+
|
270
|
+
shares, encrypted = ShamirSecretSharing::Base58.encrypt_with_sanity_check(text, available=3, needed=2)
|
271
|
+
assert_equal text, ShamirSecretSharing::Base58.decrypt(shares.shuffle[0...needed], encrypted)
|
272
|
+
end
|
273
|
+
|
274
|
+
def test_shamir_base58_encrypt_sanity_checks
|
275
|
+
klass = ShamirSecretSharing::Base58
|
276
|
+
checks, success = 5, true
|
277
|
+
[
|
278
|
+
[2,3], [2,4], [3,5]
|
279
|
+
].each{|needed,available|
|
280
|
+
checks.times{
|
281
|
+
data = "A"*32
|
282
|
+
shares, encrypted = klass.encrypt(data, available, needed, 96)
|
283
|
+
needed.upto(available).each{|n|
|
284
|
+
shares.permutation(n).each{|shares| success = false if klass.decrypt(shares, encrypted, true) != data }
|
285
|
+
}
|
286
|
+
(needed-1).downto(2).each{|n|
|
287
|
+
shares.permutation(n).each{|shares| success = false if klass.decrypt(shares, encrypted, true) != false }
|
288
|
+
}
|
289
|
+
break unless success
|
290
|
+
}
|
291
|
+
}
|
292
|
+
assert_equal true, success
|
293
|
+
end
|
294
|
+
|
295
|
+
def test_shamir_base64_encrypt
|
296
|
+
text = "A"*32
|
297
|
+
helper{|available,needed|
|
298
|
+
shares, encrypted = ShamirSecretSharing::Base64.encrypt(text, available, needed, 96)
|
299
|
+
assert_equal text, ShamirSecretSharing::Base64.decrypt(shares.shuffle[0...needed], encrypted)
|
300
|
+
}
|
301
|
+
end
|
302
|
+
|
303
|
+
def test_shamir_hex_encrypt
|
304
|
+
text = "A"*32
|
305
|
+
helper{|available,needed|
|
306
|
+
shares, encrypted = ShamirSecretSharing::Hex.encrypt(text, available, needed, 96)
|
307
|
+
assert_equal text, ShamirSecretSharing::Hex.decrypt(shares.shuffle[0...needed], encrypted)
|
308
|
+
}
|
309
|
+
end
|
310
|
+
|
311
|
+
def test_shamir_with_broken_share_checksum
|
312
|
+
secret = "hello"
|
313
|
+
share_with_broken_checksum = ShamirSecretSharing::Base58.encode("foobar")
|
314
|
+
share_with_broken_encoding = "1Il"
|
315
|
+
shares = ShamirSecretSharing::Base58.split(secret, 3, 2)
|
316
|
+
assert_equal false, ShamirSecretSharing::Base58.combine( [shares.shuffle.first, share_with_broken_checksum])
|
317
|
+
assert_equal false, ShamirSecretSharing::Base58.combine( [shares.shuffle.first, share_with_broken_encoding])
|
318
|
+
|
319
|
+
do_raise = true
|
320
|
+
err = assert_raises(ShamirSecretSharing::ShareChecksumError){ ShamirSecretSharing::Base58.combine( [shares.shuffle.first, share_with_broken_checksum], do_raise) }
|
321
|
+
assert_match /share: /, err.message
|
322
|
+
assert_raises(ShamirSecretSharing::ShareDecodeError){ ShamirSecretSharing::Base58.combine( [shares.shuffle.first, share_with_broken_encoding], do_raise) }
|
323
|
+
assert_match /share: /, err.message
|
324
|
+
end
|
325
|
+
|
326
|
+
def test_shamir_encrypt_with_broken_encypted_data
|
327
|
+
text = "A"*32
|
328
|
+
broken_encrypted_data = ShamirSecretSharing::Base58.encode("foobar")
|
329
|
+
broken_encrypted_data_encoding = "1Il"
|
330
|
+
share_with_broken_encoding = "1Il"
|
331
|
+
shares, encrypted = ShamirSecretSharing::Base58.encrypt(text, 3, 2, 96)
|
332
|
+
assert_equal false, ShamirSecretSharing::Base58.decrypt(shares.shuffle[0...2], broken_encrypted_data)
|
333
|
+
assert_equal false, ShamirSecretSharing::Base58.decrypt(shares.shuffle[0...2], broken_encrypted_data_encoding)
|
334
|
+
|
335
|
+
do_raise = true
|
336
|
+
assert_raises(OpenSSL::Cipher::CipherError) { ShamirSecretSharing::Base58.decrypt(shares.shuffle[0...2], broken_encrypted_data, do_raise) }
|
337
|
+
err = assert_raises(ShamirSecretSharing::ShareDecodeError){ ShamirSecretSharing::Base58.decrypt( [shares.shuffle.first, share_with_broken_encoding], encrypted, do_raise) }
|
338
|
+
assert_match /share: /, err.message
|
339
|
+
err = assert_raises(ShamirSecretSharing::ShareDecodeError){ ShamirSecretSharing::Base58.decrypt(shares.shuffle[0...2], broken_encrypted_data_encoding, do_raise) }
|
340
|
+
assert_match /encrypted_data: /, err.message
|
341
|
+
end
|
342
|
+
|
343
|
+
def test_shamir_split_argument_errors
|
344
|
+
assert_raises_and_message(ArgumentError, "needed must be <= available") { ShamirSecretSharing::Base58.split("foobar", 2, 3) }
|
345
|
+
assert_raises_and_message(ArgumentError, "needed must be >= 2") { ShamirSecretSharing::Base58.split("foobar", 3, 1) }
|
346
|
+
assert_raises_and_message(ArgumentError, "available must be <= 250") { ShamirSecretSharing::Base58.split("foobar", 251, 2) }
|
347
|
+
assert_raises_and_message(ArgumentError, "bytelength of secret must be >= 1") { ShamirSecretSharing::Base58.split("", 3, 2) }
|
348
|
+
assert_raises_and_message(ArgumentError, "bytelength of secret must be <= 4096") { ShamirSecretSharing::Base58.split("A"*4097, 3, 2) }
|
349
|
+
end
|
350
|
+
|
351
|
+
end
|
352
|
+
|
353
|
+
=begin
|
354
|
+
require 'pp'
|
355
|
+
|
356
|
+
pp shares = ShamirSecretSharing::Base58.split("hello", 6, 3)
|
357
|
+
pp ShamirSecretSharing::Base58.combine(shares[0...3])
|
358
|
+
|
359
|
+
pp shares = ShamirSecretSharing::Base64.split("hello", 6, 3)
|
360
|
+
pp ShamirSecretSharing::Base64.combine(shares[0...3])
|
361
|
+
|
362
|
+
pp shares = ShamirSecretSharing::Hex.split("hello", 6, 3)
|
363
|
+
pp ShamirSecretSharing::Hex.combine(shares[0...3])
|
364
|
+
|
365
|
+
pp shares = ShamirSecretSharing::Number.split(123, 6, 3)
|
366
|
+
pp ShamirSecretSharing::Number.combine(shares[0...3])
|
367
|
+
|
368
|
+
|
369
|
+
shares, encrypted = ShamirSecretSharing::Base58.encrypt("A"*32, 6, 3, 96)
|
370
|
+
pp [shares, encrypted]
|
371
|
+
p ShamirSecretSharing::Base58.decrypt(shares.shuffle[0...3], encrypted)
|
372
|
+
|
373
|
+
shares, encrypted = ShamirSecretSharing::Base64.encrypt("A"*32, 6, 3, 96)
|
374
|
+
pp [shares, encrypted]
|
375
|
+
p ShamirSecretSharing::Base64.decrypt(shares.shuffle[0...3], encrypted)
|
376
|
+
|
377
|
+
shares, encrypted = ShamirSecretSharing::Hex.encrypt("A"*32, 6, 3, 96)
|
378
|
+
pp [shares, encrypted]
|
379
|
+
p ShamirSecretSharing::Hex.decrypt(shares.shuffle[0...3], encrypted)
|
380
|
+
=end
|
381
|
+
|
382
|
+
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 = "extended-shamir-secret-sharing"
|
7
|
+
s.version = ShamirSecretSharing::VERSION
|
8
|
+
s.authors = ["lian", "Armand Mégrot"]
|
9
|
+
s.email = ["meta.rb@gmail.com", "armand.megrot@cashbee.fr"]
|
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,48 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: extended-shamir-secret-sharing
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- lian
|
8
|
+
- Armand Mégrot
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2019-05-15 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Gem for Shamir's Secret Sharing
|
15
|
+
email:
|
16
|
+
- meta.rb@gmail.com
|
17
|
+
- armand.megrot@cashbee.fr
|
18
|
+
executables: []
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- README.rdoc
|
23
|
+
- Rakefile
|
24
|
+
- lib/shamir-secret-sharing.rb
|
25
|
+
- shamir-secret-sharing.gemspec
|
26
|
+
homepage: ''
|
27
|
+
licenses: []
|
28
|
+
metadata: {}
|
29
|
+
post_install_message:
|
30
|
+
rdoc_options: []
|
31
|
+
require_paths:
|
32
|
+
- lib
|
33
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
34
|
+
requirements:
|
35
|
+
- - ">="
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 1.3.6
|
43
|
+
requirements: []
|
44
|
+
rubygems_version: 3.0.1
|
45
|
+
signing_key:
|
46
|
+
specification_version: 4
|
47
|
+
summary: Gem for Shamir's Secret Sharing
|
48
|
+
test_files: []
|