Skip to content

pure-python implementation of the SPAKE2 Password-Authenticated Key Exchange algorithm

License

Notifications You must be signed in to change notification settings

warner/python-spake2

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

26 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Pure-Python SPAKE2

Build Status

This is an easy-to-use implementation of the SPAKE2 password-authenticated key exchange algorithm, implemented purely in Python, released under the MIT license. This allows two parties, who share a weak password, to safely derive a strong shared secret (and therefore build an encrypted+authenticated channel). A passive attacker who eavesdrops on the connection learns no information about the password or the generated secret. An active attacker (man-in-the-middle) gets exactly one guess at the password, and unless they get it right, they learn no information about the password or the generated secret. Each execution of the protocol enables one guess. The use of a weak password is made safer by the rate-limiting of guesses: no off-line attack is possible.

The protocol requires the exchange of one pair of messages, so only one round trip is necessary to establish the session key. If key-confirmation is necessary, that will require a second round trip.

All messages are JSON-serializable. For the default security level (using a 1024-bit modulus, roughly equivalent to an 80-bit symmetric key), the message is about 265 bytes long. An alternative binary encoding is available, which reduces the message sizes by about 50%.

Dependencies

This package is pure-python: no C code or compiled extension modules are used. It requires the 'simplejson' module for data serialization.

Speed

To run the built-in speed tests, just run the bench_spake2.py script.

SPAKE2 consists of two phases, separated by a single message exchange. On my 2008 mac laptop, the default params_80 security level takes about 20ms to complete both phases. The params_112 level takes about 185ms, and params_128 takes about 422ms. The two phases take roughly equal time.

This library uses only Python. A version which used C speedups for the large modular multiplication operations would probably be an order of magnitude faster.

History

The protocol comes from Dan Boneh and Victor Shoup, described as "PAKE2" in their ["cryptobook"] 1. This is a form of "SPAKE2", defined by Abdalla and Pointcheval at [RSA 2005] 2. Additional recommendations for groups and distinguished elements were published in [Ladd's IETF draft] 3.

The Boneh/Shoup chapter that defines PAKE2 also defines an augmented variant named "PAKE2+", which changes one side (typically a server) to record a derivative of the password instead of the actual password. In PAKE2+, a server compromise does not immediately give access to the passwords: instead, the attacker must perform an offline dictionary attack against the stolen data before they can learn the passwords. PAKE2+ support is planned, but not yet implemented.

Brian Warner wrote this Python version in July 2010, based upon the algorithm from their book.

Testing

To run the built-in test suite from a source directory, do:

PYTHONPATH=. python spake2/test/test_spake2.py

The tests take approximately 3 seconds on my laptop.

Security

This library does not protect against timing attacks. Do not allow attackers to measure how long it takes you to create or respond to a message. This library depends upon a strong source of random numbers. Do not use it on a system where os.urandom() is weak.

Usage

Alice and Bob both initialize their SPAKE2 instances with the same (weak) password. They will exchange messages to (hopefully) derive a shared secret key "K". The protocol is symmetric: for each operation that Alice does, Bob will do the same. For each message that Alice sends, Bob will send a corresponding message.

However, there are two roles in the SPAKE2 protocol, "P" and "Q". The two sides must agree ahead of time which one will play which role (the messages they generate depend upon which side they play). For environments in which one piece of code always plays the same role, there are two separate classes SPAKE2_P and SPAKE2_Q to make this easier to set up.

Each instance of a SPAKE2 protocol uses a set of shared parameters. These include a group, a generator, and a pair of arbitrary group elements. The python-spake2 implementation comes with several pre-generated parameter sets, with various security levels.

You start by creating a SPAKE2 instance, using the password and the side indicator ("P" or "Q"). You can override an option to increase the security level (at the expense of processing speed). Then you ask the instance for the outbound message by calling msg_out=p.one(), and send it to your partner. Once you receive the corresponding inbound message, you pass it into the instance and extract the (shared) key bytestring with key=p.two(msg_in). For example, the client-side might do:

from spake2 import SPAKE2_P
p = SPAKE2_P("our password")
msg_out = p.one()
send(msg_out)
msg_in = receive()
key = p.two(msg_in)

while the server-side might do:

from spake2 import SPAKE2_Q
q = SPAKE2_Q("our password")
msg_out = q.one()
send(msg_out)
msg_in = receive()
key = q.two(msg_in)

If both sides used the same password, and there is no man-in-the-middle, then both sides will obtain the same key. If not, the two sides will get different keys, so using "key" for data encryption will result in garbled data. To safely test for identical keys before use, you can perform a second message exchange at the end of the protocol, before actually using the key (be careful to not simply send the shared key over the encrypted wire: this would allow a MitM to learn the key that they could otherwise not guess). This key-confirmation step is asymmetric: one side will always learn about the success or failure of the protocol before the other.

# Alice does this:
...
key = p.two(msg_in)
hhkey = sha256(sha256(key).digest()).digest()
send(hhkey)

# and Bob does this:
...
key = q.two(msg_in)
their_hhkey = receive()
my_hhkey = sha256(sha256(key).digest()).digest()
assery my_hhkey == their_hhkey
hkey = sha256(key).digest()
send(hkey)

# and then Alice does this:
their_hkey = receive()
my_hkey = sha256(key).digest()
assert my_hkey == their_hkey

The shared "key" can be used as an AES data-encryption key, and/or an HMAC key to provide data integrity. It can also be used to derive other session keys as necessary.

footnotes

About

pure-python implementation of the SPAKE2 Password-Authenticated Key Exchange algorithm

Resources

License

Stars

Watchers

Forks

Packages

No packages published