Safe Haskell | None |
---|---|
Language | Haskell2010 |
Say that you and someone else share a secret password, and you want to use this password to arrange some secure channel of communication. You want:
- to know that the other party also knows the secret password (maybe they're an imposter!)
- the password to be secure against offline dictionary attacks
- probably some other things
SPAKE2 is an algorithm for agreeing on a key exchange that meets these criteria. See Simple Password-Based Encrypted Key Exchange Protocols by Michel Abdalla and David Pointcheval for more details.
How it works
Preliminaries
Before exchanging, two nodes need to agree on the following, out-of-band:
In general:
- hash algorithm, \(H\)
- group to use, \(G\)
- arbitrary members of group to use for blinding
- a means of converting this password to a scalar of group
For a specific exchange:
- whether the connection is symmetric or asymmetric
- the IDs of the respective sides
- a shared, secret password in bytes
Protocol
How we map the password to a scalar
Use HKDF expansion (see expandData
) to expand the password by 16 bytes,
using an empty salt, and "SPAKE2 pw" as the info.
Then, use a group-specific mapping from bytes to scalars. Since scalars are normally isomorphic to integers, this will normally be a matter of converting the bytes to an integer using standard deserialization and then turning the integer into a scalar.
How we exchange information
See Math
for details on the mathematics of the exchange.
How python-spake2 works
- Message to other side is prepended with a single character,
A
,B
, orS
, to indicate which side it came from - The hash function for generating the session key has a few interesting properties:
- uses SHA256 for hashing
- does not include password or IDs directly, but rather uses their SHA256 digests as inputs to the hash
- for the symmetric version, it sorts \(X^{\star}\) and \(Y^{\star}\), because neither side knows which is which
- By default, the ID of either side is the empty bytestring
Open questions
- how does endianness come into play?
- what is Shallue-Woestijne-Ulas and why is it relevant?
References
- Javascript implementation, includes long, possibly relevant discussion
- Python implementation
- SPAKE2 random elements - blog post by warner about choosing \(M\) and \(N\)
- Simple Password-Based Encrypted Key Exchange Protocols by Michel Abdalla and David Pointcheval
- draft-irtf-cfrg-spake2-03 - expired IRTF draft for SPAKE2
- data Password
- makePassword :: ByteString -> Password
- data Protocol group hashAlgorithm
- makeAsymmetricProtocol :: hashAlgorithm -> group -> Element group -> Element group -> SideID -> SideID -> WhichSide -> Protocol group hashAlgorithm
- makeSymmetricProtocol :: hashAlgorithm -> group -> Element group -> SideID -> Protocol group hashAlgorithm
- startSpake2 :: (MonadRandom randomly, AbelianGroup group) => Protocol group hashAlgorithm -> Password -> randomly (Spake2Exchange group)
- computeOutboundMessage :: AbelianGroup group => Spake2Exchange group -> Element group
- generateKeyMaterial :: AbelianGroup group => Spake2Exchange group -> Element group -> Element group
- extractElement :: Group group => Protocol group hashAlgorithm -> ByteString -> Either MessageError (Element group)
- data MessageError
- formatError :: MessageError -> Text
- elementToMessage :: Group group => Protocol group hashAlgorithm -> Element group -> ByteString
- createSessionKey :: (Group group, HashAlgorithm hashAlgorithm) => Protocol group hashAlgorithm -> Element group -> Element group -> Element group -> Password -> ByteString
- newtype SideID = SideID {}
- data WhichSide
Documentation
Shared secret password used to negotiate the connection.
Constructor deliberately not exported,
so that once a Password
has been created, the actual password cannot be retrieved by other modules.
Construct with makePassword
.
makePassword :: ByteString -> Password Source #
Construct a password.
The SPAKE2 protocol
data Protocol group hashAlgorithm Source #
Everything required for the SPAKE2 protocol.
Both sides must agree on these values for the protocol to work.
This mostly means value equality, except for us
,
where each side must have complementary values.
Construct with makeAsymmetricProtocol
or makeSymmetricProtocol
.
makeAsymmetricProtocol :: hashAlgorithm -> group -> Element group -> Element group -> SideID -> SideID -> WhichSide -> Protocol group hashAlgorithm Source #
Construct an asymmetric SPAKE2 protocol.
makeSymmetricProtocol :: hashAlgorithm -> group -> Element group -> SideID -> Protocol group hashAlgorithm Source #
Construct a symmetric SPAKE2 protocol.
startSpake2 :: (MonadRandom randomly, AbelianGroup group) => Protocol group hashAlgorithm -> Password -> randomly (Spake2Exchange group) Source #
Commence a SPAKE2 exchange.
computeOutboundMessage :: AbelianGroup group => Spake2Exchange group -> Element group Source #
Determine the element (either \(X^{\star}\) or \(Y^{\star}\)) to send to the other side.
:: AbelianGroup group | |
=> Spake2Exchange group | An initiated SPAKE2 exchange |
-> Element group | The outbound message from the other side (i.e. inbound to us) |
-> Element group | The final piece of key material to generate the session key. |
Generate key material, \(K\), given a message from the other side (either \(Y^{\star}\) or \(X^{\star}\)).
This key material is the last piece of input required to make the session key, \(SK\), which should be generated as:
\[SK \leftarrow H(A, B, X^{\star}, Y^{\star}, K, pw)\]
Where:
- \(H\) is a hash function
- \(A\) identifies the initiating side
- \(B\) identifies the receiving side
- \(X^{star}\) is the outbound message from the initiating side
- \(Y^{star}\) is the outbound message from the receiving side
- \(K\) is the result of this function
- \(pw\) is the password (this is what makes it SPAKE2, not SPAKE1)
extractElement :: Group group => Protocol group hashAlgorithm -> ByteString -> Either MessageError (Element group) Source #
Extract an element on the group from an incoming message.
Returns a MessageError
if we cannot decode the message,
or the other side does not appear to be the expected other side.
TODO: Need to protect against reflection attack at some point.
data MessageError Source #
An error that occurs when interpreting messages from the other side of the exchange.
formatError :: MessageError -> Text Source #
Turn a MessageError
into human-readable text.
elementToMessage :: Group group => Protocol group hashAlgorithm -> Element group -> ByteString Source #
Turn an element into a message from this side of the protocol.
:: (Group group, HashAlgorithm hashAlgorithm) | |
=> Protocol group hashAlgorithm | The protocol used for this exchange |
-> Element group | The message from side A, \(X^{\star}\), or either side if symmetric |
-> Element group | The message from side B, \(Y^{\star}\), or either side if symmetric |
-> Element group | The calculated key material, \(K\) |
-> Password | The shared secret password |
-> ByteString | A session key to use for further communication |
Create a session key based on the output of SPAKE2.
\[SK \leftarrow H(A, B, X^{\star}, Y^{\star}, K, pw)\]
Including \(pw\) in the session key is what makes this SPAKE2, not SPAKE1.
Bytes that identify a side of the protocol