cloversplitter 0.2.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.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/cloversplitter.rb +230 -0
  3. metadata +48 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7f5dad685414f25162e92b3169f4780edf7934455fe9e6da0880576b3e08e8c0
4
+ data.tar.gz: 5b0e6c43be0738f1ac3fd7d613359c86fc8b61b8081efdc519ba111b261b52d4
5
+ SHA512:
6
+ metadata.gz: d787ad4c93b31ac76d3b50a8f211bbe031891b3d3f4bdf994687524bbf3ed940652ad4325865031c195a292de711be017a4980c02caef8185281cb76ab6ce6de
7
+ data.tar.gz: e62ed85aff2468beea69a22d488902761557b16ab65fc953a0abe917f59f88e74d30c691365fd0dbada1be698e2a108eb3dff6f71189128f4bd0ccd382c1c167
@@ -0,0 +1,230 @@
1
+ # Title: CLOVERSPLITTER
2
+ # Version: 0.2.1
3
+ #
4
+ # WARNING: Please be aware that this gem has not undergone any form of security evaluation. This gem is not recommended for usage under mission-critical circumstances and should not be relied upon to protect confidential or secret information. Users should assume that this gem is insecure until they can independently confirm otherwise.
5
+ #
6
+ # Copyright (C) 2020 Peter Bruce Funnell
7
+ #
8
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ # of this software and associated documentation files (the "Software"), to deal
10
+ # in the Software without restriction, including without limitation the rights
11
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ # copies of the Software, and to permit persons to whom the Software is
13
+ # furnished to do so, subject to the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be included in all
16
+ # copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
+ # SOFTWARE.
25
+
26
+ require "securerandom"
27
+
28
+ class CloverSplitter
29
+ def self.eval_at(poly, x, prime)
30
+ # Evaluates polynomial at x (used for share generation).
31
+ accum = 0
32
+
33
+ poly.reverse.each do |coeff|
34
+ accum = ((accum*x)+coeff) % prime
35
+ end
36
+
37
+ return accum
38
+ end
39
+
40
+ def self.str_to_int(a)
41
+ # Turns an ASCII-8BIT string into an integer.
42
+
43
+ # Prepend \x80 to the string to prevent information loss occuring for strings that start with \x00.
44
+ a = "\x80".force_encoding("ASCII-8BIT")+a
45
+
46
+ # Decode string into a list of integers (with one integer representing each character).
47
+ b = a.each_byte.to_a
48
+
49
+ # Initialise c_b; this will be used to hold bytes (expressed in binary) for each character.
50
+ c_b = String.new()
51
+
52
+ # Loop through each value in b, appending the corresponding binary sequence for each character to c_b.
53
+ b.each do |i|
54
+ n = i.to_s(2)
55
+ while n.length < 8 do
56
+ n = "0"+n
57
+ end
58
+ c_b += n
59
+ end
60
+
61
+ # Convert c_b into an integer.
62
+ c_b.to_i(2)
63
+ end
64
+
65
+ def self.int_to_str(a)
66
+ # Turns an integer into a string.
67
+
68
+ # Convert the integer into a string of binary.
69
+ b = a.to_s(2)
70
+
71
+ # Add zeroes to the start of the string of binary until the length is a multiple of 8.
72
+ while b.length % 8 != 0 do
73
+ b = "0"+b
74
+ end
75
+
76
+ # Convert the string of binary into a list of character values.
77
+ c = []
78
+ c_a = b.chars.each_slice(8).to_a
79
+ c_a.each do |i|
80
+ s = String.new()
81
+ i.each do |j|
82
+ s << j
83
+ end
84
+ c << s.to_i(2)
85
+ end
86
+
87
+ # Convert the list of character values back into a string.
88
+ d = String.new()
89
+ c.each do |i|
90
+ d << i.chr
91
+ end
92
+
93
+ # Ignore the first byte (which will always be \x80).
94
+ d = d[1, d.length-1]
95
+
96
+ return d
97
+ end
98
+
99
+ def self.generate_shares(secret, minimum=3, shares=6, prime=((2**3217)-1))
100
+ # This function takes a secret string and spits out a list of shares.
101
+ # Changing the default values for minimum, shares and prime can cause errors or other problems.
102
+ # secret: This argument should be set to the secret string from which the user wishes to generate shares.
103
+ # minimum: The minimum number of shares required to recover the secret. [DEFAULT: 3]
104
+ # shares: The total number of shares to be generated. [DEFAULT: 6]
105
+ # prime: The prime number used for share generation. [DEFAULT: ((2**3217-1)) [NOTE: This is the 18th Mersenne Prime]]
106
+
107
+ # Convert the secret into ASCII-8BIT.
108
+ secret = secret.force_encoding("ASCII-8BIT")
109
+
110
+ # Convert the secret into an integer.
111
+ secret_int = self.str_to_int(secret)
112
+
113
+ # Ensure that the parameters make at least some sense.
114
+ if minimum > shares
115
+ raise("The total number of shares to be generated must equal or exceed the minimum number of shares required for recovery.")
116
+ end
117
+
118
+ # Create a list of coefficients for the polynomial, consisting of the secret integer and a series of randomly generated integers.
119
+ # The total length of the list should be the minimum number of shares required for recovery of the secret integer.
120
+ poly = [secret_int]+(Array.new(minimum-1) {(SecureRandom.rand(prime))})
121
+
122
+ # Evaluate n points on the polynomial, where n is the total number of shares to be generated.
123
+ points = []
124
+ (1..shares).each do |i|
125
+ points << [i, eval_at(poly, i, prime)]
126
+ end
127
+
128
+ # Attempt a test recovery of the secret from the set of all points.
129
+ check = self.recover_secret(points, prime)
130
+
131
+ # If the test recovery yielded the original secret, then the shares are valid and should be returned; if not, something went wrong.
132
+ if check == secret
133
+ return points
134
+ else
135
+ raise("Something went wrong during share generation. This could be due to the secret being too long or due to the minimum, shares and/or prime parameters being misconfigured.")
136
+ end
137
+ end
138
+
139
+ def self.egcd(a, b)
140
+ # Extended Euclidean algorithm.
141
+ x, y = 0, 1
142
+ last_x, last_y = 1, 0
143
+
144
+ while b != 0 do
145
+ quot = a/b
146
+ a, b = b, a % b
147
+ x, last_x = last_x-quot*x, x
148
+ y, last_y = last_y-quot*y, y
149
+ end
150
+
151
+ return last_x, last_y
152
+ end
153
+
154
+ def self.divmod(num, den, p)
155
+ a, b = egcd(den, p)
156
+
157
+ return num*a
158
+ end
159
+
160
+ def self.prod(vals)
161
+ # Calculate the product of a list of values.
162
+ accum = 1
163
+
164
+ vals.each do |v|
165
+ accum *= v
166
+ end
167
+
168
+ return accum
169
+ end
170
+
171
+ def self.sum(vals)
172
+ # Calculate the sum of a list of values.
173
+ accum = 0
174
+
175
+ vals.each do |v|
176
+ accum += v
177
+ end
178
+
179
+ return accum
180
+ end
181
+
182
+ def self.lagrange_interpolate(x, x_s, y_s, p)
183
+ k = x_s.length
184
+
185
+ if k != x_s.uniq.length
186
+ raise("An error occured during lagrange interpolation.")
187
+ end
188
+
189
+ nums = []
190
+ dens = []
191
+ (0..k-1).each do |i|
192
+ others = Array.new(x_s)
193
+ cur = others.delete_at(i)
194
+ nums_a = Array.new()
195
+ dens_a = Array.new()
196
+ others.each do |o|
197
+ nums_a << x-o
198
+ dens_a << cur-o
199
+ end
200
+ nums << prod(nums_a)
201
+ dens << prod(dens_a)
202
+ end
203
+
204
+ den = prod(dens)
205
+ num_a = Array.new()
206
+ (0..(k-1)).each do |i|
207
+ num_a << divmod(nums[i]*den*y_s[i] % p, dens[i], p)
208
+ end
209
+
210
+ num = sum(num_a)
211
+
212
+ return (divmod(num, den, p)+p) % p
213
+ end
214
+
215
+ def self.recover_secret(shares, prime=((2**3217)-1))
216
+ # This function takes a list of shares and attempts to recover the secret string from those shares. If the number of shares is below the minimum number of shares required for recovery, the resulting string will be nonsensical and may scramble/gargle the console if printed. On a *nix system, the console can usually be reset with the "reset" command.
217
+ # shares: A list containing two or more shares.
218
+ # prime: The prime number that was used for share generation.
219
+
220
+ if shares.length < 2
221
+ raise("At least two shares are required in order to attempt secret recovery.")
222
+ end
223
+
224
+ x_s, y_s = shares.transpose
225
+
226
+ recovered_data = lagrange_interpolate(0, x_s, y_s, prime)
227
+
228
+ return self.int_to_str(recovered_data)
229
+ end
230
+ end
metadata ADDED
@@ -0,0 +1,48 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cloversplitter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.1
5
+ platform: ruby
6
+ authors:
7
+ - Peter Funnell
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-06-09 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: 'A pure-Ruby implementation of Shamir''s Secret Sharing. WARNING: Please
14
+ be aware that this gem has not undergone any form of security evaluation. This gem
15
+ is not recommended for usage under mission-critical circumstances and should not
16
+ be relied upon to protect confidential or secret information. Users should assume
17
+ that this gem is insecure until they can independently confirm otherwise.'
18
+ email: hello@octetsplicer.com
19
+ executables: []
20
+ extensions: []
21
+ extra_rdoc_files: []
22
+ files:
23
+ - lib/cloversplitter.rb
24
+ homepage: https://github.com/octetsplicer/CLOVERSPLITTER
25
+ licenses:
26
+ - MIT
27
+ metadata: {}
28
+ post_install_message:
29
+ rdoc_options: []
30
+ require_paths:
31
+ - lib
32
+ required_ruby_version: !ruby/object:Gem::Requirement
33
+ requirements:
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: 2.5.5
37
+ required_rubygems_version: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ requirements: []
43
+ rubyforge_project:
44
+ rubygems_version: 2.7.6.2
45
+ signing_key:
46
+ specification_version: 4
47
+ summary: Shamir's Secret Sharing in Ruby
48
+ test_files: []