oprfs module

Easy-to-deploy oblivious pseudo-random function (OPRF) service that allows other parties (typically participants in some secure multi-party computation protocol) to maintain a persistent mask which they cannot decrypt but which they can safely apply (via requests to the service) to private data values of their choice.

This module includes an OPRF service request handler (that serves as an endpoint for the service and can be used in conjunction with libraries such as Flask) and client request construction class (to help clients build requests concisely).

oprfs.oprfs.key() bcl.bcl.secret[source]

Create a secret key to be maintained by the service.

>>> len(key())
32
>>> isinstance(key(), bcl.secret)
True
oprfs.oprfs.key_base64() str[source]

Create a secret key to be maintained by the service and return its Base64 UTF-8 string representation.

>>> len(base64.standard_b64decode(key_base64()))
32
oprfs.oprfs.mask(k: bcl.bcl.secret, m: Optional[bcl.bcl.cipher] = None, d: Optional[oprf.oprf.data] = None) Union[bcl.bcl.cipher, oprf.oprf.data][source]

Function implementing a masking service. If only a secret key is supplied, this function creates a mask object, encrypts it using the supplied secret key, and returns the resulting cipher object. If an encrypted mask object and a data object are also supplied, it decrypts the supplied cipher object into a mask object, applies it to the data object, and returns the result.

>>> k = key()
>>> m = mask(k)

The two objects k and m can now be used to mask data.

>>> d = oprf.data.hash('abc')
>>> mask(k, m, d) == oprf.mask(bcl.symmetric.decrypt(k, m))(d)
True

If an encrypted mask object is supplied, a data object must also be supplied.

>>> mask(k, m)
Traceback (most recent call last):
  ...
ValueError: data to be masked must be supplied
oprfs.oprfs.handler(k: bcl.bcl.secret, request: Union[str, dict]) dict[source]

Wrapper for service function that accepts inputs as a JSON string or a Python dict instance (e.g., for use within a route defined using the Flask library).

It is possible to request a new encrypted mask. Note that an empty request must be supplied to the handler.

>>> k = key()
>>> r = handler(k, {})
>>> r['status']
'success'
>>> r = handler(k, '{}')
>>> r['status']
'success'

The encrypted mask can be used to mask data. Note that it is the responsibility of the service implementation to maintain and supply the secret key to the handler.

>>> m = oprf.mask.from_base64(r['mask'][0])
>>> d = oprf.data.hash('abc')
>>> r = handler(k, {'mask': [m.to_base64()], 'data': [d.to_base64()]})
>>> r['status']
'success'

The example below reproduces the example above, but submits the request to the handler as a string.

>>> (m_str, d_str) = (str(m.to_base64()), str(d.to_base64()))
>>> s = '{"mask": ["' + m_str + '"], "data": ["' + d_str + '"]}'
>>> r = handler(k, s)
>>> r['status']
'success'

The example below confirms that the response contains the masked data.

>>> oprf.data.from_base64(r['data'][0]) == (
...     oprf.mask(bcl.symmetric.decrypt(k, bcl.cipher(m)))(d)
... )
True

If the supplied request is not valid (e.g., if the data is missing), then the returned response indicates failure.

>>> r = handler(k, {'mask': [m.to_base64()]})
>>> r['status']
'failure'