Welcome to petlib’s documentation!

https://travis-ci.org/gdanezis/petlib.svg?branch=master Documentation Status

Pre-requisites

On Ubuntu / debian use apt-get to install package libssl-dev. Ensure you also install libffi-dev and python-dev:

sudo apt-get install python-dev
sudo apt-get install libssl-dev
sudo apt-get install libffi-dev

On MacOS, install OpenSSL 1.1.x using homebrew:

brew install openssl@1.1

On Windows, install 32 bit or 64 bit OpenSSL binary edition matching your Python installation. Ensure libeay32.dll is on the system PATH (https://www.openssl.org/related/binaries.html). Configure the path variables of Microsoft VS compilers for 32 bit or 64 bit architectures, by executing the command vcvars32.bat or vcvarsx86_amd64.bat.

Quick install

If you have pip installed the following command should install petlib:

pip install petlib

Test your installation:

python -c "import petlib; petlib.run_tests()"

Testing and Packaging

You will need a working Python 2.7 and 3.6 environemnt with pytest:

sudo apt-get install python-pytest
sudo apt-get install python3-pytest
sudo apt-get install python-sphinx
sudo pip install Mock

To build the distribution, create a venv for tests and run all tests (including the examples):

paver
Specific paver targets include:
  • unit_tests: runs the unit tests.
  • build: builds a distribution bundle in the dist directory.
  • make_docs: builds the html documentation in docs/_build/html
  • make_env: initialized a virtualenv with a fresh petlib in folder test_env/pltest.
  • big_test: runs all the examples in a virtual environment.
  • test: runs all tests.

Under the hood. Petlib uses py.test for managing and running unit tests, and the pytest-cov module for test coverage. For running all tests and generating a code coverage report run:

py.test --doctest-modules --cov petlib petlib/*.py

To generate an HTML report of lines not covered by tests run:

py.test --doctest-modules --cov-report html --cov petlib petlib/*.py

To build the Sphinx HTML documentation go into docs and run:

make html

To build the source distribution run (and add upload if you wish to upload it to pypi):

python setup.py sdist

petlib Introduction & Examples

The petlib library is designed as a one-stop-shop library to prototype advanced Privacy Enhancing Technologies (PETs) in Python. It abstracts a number of OpenSSL APIs supporting operations on big numbers (bn), elliptic curves (ec), ciphers such as AES in a variety of modes of operation (CTM, GCM, …) (ciphers), message authentication codes (hmac) and ECDSA signatures (ecdsa).

Besides petlib Python offers a number of modules in the standard library that are necessary to support modern cryptography:

  • The hashlib module offers cryptographic hash functions such as sha256, and sha512.
  • The os module offers the urandom cryptographically strong random number generator.
  • The hmac module offers keyed hash-based message authentication codes as well as facilities for constant time comparison.
Examples of use of petlib for implementing historical as well as state of the art PETs include:

petlib Modules

Module petlib.bn

class petlib.bn.Bn(num=0)[source]

The core Big Number class. It supports all comparisons (<, <=, ==, !=, >=, >), arithmetic operations (+, -, %, /, divmod, pow) and copy operations (copy and deep copy). The right-hand side operand may be a small native python integer (<2^64).

static from_decimal(sdec)[source]

Creates a Big Number from a decimal string.

Args:
sdec (string): numeric string possibly starting with minus.
See Also:
str() produces a decimal string from a big number.
Example:
>>> hundred = Bn.from_decimal("100")
>>> str(hundred)
'100'
static from_hex(shex)[source]

Creates a Big Number from a hexadecimal string.

Args:
shex (string): hex (0-F) string possibly starting with minus.
See Also:
hex() produces a hexadecimal representation of a big number.
Example:
>>> Bn.from_hex("FF")
255
static from_binary(sbin)[source]

Creates a Big Number from a byte sequence representing the number in Big-endian 8 byte atoms. Only positive values can be represented as byte sequence, and the library user should store the sign bit separately.

Args:
sbin (string): a byte sequence.
Example:
>>> byte_seq = unhexlify(b"010203")
>>> Bn.from_binary(byte_seq)
66051
>>> (1 * 256**2) + (2 * 256) + 3
66051
static get_prime(bits, safe=1)[source]

Builds a prime Big Number of length bits.

Args:
bits (int) – the number of bits. safe (int) – 1 for a safe prime, otherwise 0.
copy()[source]

Returns a copy of the Bn object.

bool()[source]

Turn Bn into boolean. False if zero, True otherwise.

repr()[source]

The representation of the number as a decimal string

int()[source]

A native python integer representation of the Big Number. Synonym for int(bn).

hex()[source]

The representation of the string in hexadecimal. Synonym for hex(n).

binary()[source]

Returns a byte sequence storing the absolute value of the Big Number in Big-Endian format (with 8 bit atoms). You need to extact the sign separately.

Example:
>>> bin = Bn(66051).binary()
>>> hexlify(bin) == b'010203'
True
random()[source]

Returns a cryptographically strong random number 0 <= rnd < self.

Example:
>>> r = Bn(100).random()
>>> 0 <= r < 100
True
int_neg()[source]

Returns the negative of this number. Synonym with -self.

Example:

>>> one100 = Bn(100)
>>> one100.int_neg()
-100
>>> -one100
-100
int_add(other)[source]

Returns the sum of this number with another. Synonym for self + other.

Example:

>>> one100 = Bn(100)
>>> two100 = Bn(200)
>>> two100.int_add(one100) # Function syntax
300
>>> two100 + one100        # Operator syntax
300
int_sub(other)[source]

Returns the difference between this number and another. Synonym for self - other.

Example:

>>> one100 = Bn(100)
>>> two100 = Bn(200)
>>> two100.int_sub(one100) # Function syntax
100
>>> two100 - one100        # Operator syntax
100
int_mul(other)[source]

Returns the product of this number with another. Synonym for self * other.

Example:

>>> one100 = Bn(100)
>>> two100 = Bn(200)
>>> one100.int_mul(two100) # Function syntax
20000
>>> one100 * two100        # Operator syntax
20000
mod_add(other, m)[source]

Returns the sum of self and other modulo m.

Example:

>>> Bn(10).mod_add(Bn(2), Bn(11))  # Only function notation available
1
mod_sub(other, m)[source]

Returns the difference of self and other modulo m.

Example:

>>> Bn(10).mod_sub(Bn(2), Bn(11))  # Only function notation available
8
mod_mul(other, m)[source]

Return the product of self and other modulo m.

Example:

>>> Bn(10).mod_mul(Bn(2), Bn(11))  # Only function notation available
9
mod_inverse(m)[source]

Compute the inverse mod m, such that self * res == 1 mod m.

Example:

>>> Bn(10).mod_inverse(m = Bn(11))  # Only function notation available
10
>>> Bn(10).mod_mul(Bn(10), m = Bn(11)) == Bn(1)
True
mod_pow(other, m, ctx=None)[source]

Performs the modular exponentiation of self ** other % m.

Example:
>>> one100 = Bn(100)
>>> one100.mod_pow(2, 3)   # Modular exponentiation
1
divmod(other)[source]

Returns the integer division and remainder of this number by another. Synonym for (div, mod) = divmod(self, other)

int_div(other)[source]

Returns the integer division of this number by another. Synonym of self / other.

Example:

>>> one100 = Bn(100)
>>> two100 = Bn(200)
>>> two100.int_div(one100) # Function syntax
2
>>> two100 / one100        # Operator syntax
2
mod(other)[source]

Returns the remainder of this number modulo another. Synonym for self % other.

Example:

>>> one100 = Bn(100)
>>> two100 = Bn(200)
>>> two100.mod(one100) # Function syntax
0
>>> two100 % one100        # Operator syntax
0
pow(other, modulo=None, ctx=None)[source]

Returns the number raised to the power other optionally modulo a third number. Synonym with pow(self, other, modulo).

Example:

>>> one100 = Bn(100)
>>> one100.pow(2)      # Function syntax
10000
>>> one100 ** 2        # Operator syntax
10000
>>> one100.pow(2, 3)   # Modular exponentiation
1
is_prime()[source]

Returns True if the number is prime, with negligible prob. of error.

is_odd()[source]

Returns True if the number is odd.

is_bit_set(n)[source]

Returns True if the nth bit is set

num_bits()[source]

Returns the number of bits representing this Big Number

Module petlib.ec

class petlib.ec.EcGroup(nid=713, optimize_mult=True)[source]
static list_curves()[source]

Return a dictionary of name id (int) to curve names (str).

Example:
>>> curves = EcGroup.list_curves()
>>> curves[713]
'NIST/SECG curve over a 224 bit prime field'
parameters()[source]

Returns a dictionary with the parameters (a,b and p) of the curve.

Example:
>>> params = EcGroup(713).parameters()
>>> params["a"]
26959946667150639794667015087019630673557916260026308143510066298878
>>> params["b"]
18958286285566608000408668544493926415504680968679321075787234672564
>>> params["p"]
26959946667150639794667015087019630673557916260026308143510066298881
generator()[source]

Returns the generator of the EC group.

infinite()[source]

Returns a point at infinity.

Example:
>>> G = EcGroup()
>>> G.generator() + G.infinite() == G.generator() ## Should hold.
True
order()[source]

Returns the order of the group as a Big Number.

Example:
>>> G = EcGroup()
>>> G.order() * G.generator() == G.infinite() ## Should hold.
True
sum(elems)[source]

Sum efficiently a number of elements

wsum(weights, elems)[source]

Sum efficiently a number of elements each multiplied by a bn in weights

nid()[source]

Returns the Open SSL group ID

check_point(pt)[source]

Ensures the point is on the curve.

Example:
>>> G = EcGroup()
>>> G.check_point(G.generator())
True
>>> G.check_point(G.infinite())
True
hash_to_point(hinput)[source]

Hash a string into an EC Point.

get_points_from_x(x)[source]

Returns the two EC points with the given x coordinate.

class petlib.ec.EcPt(group)[source]

An EC point, supporting point addition, doubling and multiplication with a scalar

static from_binary(sbin, group)[source]

Create a point from a byte sequence.

Example:
>>> G = EcGroup()
>>> byte_string = G.generator().export()                # Export EC point as byte string
>>> EcPt.from_binary(byte_string, G) == G.generator()   # Import EC point from binary string
True
pt_add(other)[source]

Adds two points together. Synonym with self + other.

Example:
>>> g = EcGroup().generator()
>>> g.pt_add(g) == (g + g) == (2 * g) == g.pt_double() # Equivalent formulations
True
pt_add_inplace(other)[source]

Adds two points together and puts the result in self.pt.

pt_double()[source]

Doubles the point. equivalent to “self + self”.

pt_double_inplace()[source]

Doubles the point and mutates it to hold the result.

pt_neg()[source]

Returns the negative of the point. Synonym with -self.

Example:
>>> G = EcGroup()
>>> g = G.generator()
>>> g + (-g) == G.infinite() # Unary negative operator.
True
>>> g - g == G.infinite()    # Binary negative operator.
True
pt_neg_inplace()[source]

Mutates the point to hold the negative value of the point.

pt_mul(scalar)[source]

Returns the product of the point with a scalar (not commutative). Synonym with scalar * self.

Example:
>>> G = EcGroup()
>>> g = G.generator()
>>> 100 * g == g.pt_mul(100) # Operator and function notation mean the same
True
>>> G.order() * g == G.infinite() # Scalar mul. by the order returns the identity element.
True
pt_mul_inplace(scalar)[source]

Multiplies a scalar with a point and mutates the point to hold the result.

pt_eq(other)[source]

Returns a boolean denoting whether the points are equal. Synonym with self == other.

Example:
>>> G = EcGroup()
>>> g = G.generator()
>>> 40 * g + 60 * g == 100 * g
True
>>> g == 2 * g
False
export(form=POINT_CONVERSION_COMPRESSED)[source]

Returns a string binary representation of the point in compressed coordinates.

Example:
>>> G = EcGroup()
>>> byte_string = G.generator().export()
>>> print(hexlify(byte_string).decode("utf8"))
02b70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21
is_infinite()[source]

Returns True if this point is at infinity, otherwise False.

Example:
>>> G = EcGroup()
>>> g, o = G.generator(), G.order()
>>> (o * g).is_infinite()
True
get_affine()[source]

Return the affine coordinates (x,y) of this EC Point.

Example:
>>> G = EcGroup()
>>> g = G.generator()
>>> x, y = g.get_affine()
>>> x
19277929113566293071110308034699488026831934219452440156649784352033
>>> y
19926808758034470970197974370888749184205991990603949537637343198772

Module petlib.cipher

class petlib.cipher.Cipher(name, _alg=None)[source]

A class representing a symmetric cipher and mode.

Example:

An example of encryption and decryption using AES in counter mode.

>>> from os import urandom
>>> aes = Cipher("AES-128-CTR")     # Init AES in Counter mode
>>> key = urandom(16)
>>> iv  = urandom(16)
>>>
>>> # Get a CipherOperation object for encryption
>>> enc = aes.enc(key, iv)
>>> ref = b"Hello World"
>>> ciphertext = enc.update(ref)
>>> ciphertext += enc.finalize()
>>>
>>> # Get a CipherOperation object for decryption
>>> dec = aes.dec(key, iv)
>>> plaintext = dec.update(ciphertext)
>>> plaintext += dec.finalize()
>>> plaintext == ref # Check resulting plaintest matches referece one.
True
len_IV()[source]

Return the Initialization Vector length in bytes.

len_key()[source]

Return the secret key length in bytes.

len_block()[source]

Return the block size in bytes.

get_nid()[source]

Return the OpenSSL nid of the cipher and mode.

op(key, iv, enc=1)[source]

Initializes a cipher operation, either encrypt or decrypt and returns a CipherOperation object

Args:
key (str): the block cipher symmetric key. Length depends on block cipher choice. iv (str): an Initialization Vector of up to the block size. (Can be shorter.) enc (int): set to 1 to perform encryption, or 0 to perform decryption.
enc(key, iv)[source]

Initializes an encryption engine with the cipher with a specific key and Initialization Vector (IV). Returns the CipherOperation engine.

Args:
key (str): the block cipher symmetric key. Length depends on block cipher choice. iv (str): an Initialization Vector of up to the block size. (Can be shorter.)
dec(key, iv)[source]

Initializes a decryption engine with the cipher with a specific key and Initialization Vector (IV). Returns the CipherOperation engine.

Args:
key (str): the block cipher symmetric key. Length depends on block cipher choice. iv (str): an Initialization Vector of up to the block size. (Can be shorter.)
static aes_128_gcm()[source]

Returns a pre-initalized AES-GCM cipher with 128 bits key size

static aes_192_gcm()[source]

Returns a pre-initalized AES-GCM cipher with 192 bits key size

static aes_256_gcm()[source]

Returns a pre-initalized AES-GCM cipher with 256 bits key size

quick_gcm_enc(key, iv, msg, assoc=None, tagl=16)[source]

One operation GCM encryption.

Args:
key (str): the AES symmetric key. Length depends on block cipher choice. iv (str): an Initialization Vector of up to the block size. (Can be shorter.) msg (str): the message to encrypt. assoc (str): associated data that will be integrity protected, but not encrypted. tagl (int): the length of the tag, up to the block length.
Example:

Use of quick_gcm_enc and quick_gcm_dec for AES-GCM operations.

>>> from os import urandom      # Secure OS random source
>>> aes = Cipher("aes-128-gcm") # Initialize AES-GCM with 128 bit keys
>>> iv = urandom(16)
>>> key = urandom(16)
>>> # Encryption using AES-GCM returns a ciphertext and a tag
>>> ciphertext, tag = aes.quick_gcm_enc(key, iv, b"Hello")
>>> # Decrytion using AES-GCM
>>> p = aes.quick_gcm_dec(key, iv, ciphertext, tag)
>>> assert p == b'Hello'
quick_gcm_dec(key, iv, cip, tag, assoc=None)[source]

One operation GCM decrypt. See usage example in “quick_gcm_enc”. Throws an exception on failure of decryption

Args:
key (str): the AES symmetric key. Length depends on block cipher choice. iv (str): an Initialization Vector of up to the block size. (Can be shorter.) cip (str): the ciphertext to decrypt. tag (int): the integrity tag. assoc (str): associated data that will be integrity protected, but not encrypted.
class petlib.cipher.CipherOperation(xenc)[source]
update(data)[source]

Processes some data, and returns a partial result.

finalize()[source]

Finalizes the operation and may return some additional data. Throws an exception if the authenticator tag is different from the expected value.

Example:

Example of the exception thrown when an invalid tag is provided.

>>> from os import urandom
>>> aes = Cipher.aes_128_gcm()              # Define an AES-GCM cipher
>>> iv = urandom(16)
>>> key = urandom(16)
>>> ciphertext, tag = aes.quick_gcm_enc(key, iv, b"Hello")
>>>
>>> dec = aes.dec(key, iv)                  # Get a decryption CipherOperation
>>> dec.set_tag(urandom(len(tag)))          # Provide an invalid tag.
>>> plaintext = dec.update(ciphertext)      # Feed in the ciphertext for decryption.
>>> try:
...    dec.finalize()                       # Check and Finalize.
... except:
...    print("Failure")
Failure

Throws an exception since integrity check fails due to the invalid tag.

update_associated(data)[source]

Processes some GCM associated data, and returns nothing.

get_tag(tag_len=16)[source]

Get the GCM authentication tag. Execute after finalizing the encryption.

Example:

AES-GCM encryption usage:

>>> from os import urandom
>>> aes = Cipher.aes_128_gcm()          # Initialize AES cipher
>>> key = urandom(16)
>>> iv = urandom(16)
>>> enc = aes.enc(key, iv)              # Get an encryption CipherOperation
>>> enc.update_associated(b"Hello")     # Include some associated data
>>> ciphertext = enc.update(b"World!")  # Include some plaintext
>>> nothing = enc.finalize()            # Finalize
>>> tag = enc.get_tag(16)               # Get the AES-GCM tag
set_tag(tag)[source]

Specify the GCM authenticator tag. Must be done before finalizing decryption

Example:

AES-GCM decryption and check:

>>> aes = Cipher.aes_128_gcm()              # Define an AES-GCM cipher
>>> ciphertext, tag = (b'dV\xb9:\xd0\xbe', b'pA\xbe?\xfc\xd1&\x03\x1438\xc5\xf8In\xaa')
>>> dec = aes.dec(key=b"A"*16, iv=b"A"*16)  # Get a decryption CipherOperation
>>> dec.update_associated(b"Hello")         # Feed in the non-secret assciated data.
>>> plaintext = dec.update(ciphertext)      # Feed in the ciphertext for decryption.
>>> dec.set_tag(tag)                        # Provide the AES-GCM tag for integrity.
>>> nothing = dec.finalize()                # Check and finalize.
>>> assert plaintext == b'World!'

Module petlib.hmac

class petlib.hmac.Hmac(name, key)[source]

Initialize the HMAC by name with a key.

Args:
name: the name of the hash function to be used. key: the cryptographic symmetric key of the HMAC.
Returns:
An HMAC instance, ready to accept data to MAC.

Example:

>>> h = Hmac(b"sha512", b"Jefe")
>>> h.update(b"what do ya want ")
>>> h.update(b"for nothing?")
>>> d = h.digest()
>>> len(d)
64
>>> hexlify(d)[:10] == b"164b7a7bfc"
True
update(data)[source]

Update the HMAC with some data to authenticate.

Args:
data: the data to add to the MAC.

Note: you must not call update after you finalize the HMAC.

Raises:
Exception: if called after the HMAC has been finalized.
digest()[source]

Output the HMAC digest as a binary string.

Returns:
The digest as a binary data string.
petlib.hmac.secure_compare(a1, a2)[source]

A constant-time comparison function. Returns True if the two strings are equal and False otherwise.

Args:
a1 (str): the first string a2 (str): the second string
Returns:
bool: whether the two stings are equal.

Module petlib.ecdsa

A library providing signature and verification functions for the ECDSA scheme.

Example:

How to use do_ecdsa_sign and do_ecdsa_verify to sign and verify a string:

>>> from hashlib import sha1
>>> # Generate a signature / verification key pair.
>>> G = EcGroup()
>>> sig_key = G.order().random()
>>> ver_key = sig_key * G.generator()
>>> # Hash the (potentially long) message into a short digest.
>>> digest = sha1(b"Hello World!").digest()
>>> # Sign and verify signature
>>> sig = do_ecdsa_sign(G, sig_key, digest)
>>> do_ecdsa_verify(G, ver_key, sig, digest)
True

Fast signatures can be constructed using do_ecdsa_setup:

>>> from hashlib import sha1
>>> # Generate a signature / verification key pair.
>>> G = EcGroup()
>>> sig_key = G.order().random()
>>> ver_key = sig_key * G.generator()
>>> # Hash the (potentially long) message into a short digest.
>>> digest = sha1(b"Hello World!").digest()
>>> # Sign and verify signature
>>> kinv_rp = do_ecdsa_setup(G, sig_key)
>>> sig = do_ecdsa_sign(G, sig_key, digest, kinv_rp = kinv_rp)
>>> do_ecdsa_verify(G, ver_key, sig, digest)
True
petlib.ecdsa.do_ecdsa_setup(G, priv)[source]

Compute the parameters kinv and rp to (optionally) speed up ECDSA signing.

petlib.ecdsa.do_ecdsa_sign(G, priv, data, kinv_rp=None)[source]

A quick function to ECDSA sign a hash.

Args:
G (EcGroup): the group in which math is done. priv (Bn): the secret key. data (str): the string to sign. kinv_rp (opaque): optional setup parameters.
Returns:
Bn, Bn: The (r, s) signature
petlib.ecdsa.do_ecdsa_verify(G, pub, sig, data)[source]

A quick function to ECDSA verify a hash.

Args:
G (EcGroup): the group in which math is done. pub (EcPt): the secret key sig (Bn, Bn): the (r,s) signature data (str): the string to sign
Returns:
bool: A Boolean indicating whether the signature verifies.

Module petlib.pack

The module provides functions to pack and unpack petlib Bn, EcGroup, and EcPt strucures.

Example:
>>> # Define a custom class, encoder and decoder
>>> class CustomType:
...     def __eq__(self, other):
...         return isinstance(other, CustomType)
>>>
>>> def enc_custom(obj):
...     return b''
>>>
>>> def dec_custom(data):
...     return CustomType()
>>>
>>> register_coders(CustomType, 10, enc_custom, dec_custom)
>>>
>>> # Define a structure
>>> G = EcGroup()
>>> custom_obj = CustomType()
>>> test_data = [G, G.generator(), G.order(), custom_obj]
>>>
>>> # Encode and decode custom structure
>>> packed = encode(test_data)
>>> x = decode(packed)
>>> assert x == test_data
petlib.pack.encode(structure, custom_encoder=None)[source]

Encode a structure containing petlib objects to a binary format. May define a custom encoder for user classes.

petlib.pack.decode(packed_data, custom_decoder=None)[source]

Decode a binary byte sequence into a structure containing pelib objects. May define a custom decoder for custom classes.

Indices and tables

License

Petlib is licensed under the following terms:

Copyright (c) 2014, George Danezis (UCL) All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.