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
Synopsis
- 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
- spake2Exchange :: (AbelianGroup group, HashAlgorithm hashAlgorithm) => Protocol group hashAlgorithm -> Password -> (ByteString -> IO ()) -> IO (Either error ByteString) -> IO (Either (MessageError error) ByteString)
- 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 error) (Element group)
- data MessageError e
- formatError :: Show e => MessageError e -> 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.
:: (AbelianGroup group, HashAlgorithm hashAlgorithm) | |
=> Protocol group hashAlgorithm | A |
-> Password | The password shared between both sides. Construct with |
-> (ByteString -> IO ()) | An action to send a message. The |
-> IO (Either error ByteString) | An action to receive a message. The |
-> 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.
:: 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
Eq e => Eq (MessageError e) Source # | |
Defined in Crypto.Spake2 (==) :: MessageError e -> MessageError e -> Bool # (/=) :: MessageError e -> MessageError e -> Bool # | |
Show e => Show (MessageError e) Source # | |
Defined in Crypto.Spake2 showsPrec :: Int -> MessageError e -> ShowS # show :: MessageError e -> String # showList :: [MessageError e] -> ShowS # |
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.
:: (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.
Bytes that identify a side of the protocol
Which side we are.
Instances
Bounded WhichSide Source # | |
Enum WhichSide Source # | |
Defined in Crypto.Spake2 succ :: WhichSide -> WhichSide # pred :: WhichSide -> WhichSide # fromEnum :: WhichSide -> Int # enumFrom :: WhichSide -> [WhichSide] # enumFromThen :: WhichSide -> WhichSide -> [WhichSide] # enumFromTo :: WhichSide -> WhichSide -> [WhichSide] # enumFromThenTo :: WhichSide -> WhichSide -> WhichSide -> [WhichSide] # | |
Eq WhichSide Source # | |
Ord WhichSide Source # | |
Defined in Crypto.Spake2 | |
Show WhichSide Source # | |