├── .gitignore ├── .travis.yml ├── Contributors.md ├── LICENSE ├── README.md ├── SSSA ├── __init__.py ├── sssa.py └── utils.py ├── docs └── safeprime.txt ├── setup.py ├── sssa_tests.py └── utils_tests.py /.gitignore: -------------------------------------------------------------------------------- 1 | # ---> Python 2 | # Byte-compiled / optimized / DLL files 3 | __pycache__/ 4 | *.py[cod] 5 | *$py.class 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | env/ 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *,cover 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | # PyBuilder 59 | target/ 60 | 61 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | - "2.6" 5 | - "2.7" 6 | - "3.2" 7 | - "3.3" 8 | - "3.4" 9 | - "3.5" 10 | - "nightly" 11 | 12 | script: 13 | - python ./utils_tests.py 14 | - python ./sssa_tests.py 15 | -------------------------------------------------------------------------------- /Contributors.md: -------------------------------------------------------------------------------- 1 | Contributors: 2 | Alexander Scheel - alexander.m.scheel@gmail.com 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Alexander Scheel, Joel May, Matthew Burket 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sssa-python 2 | [![Build Status](https://travis-ci.org/SSSaaS/sssa-python.svg?branch=master)](https://travis-ci.org/SSSaaS/sssa-python) 3 | 4 | An implementation of Shamir's Secret Sharing Algorithm in Python 5 | Compatible with both Python 2.7 and 3.5; other versions untested. 6 | 7 | Copyright (C) 2015 Alexander Scheel, Joel May, Matthew Burket 8 | See Contributors.md for a complete list of contributors. 9 | Licensed under the MIT License. 10 | 11 | ## Usage 12 | Note: this library is for a pure implementation of SSS in Python; 13 | if you are looking for the API Library for SSSaaS, look [here](https://github.com/SSSAAS/sssaas-python). 14 | 15 | sssa.create(minimum, shares, raw) - creates a set of shares 16 | 17 | sssa.combine(shares) - combines shares into secret 18 | 19 | For more detailed documentation, check out docs/sssa.md 20 | 21 | ## Contributing 22 | We welcome pull requests, issues, security advice on this library, or other contributions you feel are necessary. Feel free to open an issue to discuss any questions you have about this library. 23 | 24 | The reference implementation for this cross-language project was written in Go, [here](https://github.com/SSSAAS/sssa-golang). 25 | Please make sure all tests pass before submitting a pull request. In particular, 26 | `python ./utils_tests.py && python ./sssa_tests.py` will run all internal tests 27 | and the [go-libtest](https://github.com/SSSAAS/go-libtest) suite's tests should be run 28 | against the changes before submission. 29 | 30 | For security issues, send a GPG-encrypted email to with public key [0xBDC5F518A973035E](https://pgp.mit.edu/pks/lookup?op=vindex&search=0xBDC5F518A973035E). 31 | -------------------------------------------------------------------------------- /SSSA/__init__.py: -------------------------------------------------------------------------------- 1 | from SSSA.sssa import sssa 2 | from SSSA.utils import utils 3 | -------------------------------------------------------------------------------- /SSSA/sssa.py: -------------------------------------------------------------------------------- 1 | from SSSA.utils import utils 2 | 3 | class sssa: 4 | prime = 115792089237316195423570985008687907853269984665640564039457584007913129639747 5 | util = utils() 6 | 7 | def create(self, minimum, shares, raw): 8 | if (shares < minimum): 9 | return 10 | 11 | secret = self.util.split_ints(raw) 12 | numbers = [0] 13 | polynomial = [] 14 | for i in range(0, len(secret)): 15 | polynomial.append([secret[i]]) 16 | for j in range(1, minimum): 17 | value = self.util.random() 18 | while value in numbers: 19 | value = self.util.random() 20 | numbers.append(value) 21 | 22 | polynomial[i].append(value) 23 | 24 | result = [""]*shares 25 | 26 | for i in range(0, shares): 27 | for j in range(0, len(secret)): 28 | value = self.util.random() 29 | while value in numbers: 30 | value = self.util.random() 31 | numbers.append(value) 32 | 33 | y = self.util.evaluate_polynomial(polynomial[j], value) 34 | 35 | result[i] += self.util.to_base64(value) 36 | result[i] += self.util.to_base64(y) 37 | 38 | return result 39 | 40 | def combine(self, shares): 41 | secrets = [] 42 | 43 | for index,share in enumerate(shares): 44 | if len(share) % 88 != 0: 45 | return 46 | 47 | count = int(len(share) / 88) 48 | secrets.append([]) 49 | 50 | for i in range(0, count): 51 | cshare = share[i*88:(i+1)*88] 52 | secrets[index].append([self.util.from_base64(cshare[0:44]), self.util.from_base64(cshare[44:88])]) 53 | 54 | secret = [0] * len(secrets[0]) 55 | 56 | for part_index,part in enumerate(secret): 57 | for share_index,share in enumerate(secrets): 58 | origin = share[part_index][0] 59 | originy = share[part_index][1] 60 | numerator = 1 61 | denominator = 1 62 | for product_index,product in enumerate(secrets): 63 | if product_index != share_index: 64 | current = product[part_index][0] 65 | numerator = (numerator * (-1*current)) % self.util.prime 66 | denominator = (denominator * (origin - current)) % self.util.prime 67 | 68 | working = ((originy * numerator * self.util.mod_inverse(denominator)) + self.util.prime) 69 | secret[part_index] = (secret[part_index] + working) % self.util.prime 70 | 71 | return self.util.merge_ints(secret) 72 | -------------------------------------------------------------------------------- /SSSA/utils.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import codecs 3 | import math 4 | import struct 5 | from random import SystemRandom 6 | 7 | class utils: 8 | prime = 0 9 | 10 | def __init__(self, prime=115792089237316195423570985008687907853269984665640564039457584007913129639747): 11 | self.prime = prime 12 | 13 | def random(self): 14 | return SystemRandom().randrange(self.prime) 15 | 16 | def split_ints(self, secret): 17 | result = [] 18 | 19 | working = None 20 | byte_object = None 21 | try: 22 | byte_object = bytes(secret, "utf8") 23 | except: 24 | byte_object = bytes(secret) 25 | text = codecs.encode(byte_object, 'hex_codec').decode('utf8') + "00"*(32 - (len(byte_object) % 32)) 26 | 27 | for i in range(0, int(len(text)/64)): 28 | result.append(int(text[i*64:(i+1)*64], 16)) 29 | 30 | return result 31 | 32 | def merge_ints(self, secrets): 33 | result = "" 34 | 35 | for secret in secrets: 36 | hex_data = hex(secret)[2:].replace("L", "") 37 | result += "0"*(64 - (len(hex_data))) + hex_data 38 | 39 | byte_object = None 40 | try: 41 | byte_object = bytes(result, "utf8") 42 | 43 | return codecs.decode(byte_object, 'hex_codec').decode('utf8').rstrip("\00\x00") 44 | except: 45 | byte_object = bytes(result) 46 | 47 | return codecs.decode(byte_object, 'hex_codec').rstrip("\00\x00") 48 | pass 49 | 50 | def evaluate_polynomial(self, coefficients, value): 51 | result = 0 52 | for coefficient in reversed(coefficients): 53 | result = result * value + coefficient 54 | result = result % self.prime 55 | 56 | return result 57 | 58 | def to_base64(self, number): 59 | tmp = hex(number)[2:].replace("L", "") 60 | tmp = "0"*(64 - len(tmp)) + tmp 61 | 62 | try: 63 | tmp = bytes(tmp, "utf8") 64 | except: 65 | tmp = bytes(tmp) 66 | 67 | result = str(base64.urlsafe_b64encode(b'\00'*(64 - len(tmp)) + codecs.decode(tmp, 'hex_codec')).decode('utf8')) 68 | 69 | if len(result) != 44: 70 | print("error: result, tmp, number") 71 | print(result) 72 | print(len(result)) 73 | print(tmp) 74 | print(len(tmp)) 75 | print(number) 76 | print(hex(number)) 77 | print(hex(codecs.decode(tmp, 'hex_codec'))) 78 | return result 79 | 80 | def from_base64(self, number): 81 | byte_number = number 82 | try: 83 | byte_number = bytes(byte_number, "utf8") 84 | except: 85 | byte_number = bytes(byte_number) 86 | 87 | tmp = base64.urlsafe_b64decode(byte_number) 88 | 89 | try: 90 | tmp = bytes(tmp, "utf8") 91 | except: 92 | tmp = bytes(tmp) 93 | 94 | return int(codecs.encode(tmp, 'hex_codec'), 16) 95 | 96 | def gcd(self, a, b): 97 | if b == 0: 98 | return [a, 1, 0] 99 | else: 100 | n = int(math.floor(a*1.0/b)) 101 | c = a % b 102 | r = self.gcd(b, c) 103 | return [r[0], r[2], r[1] - r[2]*n] 104 | 105 | def mod_inverse(self, number): 106 | remainder = (self.gcd(self.prime, number % self.prime))[2] 107 | if number < 0: 108 | remainder *= -1 109 | return (self.prime + remainder) % self.prime 110 | -------------------------------------------------------------------------------- /docs/safeprime.txt: -------------------------------------------------------------------------------- 1 | 2^256 - 189: 2 | 115792089237316195423570985008687907853269984665640564039457584007913129639747 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | SSSA Python 3 | =========== 4 | """ 5 | 6 | from setuptools import setup 7 | 8 | setup( 9 | name="sssa-python", 10 | version="0.0.2", 11 | url="https://github.com/SSSaaS/sssa-python", 12 | license="MIT", 13 | author="Alexander Scheel", 14 | author_email="alexander.m.scheel@gmail.com", 15 | description=("Helper Shamir's Secret Sharing module for Python"), 16 | packages=["SSSA"], 17 | classifiers=[ 18 | "Intended Audience :: Developers", 19 | "License :: OSI Approved :: MIT License", 20 | "Operating System :: OS Independent", 21 | "Topic :: Security :: Cryptography", 22 | ], 23 | ) 24 | -------------------------------------------------------------------------------- /sssa_tests.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from SSSA import sssa 3 | 4 | class TestStringMethods(unittest.TestCase): 5 | def test_create_combine(self): 6 | values = ["N17FigASkL6p1EOgJhRaIquQLGvYV0", "0y10VAfmyH7GLQY6QccCSLKJi8iFgpcSBTLyYOGbiYPqOpStAf1OYuzEBzZR", "KjRHO1nHmIDidf6fKvsiXWcTqNYo2U9U8juO94EHXVqgearRISTQe0zAjkeUYYBvtcB8VWzZHYm6ktMlhOXXCfRFhbJzBUsXaHb5UDQAvs2GKy6yq0mnp8gCj98ksDlUultqygybYyHvjqR7D7EAWIKPKUVz4of8OzSjZlYg7YtCUMYhwQDryESiYabFID1PKBfKn5WSGgJBIsDw5g2HB2AqC1r3K8GboDN616Swo6qjvSFbseeETCYDB3ikS7uiK67ErIULNqVjf7IKoOaooEhQACmZ5HdWpr34tstg18rO"] 7 | minimum = [4, 6, 20] 8 | shares = [5, 100, 100] 9 | 10 | sss = sssa() 11 | 12 | for index,value in enumerate(values): 13 | self.assertEqual(sss.combine(sss.create(minimum[index], shares[index], value)), value) 14 | 15 | def test_library_combine(self): 16 | sss = sssa() 17 | shares = ["U1k9koNN67-og3ZY3Mmikeyj4gEFwK4HXDSglM8i_xc=yA3eU4_XYcJP0ijD63Tvqu1gklhBV32tu8cHPZXP-bk=", "O7c_iMBaGmQQE_uU0XRCPQwhfLBdlc6jseTzK_qN-1s=ICDGdloemG50X5GxteWWVZD3EGuxXST4UfZcek_teng=", "8qzYpjk7lmB7cRkOl6-7srVTKNYHuqUO2WO31Y0j1Tw=-g6srNoWkZTBqrKA2cMCA-6jxZiZv25rvbrCUWVHb5g=", "wGXxa_7FPFSVqdo26VKdgFxqVVWXNfwSDQyFmCh2e5w=8bTrIEs0e5FeiaXcIBaGwtGFxeyNtCG4R883tS3MsZ0=", "j8-Y4_7CJvL8aHxc8WMMhP_K2TEsOkxIHb7hBcwIBOo=T5-EOvAlzGMogdPawv3oK88rrygYFza3KSki2q8WEgs="] 18 | 19 | self.assertEqual(sss.combine(shares), "test-pass") 20 | 21 | if __name__ == '__main__': 22 | unittest.main() 23 | -------------------------------------------------------------------------------- /utils_tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | 3 | import unittest 4 | from SSSA import utils 5 | 6 | class TestStringMethods(unittest.TestCase): 7 | def test_random(self): 8 | util = utils() 9 | for i in range(0, 10000): 10 | self.assertEqual(util.random() < util.prime, True) 11 | 12 | def test_base_conversion(self): 13 | util = utils() 14 | for i in range(0, 10000): 15 | value = util.random() 16 | self.assertEqual(util.from_base64(util.to_base64(value)), value) 17 | 18 | def test_to_base64(self): 19 | util = utils() 20 | for i in range(0, 10000): 21 | value = util.random() 22 | self.assertEqual(len(util.to_base64(value)), 44) 23 | 24 | def test_split_merge(self): 25 | util = utils() 26 | values = ["N17FigASkL6p1EOgJhRaIquQLGvYV0", "0y10VAfmyH7GLQY6QccCSLKJi8iFgpcSBTLyYOGbiYPqOpStAf1OYuzEBzZR", "KjRHO1nHmIDidf6fKvsiXWcTqNYo2U9U8juO94EHXVqgearRISTQe0zAjkeUYYBvtcB8VWzZHYm6ktMlhOXXCfRFhbJzBUsXaHb5UDQAvs2GKy6yq0mnp8gCj98ksDlUultqygybYyHvjqR7D7EAWIKPKUVz4of8OzSjZlYg7YtCUMYhwQDryESiYabFID1PKBfKn5WSGgJBIsDw5g2HB2AqC1r3K8GboDN616Swo6qjvSFbseeETCYDB3ikS7uiK67ErIULNqVjf7IKoOaooEhQACmZ5HdWpr34tstg18rO"] 27 | for value in values: 28 | self.assertEqual(util.merge_ints(util.split_ints(value)), value) 29 | 30 | def test_split_merge_odds(self): 31 | util = utils() 32 | values = ["a" + "\0"*100 + "a", "a"*31 + "哈囉世界", "こんにちは、世界"*32] 33 | for value in values: 34 | self.assertEqual(util.merge_ints(util.split_ints(value)), value) 35 | 36 | def test_mod_inverse(self): 37 | util = utils() 38 | for i in range(0, 10000): 39 | value = util.random() 40 | self.assertEqual((value * util.mod_inverse(value)) % util.prime, 1) 41 | 42 | def test_evaluate_polynomial(self): 43 | util = utils() 44 | values = [[[20, 21, 42], 0], [[0, 0, 0], 4], [[1, 2, 3, 4, 5], 10]] 45 | results = [20, 0, 54321] 46 | for index,value in enumerate(values): 47 | self.assertEqual(util.evaluate_polynomial(value[0], value[1]), results[index]) 48 | 49 | if __name__ == '__main__': 50 | unittest.main() 51 | --------------------------------------------------------------------------------