-
Notifications
You must be signed in to change notification settings - Fork 143
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
343 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,3 +34,5 @@ nosetests.xml | |
.mr.developer.cfg | ||
.project | ||
.pydevproject | ||
|
||
ignore |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,39 @@ | ||
secretsharing | ||
============= | ||
|
||
A system for sharing secrets using Shamir's Secret Sharing Scheme. | ||
|
||
## Sample Usage | ||
|
||
#### Creating secrets from strings | ||
|
||
>>> from secretsharing.shamir import Secret | ||
>>> secret = Secret.from_printable_ascii("Hello, world!") | ||
>>> secret.as_printable_ascii() | ||
'Hello, world!' | ||
>>> secret.as_int() | ||
43142121247394322427211362L | ||
|
||
#### Creating secrets from integers | ||
|
||
>>> secret = Secret(43142121247394322427211362L) | ||
|
||
#### Spliting secrets into shares | ||
|
||
>>> shares = secret.split(3, 5) | ||
>>> for s in shares: | ||
... print s: | ||
... | ||
01-762cfaba2802c2191e486f | ||
02-1762f2ca77fbd06de2565c4 | ||
03-123b648dc47453748d24662 | ||
04-17ec24f587e9b535924ea48 | ||
05-8753401c25bf5b0f1d5177 | ||
|
||
#### Recovering secrets from shares | ||
|
||
>>> recovered_shares = ['02-1762f2ca77fbd06de2565c4', '04-17ec24f587e9b535924ea48', '05-8753401c25bf5b0f1d5177'] | ||
>>> recovered_secret = Secret.from_shares(recovered_shares) | ||
>>> recovered_secret.as_printable_ascii() | ||
'Hello, world!' | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
# -*- coding: utf-8 -*- | ||
""" | ||
Secret Sharing | ||
~~~~~ | ||
:copyright: (c) 2014 by Halfmoon Labs | ||
:license: MIT, see LICENSE for more details. | ||
""" | ||
|
||
__version__ = '0.1.0' | ||
|
||
from .shamir import Secret |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
# -*- coding: utf-8 -*- | ||
""" | ||
Secret Sharing | ||
~~~~~ | ||
:copyright: (c) 2014 by Halfmoon Labs | ||
:license: MIT, see LICENSE for more details. | ||
""" | ||
|
||
import random | ||
|
||
def egcd(a, b): | ||
if a == 0: | ||
return (b, 0, 1) | ||
else: | ||
g, y, x = egcd(b % a, a) | ||
return (g, x - (b // a) * y, y) | ||
|
||
def mod_inverse(k, prime): | ||
k = k % prime | ||
if k < 0: | ||
r = egcd(prime, -k)[2] | ||
else: | ||
r = egcd(prime, k)[2] | ||
return (prime + r) % prime | ||
|
||
def get_mersenne_primes(): | ||
""" Returns all the mersenne primes with less than 500 digits. | ||
All primes: | ||
3, 7, 31, 127, 8191, 131071, 524287, 2147483647L, 2305843009213693951L, | ||
618970019642690137449562111L, 162259276829213363391578010288127L, | ||
170141183460469231731687303715884105727L, | ||
68647976601306097149...12574028291115057151L, (157 digits) | ||
53113799281676709868...70835393219031728127L, (183 digits) | ||
10407932194664399081...20710555703168729087L, (386 digits) | ||
""" | ||
mersenne_prime_exponents = [ | ||
2, 3, 5, 7, 13, 17, 19, 31, 61, 89, 107, 127, 521, 607, 1279 | ||
] | ||
primes = [] | ||
for exp in mersenne_prime_exponents: | ||
prime = long(1) | ||
for i in range(exp): | ||
prime *= 2 | ||
prime -= 1 | ||
primes.append(prime) | ||
return primes | ||
|
||
def get_large_enough_prime(batch): | ||
""" Returns a prime number that is greater all the numbers in the batch. | ||
""" | ||
# build a list of primes | ||
primes = get_mersenne_primes() | ||
# find a prime that is greater than all the numbers in the batch | ||
for prime in primes: | ||
numbers_greater_than_prime = [i for i in batch if i > prime] | ||
if len(numbers_greater_than_prime) == 0: | ||
return prime | ||
return None | ||
|
||
def random_polynomial(degree, intercept, upper_bound): | ||
""" Generates a random polynomial with positive coefficients. | ||
""" | ||
if degree < 0: | ||
raise ValueError('Degree must be a non-negative number.') | ||
coefficients = [intercept] | ||
for i in range(degree): | ||
random_coeff = random.randint(0, upper_bound-1) | ||
coefficients.append(random_coeff) | ||
return coefficients | ||
|
||
def get_polynomial_points(coefficients, num_points, prime): | ||
""" Calculates the first n polynomial points. | ||
[ (1, f(1)), (2, f(2)), ... (n, f(n)) ] | ||
""" | ||
points = [] | ||
for x in range(1, num_points+1): | ||
# start with x=1 and calculate the value of y | ||
y = coefficients[0] | ||
# calculate each term and add it to y, using modular math | ||
for i in range(1, len(coefficients)): | ||
exponentiation = (long(x)**i) % prime | ||
term = (coefficients[i] * exponentiation) % prime | ||
y = (y + term) % prime | ||
# add the point to the list of points | ||
points.append((x, y)) | ||
return points | ||
|
||
def modular_lagrange_interpolation(x, points, prime): | ||
# break the points up into lists of x and y values | ||
x_values, y_values = zip(*points) | ||
# initialize f(x) and begin the calculation: f(x) = SUM( y_i * l_i(x) ) | ||
f_x = long(0) | ||
for i in range(len(points)): | ||
# evaluate the lagrange basis polynomial l_i(x) | ||
numerator, denominator = 1, 1 | ||
for j in range(len(points)): | ||
# don't compute a polynomial fraction if i equals j | ||
if i == j: continue | ||
# compute a fraction and update the existing numerator + denominator | ||
numerator = (numerator * (x - x_values[j])) % prime | ||
denominator = (denominator * (x_values[i] - x_values[j])) % prime | ||
# get the polynomial from the numerator + mod inverse of the denominator | ||
lagrange_polynomial = numerator * mod_inverse(denominator, prime) | ||
# multiply the current y and the evaluated polynomial and add it to f(x) | ||
f_x = (prime + f_x + (y_values[i] * lagrange_polynomial)) % prime | ||
return f_x | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
# -*- coding: utf-8 -*- | ||
""" | ||
Secret Sharing | ||
~~~~~ | ||
:copyright: (c) 2014 by Halfmoon Labs | ||
:license: MIT, see LICENSE for more details. | ||
""" | ||
|
||
import string | ||
from characters.charset import charset_to_int, int_to_charset | ||
from characters.hex import hex_to_int, int_to_hex, is_hex | ||
|
||
from .math import get_large_enough_prime, random_polynomial, \ | ||
get_polynomial_points, modular_lagrange_interpolation | ||
|
||
def share_to_point(share): | ||
# share should be in the format "01-d051080de7..." | ||
if isinstance(share, str) and share.count('-') == 1: | ||
x,y = share.split('-') | ||
if is_hex(x) and is_hex(y): | ||
return (hex_to_int(x), hex_to_int(y)) | ||
raise ValueError('Share format is invalid.') | ||
|
||
def point_to_share(point): | ||
# point should be in the format (1, 4938573982723...) | ||
if isinstance(point, tuple) and len(point) == 2: | ||
if isinstance(point[0], (int, long)) and isinstance(point[1], (int, long)): | ||
x,y = point | ||
if x > 255: | ||
raise ValueError('The largest x coordinate for a share is 255.') | ||
hex_x, hex_y = int_to_hex(x).zfill(2), int_to_hex(y) | ||
return hex_x + '-' + hex_y | ||
else: | ||
print "ah!" | ||
raise ValueError('Point format is invalid. Must be a pair of integers.') | ||
|
||
class Secret(): | ||
def __init__(self, secret_int): | ||
if not isinstance(secret_int, (int, long)) and secret_int >= 0: | ||
raise ValueError("Secret must be a non-negative integer.") | ||
self._secret = secret_int | ||
|
||
@classmethod | ||
def from_charset(cls, secret, charset): | ||
if not isinstance(secret, str): | ||
raise ValueError("Secret must be a string.") | ||
if not isinstance(charset, str): | ||
raise ValueError("Charset must be a string.") | ||
if (set(secret) - set(charset)): | ||
raise ValueError("Secret contains characters that aren't in the charset.") | ||
secret_int = charset_to_int(secret, charset) | ||
return cls(secret_int) | ||
|
||
@classmethod | ||
def from_hex(cls, secret): | ||
return cls.from_charset(secret, string.hexdigits[0:16]) | ||
|
||
@classmethod | ||
def from_printable_ascii(cls, secret): | ||
return cls.from_charset(secret, string.printable) | ||
|
||
@classmethod | ||
def from_shares(cls, shares): | ||
if not isinstance(shares, list): | ||
raise ValueError("Shares must be in list form.") | ||
for share in shares: | ||
if not isinstance(share, str): | ||
raise ValueError("Each share must be a string.") | ||
points = [] | ||
for share in shares: | ||
points.append(share_to_point(share)) | ||
x_values, y_values = zip(*points) | ||
prime = get_large_enough_prime(y_values) | ||
free_coefficient = modular_lagrange_interpolation(0, points, prime) | ||
secret_int = free_coefficient | ||
return cls(secret_int) | ||
|
||
def split(self, threshold, num_shares): | ||
""" Split the secret into shares. The threshold is the total number of | ||
""" | ||
if threshold < 2: | ||
raise ValueError("Threshold must be >= 2.") | ||
if threshold > num_shares: | ||
raise ValueError("Threshold must be < the total number of shares.") | ||
prime = get_large_enough_prime([self._secret, num_shares]) | ||
if not prime: | ||
raise ValueError("Error! Secret is too long for share calculation!") | ||
coefficients = random_polynomial(threshold-1, self._secret, prime) | ||
points = get_polynomial_points(coefficients, num_shares, prime) | ||
shares = [] | ||
for point in points: | ||
shares.append(point_to_share(point)) | ||
return shares | ||
|
||
def as_int(self): | ||
return self._secret | ||
|
||
def as_charset(self, charset): | ||
return int_to_charset(self._secret, charset) | ||
|
||
def as_hex(self): | ||
return self.as_charset(string.hexdigits[0:16]) | ||
|
||
def as_printable_ascii(self): | ||
return self.as_charset(string.printable) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
""" | ||
Secret Sharing | ||
============== | ||
""" | ||
|
||
from setuptools import setup | ||
|
||
setup( | ||
name='secretsharing', | ||
version='0.1.0', | ||
url='https://github.com/halfmoonlabs/secretsharing', | ||
license='MIT', | ||
author='Halfmoon Labs', | ||
author_email='[email protected]', | ||
description="Tools for sharing secrets, including shamir's secret sharing scheme.", | ||
packages=[ | ||
'secretsharing', | ||
], | ||
zip_safe=False, | ||
install_requires=[ | ||
'characters>=0.1' | ||
], | ||
classifiers=[ | ||
'Intended Audience :: Developers', | ||
'License :: OSI Approved :: MIT License', | ||
'Operating System :: OS Independent', | ||
'Programming Language :: Python', | ||
], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
# -*- coding: utf-8 -*- | ||
""" | ||
Secret Sharing | ||
~~~~~ | ||
:copyright: (c) 2014 by Halfmoon Labs | ||
:license: MIT, see LICENSE for more details. | ||
""" | ||
|
||
import unittest | ||
from test import test_support | ||
|
||
from secretsharing.shamir import Secret | ||
|
||
class ShamirSharingTest(unittest.TestCase): | ||
def setUp(self): | ||
pass | ||
|
||
def tearDown(self): | ||
pass | ||
|
||
def secret_message_to_shares(secret_message): | ||
|
||
return shares | ||
|
||
def test_short_secret(self): | ||
secret_message = 'Hello, world!' | ||
secret = Secret.from_printable_ascii(secret_message) | ||
shares = secret.split(3, 5) | ||
assert(Secret.from_shares(shares[0:3]).as_printable_ascii() == secret_message) | ||
|
||
def test_long_secret(self): | ||
secret_message = '1054dcaf130fd0c0a51cb0aa762df16faa5f9ee444f3a82f45f62c579635d1b7cba1b5f76587ecba66b' | ||
secret = Secret.from_hex(secret_message) | ||
shares = secret.split(3, 5) | ||
assert(Secret.from_shares(shares[0:3]).as_hex() == secret_message) | ||
|
||
def test_main(): | ||
|
||
test_support.run_unittest( | ||
ShamirSharingTest, | ||
) | ||
|
||
if __name__ == '__main__': | ||
test_main() | ||
|
||
|