SecretSharing.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. #
  2. # SecretSharing.py : distribute a secret amongst a group of participants
  3. #
  4. # ===================================================================
  5. #
  6. # Copyright (c) 2014, Legrandin <helderijs@gmail.com>
  7. # All rights reserved.
  8. #
  9. # Redistribution and use in source and binary forms, with or without
  10. # modification, are permitted provided that the following conditions
  11. # are met:
  12. #
  13. # 1. Redistributions of source code must retain the above copyright
  14. # notice, this list of conditions and the following disclaimer.
  15. # 2. Redistributions in binary form must reproduce the above copyright
  16. # notice, this list of conditions and the following disclaimer in
  17. # the documentation and/or other materials provided with the
  18. # distribution.
  19. #
  20. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  21. # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  22. # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  23. # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  24. # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  25. # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  26. # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  27. # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  28. # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  29. # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  30. # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  31. # POSSIBILITY OF SUCH DAMAGE.
  32. # ===================================================================
  33. from Crypto.Util.py3compat import is_native_int
  34. from Crypto.Util import number
  35. from Crypto.Util.number import long_to_bytes, bytes_to_long
  36. from Crypto.Random import get_random_bytes as rng
  37. def _mult_gf2(f1, f2):
  38. """Multiply two polynomials in GF(2)"""
  39. # Ensure f2 is the smallest
  40. if f2 > f1:
  41. f1, f2 = f2, f1
  42. z = 0
  43. while f2:
  44. if f2 & 1:
  45. z ^= f1
  46. f1 <<= 1
  47. f2 >>= 1
  48. return z
  49. def _div_gf2(a, b):
  50. """
  51. Compute division of polynomials over GF(2).
  52. Given a and b, it finds two polynomials q and r such that:
  53. a = b*q + r with deg(r)<deg(b)
  54. """
  55. if (a < b):
  56. return 0, a
  57. deg = number.size
  58. q = 0
  59. r = a
  60. d = deg(b)
  61. while deg(r) >= d:
  62. s = 1 << (deg(r) - d)
  63. q ^= s
  64. r ^= _mult_gf2(b, s)
  65. return (q, r)
  66. class _Element(object):
  67. """Element of GF(2^128) field"""
  68. # The irreducible polynomial defining
  69. # this field is 1 + x + x^2 + x^7 + x^128
  70. irr_poly = 1 + 2 + 4 + 128 + 2 ** 128
  71. def __init__(self, encoded_value):
  72. """Initialize the element to a certain value.
  73. The value passed as parameter is internally encoded as
  74. a 128-bit integer, where each bit represents a polynomial
  75. coefficient. The LSB is the constant coefficient.
  76. """
  77. if is_native_int(encoded_value):
  78. self._value = encoded_value
  79. elif len(encoded_value) == 16:
  80. self._value = bytes_to_long(encoded_value)
  81. else:
  82. raise ValueError("The encoded value must be an integer or a 16 byte string")
  83. def __eq__(self, other):
  84. return self._value == other._value
  85. def __int__(self):
  86. """Return the field element, encoded as a 128-bit integer."""
  87. return self._value
  88. def encode(self):
  89. """Return the field element, encoded as a 16 byte string."""
  90. return long_to_bytes(self._value, 16)
  91. def __mul__(self, factor):
  92. f1 = self._value
  93. f2 = factor._value
  94. # Make sure that f2 is the smallest, to speed up the loop
  95. if f2 > f1:
  96. f1, f2 = f2, f1
  97. if self.irr_poly in (f1, f2):
  98. return _Element(0)
  99. mask1 = 2 ** 128
  100. v, z = f1, 0
  101. while f2:
  102. # if f2 ^ 1: z ^= v
  103. mask2 = int(bin(f2 & 1)[2:] * 128, base=2)
  104. z = (mask2 & (z ^ v)) | ((mask1 - mask2 - 1) & z)
  105. v <<= 1
  106. # if v & mask1: v ^= self.irr_poly
  107. mask3 = int(bin((v >> 128) & 1)[2:] * 128, base=2)
  108. v = (mask3 & (v ^ self.irr_poly)) | ((mask1 - mask3 - 1) & v)
  109. f2 >>= 1
  110. return _Element(z)
  111. def __add__(self, term):
  112. return _Element(self._value ^ term._value)
  113. def inverse(self):
  114. """Return the inverse of this element in GF(2^128)."""
  115. # We use the Extended GCD algorithm
  116. # http://en.wikipedia.org/wiki/Polynomial_greatest_common_divisor
  117. if self._value == 0:
  118. raise ValueError("Inversion of zero")
  119. r0, r1 = self._value, self.irr_poly
  120. s0, s1 = 1, 0
  121. while r1 > 0:
  122. q = _div_gf2(r0, r1)[0]
  123. r0, r1 = r1, r0 ^ _mult_gf2(q, r1)
  124. s0, s1 = s1, s0 ^ _mult_gf2(q, s1)
  125. return _Element(s0)
  126. def __pow__(self, exponent):
  127. result = _Element(self._value)
  128. for _ in range(exponent - 1):
  129. result = result * self
  130. return result
  131. class Shamir(object):
  132. """Shamir's secret sharing scheme.
  133. A secret is split into ``n`` shares, and it is sufficient to collect
  134. ``k`` of them to reconstruct the secret.
  135. """
  136. @staticmethod
  137. def split(k, n, secret, ssss=False):
  138. """Split a secret into ``n`` shares.
  139. The secret can be reconstructed later using just ``k`` shares
  140. out of the original ``n``.
  141. Each share must be kept confidential to the person it was
  142. assigned to.
  143. Each share is associated to an index (starting from 1).
  144. Args:
  145. k (integer):
  146. The number of shares needed to reconstruct the secret.
  147. n (integer):
  148. The number of shares to create (at least ``k``).
  149. secret (byte string):
  150. A byte string of 16 bytes (e.g. an AES 128 key).
  151. ssss (bool):
  152. If ``True``, the shares can be used with the ``ssss`` utility
  153. (without using the "diffusion layer").
  154. Default: ``False``.
  155. Return (tuples):
  156. ``n`` tuples, one per participant.
  157. A tuple contains two items:
  158. 1. the unique index (an integer)
  159. 2. the share (16 bytes)
  160. """
  161. #
  162. # We create a polynomial with random coefficients in GF(2^128):
  163. #
  164. # p(x) = c_0 + \sum_{i=1}^{k-1} c_i * x^i
  165. #
  166. # c_0 is the secret.
  167. #
  168. coeffs = [_Element(rng(16)) for i in range(k - 1)]
  169. coeffs.append(_Element(secret))
  170. # Each share is y_i = p(x_i) where x_i
  171. # is the index assigned to the share.
  172. def make_share(user, coeffs, ssss):
  173. idx = _Element(user)
  174. # Horner's method
  175. share = _Element(0)
  176. for coeff in coeffs:
  177. share = idx * share + coeff
  178. # The ssss utility actually uses:
  179. #
  180. # p(x) = c_0 + \sum_{i=1}^{k-1} c_i * x^i + x^k
  181. #
  182. if ssss:
  183. share += _Element(user) ** len(coeffs)
  184. return share.encode()
  185. return [(i, make_share(i, coeffs, ssss)) for i in range(1, n + 1)]
  186. @staticmethod
  187. def combine(shares, ssss=False):
  188. """Recombine a secret, if enough shares are presented.
  189. Args:
  190. shares (tuples):
  191. The *k* tuples, each containing the index (an integer) and
  192. the share (a byte string, 16 bytes long) that were assigned to
  193. a participant.
  194. .. note::
  195. Pass exactly as many share as they are required,
  196. and no more.
  197. ssss (bool):
  198. If ``True``, the shares were produced by the ``ssss`` utility
  199. (without using the "diffusion layer").
  200. Default: ``False``.
  201. Return:
  202. The original secret, as a byte string (16 bytes long).
  203. """
  204. #
  205. # Given k points (x,y), the interpolation polynomial of degree k-1 is:
  206. #
  207. # L(x) = \sum_{j=0}^{k-1} y_i * l_j(x)
  208. #
  209. # where:
  210. #
  211. # l_j(x) = \prod_{ \overset{0 \le m \le k-1}{m \ne j} }
  212. # \frac{x - x_m}{x_j - x_m}
  213. #
  214. # However, in this case we are purely interested in the constant
  215. # coefficient of L(x).
  216. #
  217. k = len(shares)
  218. gf_shares = []
  219. for x in shares:
  220. idx = _Element(x[0])
  221. value = _Element(x[1])
  222. if any(y[0] == idx for y in gf_shares):
  223. raise ValueError("Duplicate share")
  224. if ssss:
  225. value += idx ** k
  226. gf_shares.append((idx, value))
  227. result = _Element(0)
  228. for j in range(k):
  229. x_j, y_j = gf_shares[j]
  230. numerator = _Element(1)
  231. denominator = _Element(1)
  232. for m in range(k):
  233. x_m = gf_shares[m][0]
  234. if m != j:
  235. numerator *= x_m
  236. denominator *= x_j + x_m
  237. result += y_j * numerator * denominator.inverse()
  238. return result.encode()