spake2-0.4.3: Implementation of the SPAKE2 Password-Authenticated Key Exchange algorithm
Safe HaskellNone
LanguageHaskell2010

Crypto.Spake2

Description

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, or S, 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

Synopsis

Documentation

data Password Source #

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.

Instances

Instances details
Eq Password Source # 
Instance details

Defined in Crypto.Spake2

Ord Password Source # 
Instance details

Defined in Crypto.Spake2

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.

spake2Exchange Source #

Arguments

:: (AbelianGroup group, HashAlgorithm hashAlgorithm) 
=> Protocol group hashAlgorithm

A Protocol with all the parameters for the exchange. These parameters must be shared by both sides. Construct with makeAsymmetricProtocol or makeSymmetricProtocol.

-> Password

The password shared between both sides. Construct with makePassword.

-> (ByteString -> IO ())

An action to send a message. The ByteString parameter is this side's SPAKE2 element, encoded using the group encoding, prefixed according to the parameters in the Protocol.

-> IO (Either error ByteString)

An action to receive a message. The ByteString generated ought to be the protocol-prefixed, group-encoded version of the other side's SPAKE2 element.

-> IO (Either (MessageError error) ByteString)

Either the shared session key or an error indicating we couldn't parse the other side's message.

Perform an entire SPAKE2 exchange.

Given a SPAKE2 protocol that has all of the parameters for this exchange, generate a one-off message from this side and receive a one off message from the other.

Once we are done, return a key shared between both sides for a single session.

Note: as per the SPAKE2 definition, the session key is not guaranteed to actually work. If the other side has failed to authenticate, you will still get a session key. Therefore, you must exchange some other message that has been encrypted using this key in order to confirm that the session key is indeed shared.

Note: the "send" and "receive" actions are performed concurrently. If you have ordering requirements, consider using a TVar or MVar to coordinate, or implementing your own equivalent of spake2Exchange.

If the message received from the other side cannot be parsed, return a MessageError.

Since 0.4.0.

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.

generateKeyMaterial Source #

Arguments

:: 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 error) (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 e Source #

An error that occurs when interpreting messages from the other side of the exchange.

Instances

Instances details
Eq e => Eq (MessageError e) Source # 
Instance details

Defined in Crypto.Spake2

Show e => Show (MessageError e) Source # 
Instance details

Defined in Crypto.Spake2

formatError :: Show e => MessageError e -> 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.

createSessionKey Source #

Arguments

:: (Group group, HashAlgorithm hashAlgorithm) 
=> Protocol group hashAlgorithm

The protocol used for this exchange

-> Element group

The outbound message, generated by this, \(X^{\star}\), or either side if symmetric

-> Element group

The inbound message, generated by the other side, \(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.

Note: In spake2 0.3 and earlier, The \(X^{\star}\) and \(Y^{\star}\) were expected to be from side A and side B respectively. Since spake2 0.4, they are the outbound and inbound elements respectively. This fixes an interoperability concern with the Python library, and reduces the burden on the caller. Apologies for the possibly breaking change to any users of older versions of spake2.

newtype SideID Source #

Bytes that identify a side of the protocol

Constructors

SideID 

Fields

Instances

Instances details
Eq SideID Source # 
Instance details

Defined in Crypto.Spake2

Methods

(==) :: SideID -> SideID -> Bool #

(/=) :: SideID -> SideID -> Bool #

Ord SideID Source # 
Instance details

Defined in Crypto.Spake2

Show SideID Source # 
Instance details

Defined in Crypto.Spake2