def recombine_secret_from_samir_shares(shares: list) -> bytes: """Permits to reconstruct a key which has its secret shared into `shares_count` shares thanks to a list of `shares` :param shares: list of k full-length shares (k being exactly the threshold of this shared secret) :return: the key reconstructed as bytes""" shares_per_secret = [] # List of lists of same-index 16-bytes shares assert len(set(share[0] for share in shares)) == len( shares) # All shares have unique idx for share in shares: idx, secret = share chunks = split_as_chunks(secret, chunk_size=16, must_pad=False) shares_per_secret.append([(idx, chunk) for chunk in chunks]) assert (len(set(len(chunks) for chunks in shares_per_secret)) == 1 ) # Same-length lists all_chunk_shares = list(zip(*shares_per_secret)) chunks = [] for chunk_shares in all_chunk_shares: chunk = _recombine_128b_shares_into_bytestring(chunk_shares) chunks.append(chunk) secret = recombine_chunks(chunks, chunk_size=SHAMIR_CHUNK_LENGTH, must_unpad=True) return secret
def test_split_as_chunks_and_recombine(): bytestring = get_random_bytes(100) chunks = split_as_chunks(bytestring, chunk_size=25, must_pad=True) assert all(len(x) == 25 for x in chunks) result = recombine_chunks(chunks, chunk_size=25, must_unpad=True) assert result == bytestring chunks = split_as_chunks(bytestring, chunk_size=22, must_pad=True) assert all(len(x) == 22 for x in chunks) result = recombine_chunks(chunks, chunk_size=22, must_unpad=True) assert result == bytestring chunks = split_as_chunks(bytestring, chunk_size=25, must_pad=False) assert all(len(x) == 25 for x in chunks) result = recombine_chunks(chunks, chunk_size=25, must_unpad=False) assert result == bytestring with pytest.raises(ValueError, match="size multiple of chunk_size"): split_as_chunks(bytestring, chunk_size=22, must_pad=False) chunks = split_as_chunks( bytestring, chunk_size=22, must_pad=False, accept_incomplete_chunk=True ) assert not all(len(x) == 22 for x in chunks) result = recombine_chunks(chunks, chunk_size=22, must_unpad=False) assert result == bytestring
def split_bytestring_as_shamir_shares(secret: bytes, *, shares_count: int, threshold_count: int) -> list: """Generate a shared secret of `shares_count` subkeys, with `threshold_count` of them required to recompute the initial `bytestring`. :param secret: bytestring to separate as shares, whatever its length :param shares_count: the number of shares to be created for the secret :param threshold_count: the minimal number of shares needed to recombine the key :return: list of full bytestring shares""" if not shares_count: raise ValueError("Shares count must be strictly positive") if threshold_count > shares_count: raise ValueError( "Threshold count %s can't be higher than shared count %s" % (threshold_count, shares_count)) all_chunk_shares = [] # List of lists of related 16-bytes shares # Split the secret into tuples of 16 bytes exactly (after padding) chunks = split_as_chunks(secret, chunk_size=SHAMIR_CHUNK_LENGTH, must_pad=True) # Separate each chunk into share for chunk in chunks: shares = _split_128b_bytestring_into_shares(chunk, shares_count, threshold_count) all_chunk_shares.append(shares) del shares full_shares = [] for idx in range(shares_count): assert all( chunk_share[idx][0] == idx + 1 for chunk_share in all_chunk_shares) # By construction, share indices start at 1 idx_shares = (chunk_share[idx][1] for chunk_share in all_chunk_shares) complete_share = b"".join(idx_shares) full_shares.append((idx + 1, complete_share)) return full_shares
def _encrypt_via_rsa_oaep(plaintext: bytes, key: RSA.RsaKey) -> dict: """Encrypt a bytestring with PKCS#1 RSA OAEP (asymmetric algo). :param plaintext: the bytes to cipher :param key: public RSA key :return: a dict with field `digest_list`, containing bytestring chunks of variable width.""" _check_asymmetric_key_length_bits(key.size_in_bits()) cipher = PKCS1_OAEP.new(key=key, hashAlgo=RSA_OAEP_HASHER) chunks = split_as_chunks(plaintext, chunk_size=RSA_OAEP_CHUNKS_SIZE, must_pad=False, accept_incomplete_chunk=True) encrypted_chunks = [] for chunk in chunks: encrypted_chunk = cipher.encrypt(chunk) encrypted_chunks.append(encrypted_chunk) return dict(digest_list=encrypted_chunks)