blindfold module
Python library for working with encrypted data within nilDB queries and replies.
- class SecretKey[source]
Bases:
dictData structure for representing all categories of secret key instances.
- static generate(cluster: dict = None, operations: dict = None, threshold: Optional[int] = None, seed: Union[bytes, bytearray, str] = None) SecretKey[source]
Return a secret key built according to what is specified in the supplied cluster configuration, operation specification, and other parameters.
>>> sk = SecretKey.generate({'nodes': [{}]}, {'sum': True}) >>> isinstance(sk, SecretKey) True
Supplying an invalid combination of configurations and/or parameters raises a corresponding exception.
>>> SecretKey.generate({'nodes': [{}]}, {'sum': True}, threshold='abc') Traceback (most recent call last): ... TypeError: threshold must be an integer >>> SecretKey.generate({'nodes': [{}, {}]}, {'match': True}, threshold=1) Traceback (most recent call last): ... ValueError: thresholds are only supported for the sum operation >>> SecretKey.generate({'nodes': [{}]}, {'sum': True}, threshold=1) Traceback (most recent call last): ... ValueError: thresholds are only supported for multiple-node clusters >>> SecretKey.generate({'nodes': [{}]}, {'sum': True}, threshold=-1) Traceback (most recent call last): ... ValueError: threshold must a positive integer not larger than the cluster size >>> SecretKey.generate({'nodes': [{}, {}]}, {'sum': True}, threshold=3) Traceback (most recent call last): ... ValueError: threshold must a positive integer not larger than the cluster size
>>> SecretKey.generate({'nodes': [{}]}, {'sum': True}, seed=bytes([123])) Traceback (most recent call last): ... ValueError: seed-based derivation of summation-compatible keys is not supported for single-node clusters
- class ClusterKey[source]
Bases:
SecretKeyData structure for representing all categories of cluster key instances.
- static generate(cluster: dict = None, operations: dict = None, threshold: Optional[int] = None) ClusterKey[source]
Return a cluster key built according to what is specified in the supplied cluster configuration and operation specification.
>>> ck = ClusterKey.generate({'nodes': [{}, {}, {}]}, {'sum': True}) >>> isinstance(ck, ClusterKey) True
Cluster keys can only be created for clusters that have two or more nodes.
>>> ClusterKey.generate({'nodes': [{}]}, {'store': True}) Traceback (most recent call last): ... ValueError: cluster configuration must have at least two nodes
- dump() dict[source]
Return a JSON-compatible dictionary representation of this key instance.
>>> import json >>> cluster = {'nodes': [{}, {}, {}]} >>> ck = ClusterKey.generate(cluster, {'sum': True}, threshold=2) >>> isinstance(json.dumps(ck.dump()), str) True
- static load(dictionary: dict) ClusterKey[source]
Return an instance built from a JSON-compatible dictionary representation.
>>> cluster = {'nodes': [{}, {}, {}]} >>> ck = ClusterKey.generate(cluster, {'sum': True}, threshold=2) >>> ck == ClusterKey.load(ck.dump()) True
- class PublicKey[source]
Bases:
dictData structure for representing all categories of public key instances.
- static generate(secret_key: SecretKey) PublicKey[source]
Return a public key built according to what is specified in the supplied secret key.
>>> sk = SecretKey.generate({'nodes': [{}]}, {'sum': True}) >>> isinstance(PublicKey.generate(sk), PublicKey) True
- encrypt(key: Union[SecretKey, PublicKey], plaintext: Union[int, str, bytes]) Union[str, Sequence[str], Sequence[int], Sequence[Sequence[int]]][source]
Return the ciphertext obtained by using the supplied key to encrypt the supplied plaintext.
>>> key = SecretKey.generate({'nodes': [{}]}, {'store': True}) >>> isinstance(encrypt(key, 123), str) True
Invocations that involve invalid argument values or types may raise an exception.
>>> key = SecretKey.generate({'nodes': [{}]}, {'sum': True}) >>> encrypt(key, []) Traceback (most recent call last): ... TypeError: plaintext to encrypt for sum operation must be an integer >>> encrypt(key, 2 ** 64) Traceback (most recent call last): ... ValueError: numeric plaintext must be a valid 32-bit signed integer >>> del key['operations']['sum'] >>> encrypt(key, 123) Traceback (most recent call last): ... ValueError: cannot encrypt the supplied plaintext using the supplied key
- decrypt(key: SecretKey, ciphertext: Union[str, Sequence[str], Sequence[int], Sequence[Sequence[int]]]) Union[int, str, bytes][source]
Return the plaintext obtained by using the supplied key to decrypt the supplied ciphertext.
>>> key = SecretKey.generate({'nodes': [{}, {}]}, {'store': True}) >>> decrypt(key, encrypt(key, 123)) 123 >>> key = SecretKey.generate({'nodes': [{}, {}]}, {'store': True}) >>> decrypt(key, encrypt(key, -10)) -10 >>> key = SecretKey.generate({'nodes': [{}, {}]}, {'store': True}) >>> decrypt(key, encrypt(key, bytes([1, 2, 3]))) b'\x01\x02\x03' >>> key = SecretKey.generate({'nodes': [{}]}, {'store': True}) >>> decrypt(key, encrypt(key, 'abc')) 'abc' >>> key = SecretKey.generate({'nodes': [{}]}, {'store': True}) >>> decrypt(key, encrypt(key, 123)) 123 >>> key = SecretKey.generate({'nodes': [{}, {}]}, {'sum': True}) >>> decrypt(key, encrypt(key, 123)) 123 >>> key = SecretKey.generate({'nodes': [{}, {}]}, {'sum': True}) >>> decrypt(key, encrypt(key, -10)) -10 >>> key = SecretKey.generate({'nodes': [{}, {}]}, {'sum': True}, threshold=2) >>> decrypt(key, encrypt(key, 123)) 123 >>> key = SecretKey.generate({'nodes': [{}, {}, {}, {}]}, {'sum': True}, threshold=3) >>> decrypt(key, encrypt(key, 123)[:-1]) 123 >>> key = SecretKey.generate({'nodes': [{}, {}, {}, {}]}, {'sum': True}, threshold=2) >>> decrypt(key, encrypt(key, 123)[2:]) 123 >>> key = SecretKey.generate({'nodes': [{}, {}]}, {'sum': True}, threshold=1) >>> decrypt(key, encrypt(key, 123)[1:]) 123 >>> key = SecretKey.generate({'nodes': [{}, {}]}, {'sum': True}, threshold=2) >>> decrypt(key, encrypt(key, -10)) -10
An exception is raised if a ciphertext cannot be decrypted using the supplied key (e.g., because one or both are malformed or they are incompatible).
>>> key = SecretKey.generate({'nodes': [{}, {}]}, {'store': True}) >>> decrypt(key, 'abc') Traceback (most recent call last): ... ValueError: secret key requires a valid ciphertext from a multiple-node cluster >>> decrypt( ... SecretKey({'cluster': {'nodes': [{}]}, 'operations': {}}), ... 'abc' ... ) Traceback (most recent call last): ... ValueError: cannot decrypt the supplied ciphertext using the supplied key >>> key_alt = SecretKey.generate({'nodes': [{}, {}]}, {'store': True}) >>> decrypt(key_alt, encrypt(key, 123)) Traceback (most recent call last): ... ValueError: cannot decrypt the supplied ciphertext using the supplied key
- allot(document: Union[int, bool, str, list, dict]) Sequence[Union[int, bool, str, list, dict]][source]
Convert a document that may contain ciphertexts intended for multiple-node clusters into secret shares of that document. Shallow copies are created whenever possible.
>>> d = { ... 'id': 0, ... 'age': {'%allot': [1, 2, 3]}, ... 'dat': {'loc': {'%allot': [4, 5, 6]}} ... } >>> for d in allot(d): print(d) {'id': 0, 'age': {'%share': 1}, 'dat': {'loc': {'%share': 4}}} {'id': 0, 'age': {'%share': 2}, 'dat': {'loc': {'%share': 5}}} {'id': 0, 'age': {'%share': 3}, 'dat': {'loc': {'%share': 6}}}
A document with no ciphertexts intended for decentralized clusters is unmodofied; a list containing this document is returned.
>>> allot({'id': 0, 'age': 23}) [{'id': 0, 'age': 23}]
Any attempt to convert a document that has an incorrect structure raises an exception.
>>> allot({1, 2, 3}) Traceback (most recent call last): ... TypeError: boolean, integer, float, string, list, dictionary, or None expected >>> allot({'id': 0, 'age': {'%allot': [1, 2, 3], 'extra': [1, 2, 3]}}) Traceback (most recent call last): ... ValueError: allotment must only have one key >>> allot({ ... 'id': 0, ... 'age': {'%allot': [1, 2, 3]}, ... 'dat': {'loc': {'%allot': [4, 5]}} ... }) Traceback (most recent call last): ... ValueError: number of shares in subdocument is not consistent >>> allot([ ... 0, ... {'%allot': [1, 2, 3]}, ... {'loc': {'%allot': [4, 5]}} ... ]) Traceback (most recent call last): ... ValueError: number of shares in subdocument is not consistent
- unify(secret_key: SecretKey, documents: Sequence[Union[int, bool, str, list, dict]], ignore: Sequence[str] = None) Union[int, bool, str, list, dict][source]
Convert an object that may contain ciphertexts intended for multiple-node clusters into secret shares of that object. Shallow copies are created whenever possible.
>>> data = { ... 'a': [True, 'v', 12], ... 'b': [False, 'w', 34], ... 'c': [True, 'x', 56], ... 'd': [False, 'y', 78], ... 'e': [True, 'z', 90], ... } >>> sk = SecretKey.generate({'nodes': [{}, {}, {}]}, {'store': True}) >>> encrypted = { ... 'a': [True, 'v', {'%allot': encrypt(sk, 12)}], ... 'b': [False, 'w', {'%allot': encrypt(sk, 34)}], ... 'c': [True, 'x', {'%allot': encrypt(sk, 56)}], ... 'd': [False, 'y', {'%allot': encrypt(sk, 78)}], ... 'e': [True, 'z', {'%allot': encrypt(sk, 90)}], ... } >>> shares = allot(encrypted) >>> decrypted = unify(sk, shares) >>> data == decrypted True
It is possible to wrap nested lists of shares to reduce the overhead associated with the
{'%allot': ...}and{'%share': ...}wrappers.>>> data = { ... 'a': [1, [2, 3]], ... 'b': [4, 5, 6], ... 'c': None, ... 'd': 1.23 ... } >>> sk = SecretKey.generate({'nodes': [{}, {}, {}]}, {'store': True}) >>> encrypted = { ... 'a': {'%allot': [encrypt(sk, 1), [encrypt(sk, 2), encrypt(sk, 3)]]}, ... 'b': {'%allot': [encrypt(sk, 4), encrypt(sk, 5), encrypt(sk, 6)]}, ... 'c': None, ... 'd': 1.23 ... } >>> shares = allot(encrypted) >>> decrypted = unify(sk, shares) >>> data == decrypted True
The
ignoreparameter specifies which keys should be ignored during unification. By default,'_created'and'_updated'are ignored.>>> shares[0]['_created'] = '123' >>> shares[1]['_created'] = '456' >>> shares[2]['_created'] = '789' >>> shares[0]['_updated'] = 'ABC' >>> shares[1]['_updated'] = 'DEF' >>> shares[2]['_updated'] = 'GHI' >>> decrypted = unify(sk, shares) >>> data == decrypted True
Unification returns the sole document when a one-document list is supplied.
>>> 123 == unify(sk, [123]) True
Any attempt to supply incompatible document shares raises an exception.
>>> unify(sk, [123, 'abc']) Traceback (most recent call last): ... TypeError: array of compatible document shares expected