The sphinxmix package documentation

This documentation relates to the sphinxmix package version 0.0.7.

Installing

Install using pip through the command:

$ pip install sphinxmix

Basic usage

The sphinxmix package implements the Sphinx mix packet format core cryptographic functions.

The paper describing sphinx may be found here:

  • George Danezis and Ian Goldberg. Sphinx: A Compact and Provably Secure Mix Format. IEEE Symposium on Security and Privacy 2009. [link]

All the sphinxmix cryptography is encapsulated and within a SphinxParams object that is used by all subsequent functions. To make sphinxmix use different cryptographic primitives simply extend this class, or re-implement it. The default cryptographic primitives are NIST/SEGS-p224 curves, AES and SHA256.

Sending Sphinx messages

To package or process sphinx messages create a new SphinxParams object:

>>> # Instantiate a the crypto parameters for Sphinx.
>>> from sphinxmix.SphinxParams import SphinxParams
>>> params = SphinxParams()

The sphinxmix package requires some rudimentary Public Key Information: mix nodes need an identifier created by Nenc and the PKI consists of a dictionary mapping node names to pki_entry records. Those include secret keys (derived using gensecret) and public keys (derived using expon).

>>> # The minimal PKI involves names of nodes and keys
>>> from sphinxmix.SphinxClient import pki_entry, Nenc
>>> pkiPriv = {}
>>> pkiPub = {}
>>> for i in range(10):
...     nid = i
...     x = params.group.gensecret()
...     y = params.group.expon(params.group.g, [ x ])
...     pkiPriv[nid] = pki_entry(nid, x, y)
...     pkiPub[nid] = pki_entry(nid, None, y)

A client may package a message using the Sphinx format to relay over a number of mix servers. The function rand_subset may be used to select a random number of node identifiers; the function create_forward_message can then be used to package the message, ready to be sent to the first mix. Note both destination and message need to be bytes.

>>> # The simplest path selection algorithm and message packaging
>>> from sphinxmix.SphinxClient import rand_subset, \
...                                    create_forward_message
>>> use_nodes = rand_subset(pkiPub.keys(), 5)
>>> nodes_routing = list(map(Nenc, use_nodes))
>>> keys_nodes = [pkiPub[n].y for n in use_nodes]
>>> dest = b"bob"
>>> message = b"this is a test"
>>> header, delta = create_forward_message(params, nodes_routing, \
...     keys_nodes, dest, message)

The client may specify any information in the nodes_routing list, that will be passed to intermediate mixes. At a minimum this should include information about the next mix.

Processing Sphinx messages at a mix

The heart of a Sphinx mix server is the sphinx_process function, that takes the server secret and decodes incoming messages. In this example the message encode above, is decoded by the sequence of mixes.

>>> # Process message by the sequence of mixes
>>> from sphinxmix.SphinxClient import PFdecode, Relay_flag, Dest_flag, Surb_flag, receive_forward
>>> from sphinxmix.SphinxNode import sphinx_process
>>> x = pkiPriv[use_nodes[0]].x
>>> while True:
...     ret = sphinx_process(params, x, header, delta)
...     (tag, info, (header, delta), mac_key) = ret
...     routing = PFdecode(params, info)
...     if routing[0] == Relay_flag:
...         flag, addr = routing
...         x = pkiPriv[addr].x 
...     elif routing[0] == Dest_flag:
...         assert receive_forward(params, mac_key, delta) == [dest, message]
...         break

It is the responsibility of a mix to record tags of messages to prevent replay attacks. The PFdecode function may be used to recover routing Information including the next mix, or any other user specified information.

Single use reply Blocks

A facility provided by Sphinx is the creation and use of Single Use Reply Blocks (SURB) to route messages back to an anonymous receipient. First a receiver needs to create a SURB using create_surb and passes on the nymtuple structure to the sender, and storing surbkeytuple keyed by the identifier surbid:

>>> from sphinxmix.SphinxClient import create_surb, package_surb
>>> surbid, surbkeytuple, nymtuple = create_surb(params, nodes_routing, keys_nodes, b"myself")

Using the nymtuple a sender can package a message to be sent through the network, starting at the nymtuple[0] router:

>>> message = b"This is a reply"
>>> header, delta = package_surb(params, nymtuple, message)

The network processes the SURB as any other message, until it is received by the last mix in the path:

>>> x = pkiPriv[use_nodes[0]].x
>>> while True:
...    ret = sphinx_process(params, x, header, delta)
...    (tag, B, (header, delta), mac_key) = ret
...    routing = PFdecode(params, B)
...
...    if routing[0] == Relay_flag:
...        flag, addr = routing
...        x = pkiPriv[addr].x 
...    elif routing[0] == Surb_flag:
...        flag, dest, myid = routing
...        break

The final mix server must sent the myid and delta to the destination dest, where it may be decoded using the surbkeytuple.

>>> from sphinxmix.SphinxClient import receive_surb
>>> received = receive_surb(params, surbkeytuple, delta)
>>> assert received == message

Embedding arbitrary information for mixes

A sender may embed arbitrary information to mix nodes, as demonstrated by embedding b'info' to each mix, and b'final_info' to the final mix:

>>> use_nodes = rand_subset(pkiPub.keys(), 5)
>>> nodes_routing = [Nenc((n, b'info')) for n in use_nodes]
>>> keys_nodes = [pkiPub[n].y for n in use_nodes]
>>> dest = (b"bob", b"final_info")
>>> message = b"this is a test"
>>> header, delta = create_forward_message(params, nodes_routing, \
...     keys_nodes, dest, message)

Mixes decode the arbitrary structure passed by the clients, and can interpret it to implement more complex mixing strategies:

>>> x = pkiPriv[use_nodes[0]].x
>>> while True:
...     ret = sphinx_process(params, x, header, delta)
...     (tag, info, (header, delta), mac_key) = ret
...     routing = PFdecode(params, info)
...     if routing[0] == Relay_flag:
...         flag, (addr, additional_info) = routing
...         assert additional_info == b'info'
...         x = pkiPriv[addr].x 
...     elif routing[0] == Dest_flag:
...         [[dest, additional_info], msg] = receive_forward(params, mac_key, delta)
...         assert additional_info == b'final_info'
...         assert dest == b'bob'
...         break

Packaging mix messages to byte strings:

The sphinxmix package provides functions pack_message and unpack_message to serialize and deserialize mix messages using msgpack. Some meta-data about the parameter length are passed along the message any may be used to select an appropriate parameter environment for the decoding of the message.

>>> from sphinxmix.SphinxClient import pack_message, unpack_message
>>> bin_message = pack_message(params, (header, delta))
>>> param_dict = { (params.max_len, params.m):params }
>>> px, (header1, delta1) = unpack_message(param_dict, bin_message)

Development

The git repository for sphinxmix can be cloned from here: https://github.com/UCL-InfoSec/sphinx

The pytest unit tests and doctests of sphinxmix may be ran using tox simply through the command:

$ tox

To upload a new distribution of sphinxmix the maintainer simply uses:

$ python setup.py sdist upload

Core classes and functions

class sphinxmix.SphinxParams.SphinxParams(group=None, header_len=192, body_len=1024, assoc_len=0, k=16, dest_len=16)
class sphinxmix.SphinxParams.Group_ECC(gid=713)

Group operations in ECC

class sphinxmix.SphinxParamsC25519.Group_C25519

Group operations using Curve 25519

Utility functions

sphinxmix.SphinxClient.pki_entry(id, x, y)

A helper named tuple to store PKI information.

sphinxmix.SphinxClient.Nenc(idnum)

The encoding of mix names.

sphinxmix.SphinxClient.Relay_flag = '\xf0'

Routing flag indicating message is to be relayed.

sphinxmix.SphinxClient.Dest_flag = '\xf1'

Routing flag indicating message is to be delivered.

sphinxmix.SphinxClient.Surb_flag = '\xf2'

Routing flag indicating surb reply is to be delivered.

sphinxmix.SphinxClient.PFdecode(param, packed)

Decoder of prefix free encoder for commands received by mix or clients.

sphinxmix.SphinxClient.rand_subset(lst, nu)

Return a list of nu random elements of the given list (without replacement).

Client functions

sphinxmix.SphinxClient.create_forward_message(params, nodelist, keys, dest, msg, assoc=None)

Creates a forward Sphix message, ready to be processed by a first mix.

It takes as parameters a node list of mix information, that will be provided to each mix, forming the path of the message; a list of public keys of all intermediate mixes; a destination and a message; and optinally an array of associated data (byte arrays).

sphinxmix.SphinxClient.receive_forward(params, mac_key, delta)

Decodes the body of a forward message, and checks its MAC tag.

Packaging messages

sphinxmix.SphinxClient.pack_message(params, m)

A method to pack mix messages.

sphinxmix.SphinxClient.unpack_message(params_dict, m)

A method to unpack mix messages.

Mix functions

sphinxmix.SphinxNode.sphinx_process(params, secret, header, delta, assoc='')

The heart of a Sphinx server, that processes incoming messages. It takes a set of parameters, the secret of the server, and an incoming message header and body. Optinally some Associated data may also be passed in to check their integrity.

SURB functions

sphinxmix.SphinxClient.create_surb(params, nodelist, keys, dest, assoc=None)

Creates a Sphinx single use reply block (SURB) using a set of parameters; a sequence of mix identifiers; a pki mapping names of mixes to keys; and a final destination. An array of associated data, for each mix on the path, may optionally be passed in.

Returns:
  • A triplet (surbid, surbkeytuple, nymtuple). Where the surbid can be used as an index to store the secrets surbkeytuple; nymtuple is the actual SURB that needs to be sent to the receiver.
sphinxmix.SphinxClient.package_surb(params, nymtuple, message)

Packages a message to be sent with a SURB. The message has to be bytes, and the nymtuple is the structure returned by the create_surb call.

Returns a header and a body to pass to the first mix.

sphinxmix.SphinxClient.receive_surb(params, keytuple, delta)

Processes a SURB body to extract the reply. The keytuple was provided at the time of SURB creation, and can be indexed by the SURB id, which is also returned to the receiving user.

Returns the decoded message.

Ultrix Format

Mix functions

sphinxmix.UltrixNode.ultrix_process(params, secret, header, delta, assoc='')

The heart of a Ultrix server, that processes incoming messages. It takes a set of parameters, the secret of the server, and an incoming message header and body. Optinally some Associated data may also be passed in to check their integrity.

Client functions

sphinxmix.UltrixClient.create_forward_message(params, nodelist, keys, dest, msg, assoc=None)

Creates a forward Sphix message, ready to be processed by a first mix.

It takes as parameters a node list of mix information, that will be provided to each mix, forming the path of the message; a list of public keys of all intermediate mixes; a destination and a message; and optinally an array of associated data (byte arrays).

sphinxmix.UltrixClient.receive_forward(params, header, mac_key, routing, delta)

Decodes the body of a forward message, and checks its MAC tag.

SURB functions

sphinxmix.UltrixClient.create_surb(params, nodelist, keys, dest, assoc=None)

Creates a Ultrix single use reply block (SURB) using a set of parameters; a sequence of mix identifiers; a pki mapping names of mixes to keys; and a final destination. An array of associated data, for each mix on the path, may optionally be passed in.

Returns:
  • A triplet (surbid, surbkeytuple, nymtuple). Where the surbid can be used as an index to store the secrets surbkeytuple; nymtuple is the actual SURB that needs to be sent to the receiver.
sphinxmix.UltrixClient.package_surb(params, nymtuple, message)

Packages a message to be sent with a SURB. The message has to be bytes, and the nymtuple is the structure returned by the create_surb call.

Returns a header and a body to pass to the first mix.

sphinxmix.UltrixClient.decode_surb(params, header, enc_dest)

Decode the destination address of the SURB.

sphinxmix.UltrixClient.receive_surb(params, keytuple, delta)

Processes a SURB body to extract the reply. The keytuple was provided at the time of SURB creation, and can be indexed by the SURB id, which is also returned to the receiving user.

Returns the decoded message.

Indices and tables