Skip to content

jqueiroz/python-sslib

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Secret sharing library

A Python3 library for sharing secrets. Currently, only Shamir's secret sharing scheme is available, but other algorithms should be implemented in the future.

Installation

pip install sslib

Shamir's secret sharing

Overview

Shamir's algorithm allows one to to split an integer S into n shares in such a way that at least k of these shares are required to recover the original secret.

The key ideia is to build a polynomial Q of degree k-1 over a finite field GF(P), where P is a prime number greater than max(S, n), such that Q(0) = S. Then, n points are distributed to participants—for example, (1, Q(1)), (2, Q(2)), ..., (n, Q(n)).

Given the prime P and any k points, it is possible to recover the secret by reconstructing Q and evaluating Q(0). However, knowledge of fewer than k points leaves the secret S completely undetermined: for any 0 ≤ S' < P, there exists a polynomial Q' over GF(P) passing through all given points such that Q'(0) = S'.

Usage

Splitting a sequence of bytes B into n shares, at least k of which are required to rebuild the original sequence, can be done by calling shamir.split_secret(B, k, n). This function outputs a dict containing:

  • required_shares: the number of shares required for recovery;
  • prime_mod: the prime number P;
  • shares: a list containing n points of the polynomial Q.

Subsequent recovery is possible by calling shamir.recover_secret(dict). Beware that dict should include not only the list of shares (shares), but also the prime modulus used (prime_mod). The number of required shares (required_shares) is optional, but recommended. If fewer than necessary shares are provided, but required_shares is specified, an error will be reported; otherwise, an incorrect secret will be silently produced.

In the output of shamir.split_secret, the prime modulus is represented as a sequence of bytes, and points of Q are represented as ordered pairs (x, y), where x is an int and y is a sequence of bytes. In order to facilitate secret distribution, the functions shamir.to_base64, shamir.from_base64, shamir.to_hex and shamir.from_hex are provided (see examples below).

Splitting secret into shares (base64)

>>> from sslib import shamir
>>> required_shares = 2
>>> distributed_shares = 5
>>> shamir.to_base64(shamir.split_secret("this is my secret".encode('ascii'), required_shares, distributed_shares))
{'required_shares': 2, 'prime_mod': 'AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhQ==', 'shares': ['1-Swwdr0O19NSMsoG4DXxvTeB9WTykw9+a', '2-lhg7XodrvzSw+5BPsYW+LkfaPxPmFVnA', '3-4SRZDcshiZTVRJ7nVY8NDq83JOsnZtPm', '4-LDB2vQ7XU/T5ja1++Zhb7xaUCsJouE2H', '5-dzyUbFKNHlUd1rwWnaGqz33w8JmqCcet']}

Recovering secret from shares (base64)

>>> from sslib import shamir
>>> data = {'required_shares': 2, 'prime_mod': 'AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhQ==', 'shares': ['1-Swwdr0O19NSMsoG4DXxvTeB9WTykw9+a', '3-4SRZDcshiZTVRJ7nVY8NDq83JOsnZtPm']}
>>> shamir.recover_secret(shamir.from_base64(data)).decode('ascii')
'this is my secret'

Splitting secret into shares (hex)

>>> from sslib import shamir
>>> required_shares = 2
>>> distributed_shares = 5
>>> shamir.to_hex(shamir.split_secret("this is my secret".encode('ascii'), required_shares, distributed_shares))
{'required_shares': 2, 'prime_mod': '01000000000000000000000000000000000000000000000085', 'shares': ['1-19bbe18e17f1d9af8144b09ceae46a13070d36ac81fcf606', '2-3377c31c2fe388ea9a1fee196c55b3b894f9f9f3a0878698', '3-4d33a4aa47d53825b2fb2b95edc6fd5e22e6bd3abf12172a', '4-66ef86385fc6e760cbd669126f384703b0d38081dd9ca7bc', '5-80ab67c677b8969be4b1a68ef0a990a93ec043c8fc27384e']}

Recovering secret from shares (hex)

>>> from sslib import shamir
>>> data = {'required_shares': 2, 'prime_mod': '01000000000000000000000000000000000000000000000085', 'shares': ['1-19bbe18e17f1d9af8144b09ceae46a13070d36ac81fcf606', '3-4d33a4aa47d53825b2fb2b95edc6fd5e22e6bd3abf12172a']}
>>> shamir.recover_secret(shamir.from_hex(data)).decode('ascii')
'this is my secret'

Splitting secret into shares (raw)

>>> from sslib import shamir
>>> required_shares = 2
>>> distributed_shares = 5
>>> shamir.split_secret("this is my secret".encode('ascii'), required_shares, distributed_shares)
{'required_shares': 2, 'prime_mod': b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x85', 'shares': [(1, b'OL\xc1\xfc\xc7\x18\xf2D\xd1\xcd\x087|N\xda\x9b\xfd\x19\x18\xddc!w\xcc'), (2, b'\x9e\x99\x83\xf9\x8e1\xba\x15;0\x9dN\x8f*\x94\xca\x81\x11\xbeUb\xd0\x8a$'), (3, b'\xed\xe6E\xf6UJ\x81\xe5\xa4\x942e\xa2\x06N\xf9\x05\nc\xcdb\x7f\x9c|'), (4, b"=3\x07\xf3\x1ccI\xb6\r\xf7\xc7|\xb4\xe2\t'\x89\x03\tEb.\xaeO"), (5, b'\x8c\x7f\xc9\xef\xe3|\x11\x86w[\\\x93\xc7\xbd\xc3V\x0c\xfb\xae\xbda\xdd\xc0\xa7')]}

Recovering secret from shares (raw)

>>> from sslib import shamir
>>> data = {'required_shares': 2, 'prime_mod': b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x85', 'shares': [(1, b'OL\xc1\xfc\xc7\x18\xf2D\xd1\xcd\x087|N\xda\x9b\xfd\x19\x18\xddc!w\xcc'), (3, b'\xed\xe6E\xf6UJ\x81\xe5\xa4\x942e\xa2\x06N\xf9\x05\nc\xcdb\x7f\x9c|')]}
>>> shamir.recover_secret(data).decode('ascii')
'this is my secret'

Optional parameters

Randomness source

shamir.split_secret now uses python secrets [PEP 506] library as a randomness source.

Prime modulus

By default, the function shamir.split_secret chooses the modulus from a list of known primes based on the length of the secret. It is also possible to specify the prime modulus to be used, as the following code illustrates. This is especially useful for large secrets: by choosing an easily representable prime (such as a Mersenne Prime), you can distribute the prime alongside the share to each participant with little overhead. In order to split an n-byte secret, a prime greater than 28n+8 must be provided (see implementation details).

Warning: if you follow this approach, please make sure that the provided modulus is indeed a prime number.

Splitting secret into shares
>>> from sslib import shamir
>>> required_shares = 2
>>> distributed_shares = 5
>>> shamir.to_base64(shamir.split_secret("this is my secret".encode('ascii'), required_shares, distributed_shares, prime_mod=2**607-1))
{'required_shares': 2, 'prime_mod': 'f////////////////////////////////////////////////////////////////////////////////////////////////////w==', 'shares': ['1-cHBzILxFiPMcv3pmK1SHQoxRIn47n+JsrK1xv+1h86iTmEOK2IXUk/RGkskGnEDbWYx7gI3bADZD9K1GHMqTEnYVwtFGdcHSzLdMXA==', '2-YODmQXiLEeY5fvTMVqkOhRiiRPx3P8TZWVrjf9rD51EnMIcVsQupJ+iNJZINOIG2sxj3ARu2AGyH6TAX0SuzBIK4ZTUTyxBANfwzRQ==', '3-UVFZYjTQmtlWPm8ygf2Vx6TzZ3qy36dGBghVP8gl2vm6yMqgiZF9u9zTuFsT1MKSDKVygamRAKLL3bLphYzS9o9bB5jhIF6tn0EaLg==', '4-QcHMgvEWI8xy/emYrVIdCjFEifjuf4mysrXG/7WHzqJOYQ4rYhdST9EaSyQacQNtZjHuAjdsANkP0jW7Oe3y6Jv9qfyuda0bCIYBFw==', '5-MjI/o61brL+PvWP+2KakTL2VrHcqH2wfX2M4v6Lpwkrh+VG2Op0m48Vg3e0hDURIv75pgsVHAQ9TxriM7k8S2qigTGB7yvuIccroAA==']}
Recovering secret from shares
>>> from sslib import shamir
>>> data = {'required_shares': 2, 'prime_mod': 2**607-1, 'shares': ['1-cHBzILxFiPMcv3pmK1SHQoxRIn47n+JsrK1xv+1h86iTmEOK2IXUk/RGkskGnEDbWYx7gI3bADZD9K1GHMqTEnYVwtFGdcHSzLdMXA==', '3-UVFZYjTQmtlWPm8ygf2Vx6TzZ3qy36dGBghVP8gl2vm6yMqgiZF9u9zTuFsT1MKSDKVygamRAKLL3bLphYzS9o9bB5jhIF6tn0EaLg==']}
>>> shamir.recover_secret(shamir.from_base64(data)).decode('ascii')
'this is my secret'

Limitations

The current implementation of Shamir's algorithm only supports splitting secrets of up to ~26.3 KiB. While splitting larger secrets is theoretically possible, recovery from shares becomes increasingly slower. On my computer (AMD FX-8300), recovering a 26 KiB secret from three shares takes around 0.9s.

The recommended approach for larger secrets is to perform encryption using a symmetric-key algorithm (such as AES) and then split the encryption key.

Implementation details

Internally, the secret is converted to an integer by interpreting its bytes in Big-Endian. Since the first bytes of the secret could be zero, the magic byte b'*' is always prepended before conversion. Upon recovery, this first byte is subsequently discarded.

About

A Python3 library for sharing secrets.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages