Files
@ 7c94e9b021f6
Branch filter:
Location: Shamira/src/shamira.py - annotation
7c94e9b021f6
4.1 KiB
text/x-python
prefixing shares with labels
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | 8968eab714bc 8968eab714bc 438dcebc9c63 9ccd379021d5 9ccd379021d5 90fb179128d4 438dcebc9c63 438dcebc9c63 438dcebc9c63 438dcebc9c63 b52e197db5a8 b52e197db5a8 b52e197db5a8 b52e197db5a8 b52e197db5a8 b52e197db5a8 b52e197db5a8 37a1df17b9a1 735b9c2a61e9 cc4182acd584 ccb4a27318f1 37a1df17b9a1 cc4182acd584 438dcebc9c63 438dcebc9c63 438dcebc9c63 37a1df17b9a1 3907396d80a5 3907396d80a5 3907396d80a5 3907396d80a5 3907396d80a5 3907396d80a5 37a1df17b9a1 9ccd379021d5 438dcebc9c63 438dcebc9c63 37a1df17b9a1 3907396d80a5 3907396d80a5 7c94e9b021f6 7c94e9b021f6 37a1df17b9a1 37a1df17b9a1 37a1df17b9a1 cc4182acd584 37a1df17b9a1 db65075fe7e0 9ccd379021d5 9ccd379021d5 7c94e9b021f6 37a1df17b9a1 3907396d80a5 3907396d80a5 3907396d80a5 3907396d80a5 3907396d80a5 7c94e9b021f6 7c94e9b021f6 3907396d80a5 9ccd379021d5 cc4182acd584 37a1df17b9a1 7c94e9b021f6 7c94e9b021f6 7c94e9b021f6 7c94e9b021f6 7c94e9b021f6 7c94e9b021f6 7c94e9b021f6 7c94e9b021f6 9ccd379021d5 9ccd379021d5 cc4182acd584 37a1df17b9a1 3907396d80a5 3907396d80a5 3907396d80a5 3907396d80a5 3907396d80a5 9ccd379021d5 37a1df17b9a1 9ccd379021d5 37a1df17b9a1 b52e197db5a8 b52e197db5a8 b52e197db5a8 b52e197db5a8 9ccd379021d5 9ccd379021d5 cc4182acd584 cc4182acd584 cc4182acd584 cc4182acd584 cc4182acd584 cc4182acd584 9ccd379021d5 9ccd379021d5 cc4182acd584 90fb179128d4 7c94e9b021f6 cc4182acd584 90fb179128d4 b52e197db5a8 cc4182acd584 cc4182acd584 cc4182acd584 37a1df17b9a1 37a1df17b9a1 cc4182acd584 cc4182acd584 9ccd379021d5 9ccd379021d5 37a1df17b9a1 cc4182acd584 7c94e9b021f6 7c94e9b021f6 7c94e9b021f6 9ccd379021d5 9ccd379021d5 9ccd379021d5 9ccd379021d5 b52e197db5a8 9ccd379021d5 9ccd379021d5 9ccd379021d5 9c496886dde9 9c496886dde9 | # GNU GPLv3, see LICENSE
import os
import re
import base64
import binascii
import gf256
class SException(Exception): pass
class InvalidParams(SException): pass
class DetectionException(SException): pass
class DecodingException(SException): pass
class MalformedShare(SException): pass
def _share_byte(secret_b, k, n):
if not k<=n<255:
raise InvalidParams("Failed k<=n<255, k={0}, n={1}".format(k, n))
# we might be concerned with zero coefficients degenerating our polynomial, but there's no reason - we still need k shares to determine it is the case
coefs = [int(secret_b)]+[int(b) for b in os.urandom(k-1)]
points = [gf256.evaluate(coefs, i) for i in range(1, n+1)]
return points
def generate_raw(secret, k, n):
"""Splits secret into shares.
:param secret: (bytes)
:param k: number of shares necessary for secret recovery. 1 <= k <= n
:param n: (int) number of shares generated. 1 <= n < 255
:return: [(i, (bytes) share), ...]"""
shares = [_share_byte(b, k, n) for b in secret]
return [(i+1, bytes([s[i] for s in shares])) for i in range(n)]
def reconstruct_raw(*shares):
"""Tries to recover the secret from its shares.
:param shares: (((int) i, (bytes) share), ...)
:return: (bytes) reconstructed secret. Too few shares return garbage."""
secret_len = len(shares[0][1])
res = [None]*secret_len
for i in range(secret_len):
points = [(x, s[i]) for (x, s) in shares]
res[i] = (gf256.get_constant_coef(*points))
return bytes(res)
def generate(secret, k, n, encoding="b32", label="", omit_k_n=False):
"""Wraps generate_raw().
:param secret: (str or bytes)
:param k: number of shares necessary for secret recovery
:param n: number of shares generated
:param encoding: {hex, b32, b64} desired output encoding. Hexadecimal, Base32 or Base64.
:param label: (str) any label to prefix the shares with
:param omit_k_n: (boolean) suppress the default shares prefix
:return: [(str) share, ...]"""
if isinstance(secret,str):
secret = secret.encode("utf-8")
shares = generate_raw(secret, k, n)
prefix = ""
if label:
prefix = label + "."
if not omit_k_n:
prefix += "{0}.{1}.".format(k, n)
return [prefix + encode(s, encoding) for s in shares]
def reconstruct(*shares, encoding="", raw=False):
"""Wraps reconstruct_raw.
:param shares: ((str) share, ...)
:param encoding: {hex, b32, b64, ""} encoding of share strings. If not provided or empty, the function tries to guess it.
:param raw: (bool) whether to return bytes (True) or str (False)
:return: (str or bytes) reconstructed secret. Too few shares returns garbage."""
if not encoding:
encoding = detect_encoding(shares)
bs = reconstruct_raw(*(decode(s, encoding) for s in shares))
try:
return bs if raw else bs.decode(encoding="utf-8")
except UnicodeDecodeError:
raise DecodingException('Failed to decode bytes to utf-8. Either you supplied invalid shares, or you missed the "raw" flag. Offending value: {0}'.format(bs))
def encode(share, encoding="b32"):
if encoding=="hex": f = base64.b16encode
elif encoding=="b32": f = base64.b32encode
else: f = base64.b64encode
(i, bs) = share
return "{0}.{1}".format(i, f(bs).decode("utf-8"))
def decode(share, encoding="b32"):
try:
(*_, i, share_str) = share.split(".")
i = int(i)
if not 1<=i<=255:
raise MalformedShare("Malformed share: Failed 1<=k<=255, k={0}".format(i))
if encoding=="hex": f = base64.b16decode
elif encoding=="b32": f = base64.b32decode
else: f = base64.b64decode
share_bytes = f(share_str)
return (i, share_bytes)
except (ValueError, binascii.Error):
raise MalformedShare('Malformed share: share="{0}", encoding="{1}"'.format(share, encoding))
def detect_encoding(shares):
classes = [
(re.compile(r"(.*\.)?\d+\.([0-9A-F]{2})+"), "hex"),
(re.compile(r"(.*\.)?\d+\.([A-Z2-7]{8})*([A-Z2-7]{8}|[A-Z2-7]{2}={6}|[A-Z2-7]{4}={4}|[A-Z2-7]{5}={3}|[A-Z2-7]{7}={1})"), "b32"),
(re.compile(r"(.*\.)?\d+\.([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{2}={2}|[A-Za-z0-9+/]{3}={1})"), "b64")
]
for (regexp, res) in classes:
if all(regexp.fullmatch(share) for share in shares):
return res
raise DetectionException("No expected encoding detected")
if __name__=="__main__":
import cli
cli.run()
|