Botan 3.13.0
Crypto and TLS for C&
spake2p.h
Go to the documentation of this file.
1/*
2* (C) 2024,2025,2026 Jack Lloyd
3*
4* Botan is released under the Simplified BSD License (see license.txt)
5*/
6
7#ifndef BOTAN_PAKE_SPAKE2PLUS_H_
8#define BOTAN_PAKE_SPAKE2PLUS_H_
9
10#include <botan/ec_apoint.h>
11#include <botan/ec_group.h>
12#include <botan/ec_scalar.h>
13#include <botan/secmem.h>
14#include <botan/types.h>
15#include <optional>
16#include <span>
17#include <string>
18#include <string_view>
19#include <utility>
20#include <vector>
21
22namespace Botan {
23
25
26}
27
28/**
29* SPAKE2+ (RFC 9383) password authenticated key exchange
30*
31* SPAKE2+ is an augmented PAKE; the two sides are asymmetric. The prover
32* knows the password itself, while the verifier stores only a registration
33* record derived from the password. An attacker who steals the registration
34* record cannot impersonate the prover without first performing a dictionary
35* attack on the record.
36*
37* The expected message flow is
38*
39* - The prover calls ProverContext::generate_message and sends the result
40* (shareP) to the verifier.
41* - The verifier calls VerifierContext::process_message and sends the
42* result (shareV followed by confirmV) to the prover.
43* - The prover calls ProverContext::process_message, which checks the
44* verifier's key confirmation, and sends the result (confirmP) to the
45* verifier.
46* - The verifier calls VerifierContext::verify_confirmation
47*
48* After the final confirmation step both sides can call shared_secret to
49* obtain the session key (K_shared in RFC 9383)
50*
51* Protocols which embed SPAKE2+ and perform the prover's key confirmation
52* themselves (such as the proposed TLS PAKE extension) may instead call
53* VerifierContext::skip_confirmation in place of the final step.
54*/
55namespace Botan::SPAKE2p {
56
57/**
58* SPAKE2+ (RFC 9383) System Parameters
59*
60* This selects the elliptic curve group, the M/N group elements, and the
61* hash function; the hash also fixes the KDF (HKDF) and the MAC (HMAC)
62*/
63class BOTAN_PUBLIC_API(3, 13) SystemParameters final {
64 public:
65 /**
66 * The RFC 9383 ciphersuite P256-SHA256-HKDF-HMAC-SHA256
67 */
68 static SystemParameters rfc9383_p256_sha256();
69
70 /**
71 * The RFC 9383 ciphersuite P256-SHA512-HKDF-HMAC-SHA512
72 */
73 static SystemParameters rfc9383_p256_sha512();
74
75 /**
76 * The RFC 9383 ciphersuite P384-SHA256-HKDF-HMAC-SHA256
77 */
78 static SystemParameters rfc9383_p384_sha256();
79
80 /**
81 * The RFC 9383 ciphersuite P384-SHA512-HKDF-HMAC-SHA512
82 */
83 static SystemParameters rfc9383_p384_sha512();
84
85 /**
86 * The RFC 9383 ciphersuite P521-SHA512-HKDF-HMAC-SHA512
87 */
88 static SystemParameters rfc9383_p521_sha512();
89
90 /**
91 * SPAKE2+ custom system parameters for an arbitrary group
92 *
93 * The M/N values will be derived from the seed using hash2curve;
94 * note that not all groups support hash2curve.
95 *
96 * RFC 9383 Section 3.2: "Applications MAY use different M and N
97 * values, provided they are computed, e.g., using different input
98 * seeds to the algorithm in Appendix B, as random elements for
99 * which the discrete log is unknown."
100 *
101 * If the seed includes the identities of the participants, this
102 * additionally makes the scheme "quantum annoying", in that an attacker
103 * with a discrete logarithm oracle must compute a new discrete log for
104 * each (user, verifier) pair they wish to attack.
105 *
106 * @param group the elliptic curve group to use
107 * @param seed the seed bytes used to derive M and N
108 * @param hash_fn the hash function to use (eg "SHA-256")
109 */
110 static SystemParameters custom(const EC_Group& group, std::span<const uint8_t> seed, std::string_view hash_fn);
111
112 /**
113 * Return the elliptic curve group
114 */
115 const EC_Group& group() const { return m_group; }
116
117 /**
118 * Return the SPAKE2+ M group element
119 */
120 const EC_AffinePoint& spake2p_m() const { return m_spake2p_m; }
121
122 /**
123 * Return the SPAKE2+ N group element
124 */
125 const EC_AffinePoint& spake2p_n() const { return m_spake2p_n; }
126
127 /**
128 * Return the name of the hash function
129 */
130 const std::string& hash_function() const { return m_hash_fn; }
131
132 /**
133 * Return the size in bytes of a key share (shareP or shareV)
134 */
135 size_t share_size() const;
136
137 /**
138 * Return the size in bytes of a key confirmation message (confirmP or confirmV)
139 */
140 size_t confirmation_size() const;
141
142 private:
143 SystemParameters(EC_Group group, EC_AffinePoint m, EC_AffinePoint n, std::string_view hash_fn);
144
145 EC_Group m_group;
146 EC_AffinePoint m_spake2p_m;
147 EC_AffinePoint m_spake2p_n;
148 std::string m_hash_fn;
149};
150
151class ProverSecret;
152
153/**
154* SPAKE2+ Registration Record
155*
156* This is the information (w0 and L in RFC 9383) which the verifier
157* stores in order to authenticate the prover.
158*/
159class BOTAN_PUBLIC_API(3, 13) RegistrationRecord final {
160 public:
161 /**
162 * Perform password registration, returning the registration record
163 *
164 * This derives the record from the password using Argon2id; see
165 * ProverSecret::from_password for the details. The same password,
166 * identities, and salt must later be used to create the prover's secret.
167 *
168 * The identities and salt may be empty.
169 */
170 static RegistrationRecord from_password(const SystemParameters& params,
171 std::string_view password,
172 std::span<const uint8_t> prover_id,
173 std::span<const uint8_t> verifier_id,
174 std::span<const uint8_t> salt,
176
177 /**
178 * Deserialize a RegistrationRecord previously serialized by serialize
179 */
180 static RegistrationRecord deserialize(const SystemParameters& params, std::span<const uint8_t> record);
181
182 /**
183 * Serialize the registration record
184 *
185 * @warning the return value is the unencrypted registration record, which
186 * is a sensitive value allowing password guessing attacks. Encrypt it for
187 * persistent storage if possible.
188 */
190
191 private:
192 friend class ProverSecret;
193 friend class VerifierContext;
194
195 RegistrationRecord(EC_Scalar w0, EC_AffinePoint l) : m_w0(std::move(w0)), m_l(std::move(l)) {}
196
197 EC_Scalar m_w0;
198 EC_AffinePoint m_l;
199};
200
201/**
202* SPAKE2+ Prover Secret
203*
204* This is the information (w0 and w1 in RFC 9383) which the prover
205* derives from the password in order to authenticate itself.
206*/
207class BOTAN_PUBLIC_API(3, 13) ProverSecret final {
208 public:
209 /**
210 * Derive the prover secret from a password
211 *
212 * The derivation uses Argon2id with the memory-constrained parameters
213 * from RFC 9106, namely m=64 MiB, t=3, p=4. Following RFC 9383, the
214 * Argon2id passphrase input is the concatenation
215 *
216 * len(pw) || pw || len(idProver) || idProver || len(idVerifier) || idVerifier
217 *
218 * with each length an 8-byte little-endian count of bytes, and the salt
219 * is provided to Argon2id directly. The Argon2id output is split in two
220 * halves, each of which is reduced modulo the group order.
221 *
222 * The identities and salt may be empty; if a salt is available it
223 * should be used, as this prevents precomputed dictionary attacks.
224 */
225 static ProverSecret from_password(const SystemParameters& params,
226 std::string_view password,
227 std::span<const uint8_t> prover_id,
228 std::span<const uint8_t> verifier_id,
229 std::span<const uint8_t> salt);
230
231 /**
232 * Create a prover secret from already derived scalars
233 *
234 * @warning This interface is potentially unsafe, depending upon how the
235 * scalars are derived from the password. They must be uniformly random,
236 * and preferably computed in a way such that testing password guesses is
237 * expensive for an attacker. It exists to support testing, as well as
238 * applications which require using a different password hashing scheme
239 * than the default one implemented by from_password.
240 */
241 static ProverSecret from_prehashed(EC_Scalar w0, EC_Scalar w1);
242
243 /**
244 * Deserialize a ProverSecret previously serialized by serialize
245 */
246 static ProverSecret deserialize(const SystemParameters& params, std::span<const uint8_t> secret);
247
248 /**
249 * Serialize the prover secret
250 *
251 * @warning the return value is password equivalent; encrypt it for
252 * persistent storage if possible.
253 */
255
256 /**
257 * Compute the registration record (w0 and L=w1*P) for this secret
258 *
259 * This would typically be done once, when the password is first
260 * registered with the verifier.
261 */
263
264 private:
265 friend class ProverContext;
266
267 ProverSecret(EC_Scalar w0, EC_Scalar w1) : m_w0(std::move(w0)), m_w1(std::move(w1)) {}
268
269 EC_Scalar m_w0;
270 EC_Scalar m_w1;
271};
272
273/**
274* SPAKE2+ (RFC 9383) Prover
275*
276* The prover knows the password secret (w0 and w1) and authenticates
277* itself to a verifier which knows the matching registration record.
278*/
279class BOTAN_PUBLIC_API(3, 13) ProverContext final {
280 public:
281 /**
282 * Set up for an execution of the protocol
283 *
284 * The identities and context must be agreed upon by both parties; the
285 * identities must additionally match the values used during password
286 * registration. Both the identities and the context may be empty.
287 */
288 ProverContext(const SystemParameters& params,
289 const ProverSecret& secret,
290 std::span<const uint8_t> prover_id,
291 std::span<const uint8_t> verifier_id,
292 std::span<const uint8_t> context = {});
293
294 /**
295 * Generate the prover's key share (shareP), which is sent to the verifier.
296 *
297 * This can be called only once.
298 */
299 std::vector<uint8_t> generate_message(RandomNumberGenerator& rng);
300
301 /**
302 * Consume the message from the verifier (shareV followed by confirmV)
303 * and return the prover's key confirmation (confirmP), which is sent
304 * to the verifier.
305 *
306 * Throws Decoding_Error if the message is malformed, and
307 * Invalid_Authentication_Tag if the verifier's key confirmation is
308 * wrong (typically due to a password mismatch).
309 */
310 std::vector<uint8_t> process_message(std::span<const uint8_t> peer_message, RandomNumberGenerator& rng);
311
312 /**
313 * Return the shared secret (K_shared)
314 *
315 * This may be called only after process_message has succeeded.
316 */
318
319 /**
320 * Return the system parameters
321 */
322 const SystemParameters& parameters() const { return m_params; }
323
324 private:
325 enum class State : uint8_t { Initial, ShareGenerated, Complete, Failed };
326
327 SystemParameters m_params;
328 ProverSecret m_secret;
329 std::vector<uint8_t> m_prover_id;
330 std::vector<uint8_t> m_verifier_id;
331 std::vector<uint8_t> m_context;
332 std::optional<std::pair<std::vector<uint8_t>, EC_Scalar>> m_our_message;
333 secure_vector<uint8_t> m_shared_secret;
334 State m_state = State::Initial;
335};
336
337/**
338* SPAKE2+ (RFC 9383) Verifier
339*
340* The verifier does not know the password itself; it stores only the
341* registration record.
342*/
344 public:
345 /**
346 * Set up for an execution of the protocol
347 *
348 * The identities and context must be agreed upon by both parties; the
349 * identities must additionally match the values used during password
350 * registration. Both the identities and the context may be empty.
351 */
352 VerifierContext(const SystemParameters& params,
353 const RegistrationRecord& record,
354 std::span<const uint8_t> prover_id,
355 std::span<const uint8_t> verifier_id,
356 std::span<const uint8_t> context = {});
357
358 /**
359 * Consume the prover's key share (shareP) and return the verifier's
360 * response (shareV followed by confirmV), which is sent to the prover.
361 *
362 * This can be called only once. Throws Decoding_Error if the key
363 * share is malformed.
364 */
365 std::vector<uint8_t> process_message(std::span<const uint8_t> peer_message, RandomNumberGenerator& rng);
366
367 /**
368 * Check the prover's key confirmation (confirmP)
369 *
370 * Throws Invalid_Authentication_Tag if the confirmation is wrong,
371 * meaning the prover does not know the password.
372 */
373 void verify_confirmation(std::span<const uint8_t> confirmation);
374
375 /**
376 * Skip checking the prover's key confirmation (confirmP)
377 *
378 * This can be called after process_message, in place of
379 * verify_confirmation, to allow extracting the shared secret without
380 * having checked the prover's key confirmation.
381 *
382 * @warning After calling this, nothing is known about the peer; only
383 * a prover which knows the password can compute the same shared
384 * secret, but no evidence of this has been received. It is intended
385 * solely for protocols which embed SPAKE2+ and perform the prover's
386 * key confirmation themselves, for example the proposed TLS PAKE
387 * extension, where the TLS handshake takes the place of confirmP.
388 * Anywhere else, use verify_confirmation.
389 */
390 void skip_confirmation();
391
392 /**
393 * Return the shared secret (K_shared)
394 *
395 * This may be called only after verify_confirmation has succeeded,
396 * or after skip_confirmation.
397 *
398 * RFC 9383 Section 3.3: "The Verifier MUST NOT send application data
399 * to the Prover until it has received and verified the confirmation
400 * message."
401 */
403
404 /**
405 * Return the system parameters
406 */
407 const SystemParameters& parameters() const { return m_params; }
408
409 private:
410 enum class State : uint8_t { Initial, Responded, Complete, Failed };
411
412 SystemParameters m_params;
413 RegistrationRecord m_record;
414 std::vector<uint8_t> m_prover_id;
415 std::vector<uint8_t> m_verifier_id;
416 std::vector<uint8_t> m_context;
417 std::vector<uint8_t> m_expected_confirmation;
418 secure_vector<uint8_t> m_shared_secret;
419 State m_state = State::Initial;
420};
421
422} // namespace Botan::SPAKE2p
423
424#endif
#define BOTAN_PUBLIC_API(maj, min)
Definition api.h:21
std::vector< uint8_t > process_message(std::span< const uint8_t > peer_message, RandomNumberGenerator &rng)
Definition spake2p.cpp:340
std::vector< uint8_t > generate_message(RandomNumberGenerator &rng)
Definition spake2p.cpp:324
const SystemParameters & parameters() const
Definition spake2p.h:322
secure_vector< uint8_t > shared_secret() const
Definition spake2p.cpp:387
ProverContext(const SystemParameters &params, const ProverSecret &secret, std::span< const uint8_t > prover_id, std::span< const uint8_t > verifier_id, std::span< const uint8_t > context={})
Definition spake2p.cpp:313
static ProverSecret from_prehashed(EC_Scalar w0, EC_Scalar w1)
Definition spake2p.cpp:292
static ProverSecret from_password(const SystemParameters &params, std::string_view password, std::span< const uint8_t > prover_id, std::span< const uint8_t > verifier_id, std::span< const uint8_t > salt)
Definition spake2p.cpp:283
static ProverSecret deserialize(const SystemParameters &params, std::span< const uint8_t > secret)
Definition spake2p.cpp:296
RegistrationRecord registration_record(RandomNumberGenerator &rng) const
Definition spake2p.cpp:308
secure_vector< uint8_t > serialize() const
Definition spake2p.cpp:304
secure_vector< uint8_t > serialize() const
Definition spake2p.cpp:279
static RegistrationRecord deserialize(const SystemParameters &params, std::span< const uint8_t > record)
Definition spake2p.cpp:261
static RegistrationRecord from_password(const SystemParameters &params, std::string_view password, std::span< const uint8_t > prover_id, std::span< const uint8_t > verifier_id, std::span< const uint8_t > salt, RandomNumberGenerator &rng)
Definition spake2p.cpp:252
const EC_Group & group() const
Definition spake2p.h:115
const EC_AffinePoint & spake2p_n() const
Definition spake2p.h:125
static SystemParameters rfc9383_p256_sha512()
Definition spake2p.cpp:201
static SystemParameters rfc9383_p521_sha512()
Definition spake2p.cpp:216
static SystemParameters rfc9383_p384_sha256()
Definition spake2p.cpp:206
const std::string & hash_function() const
Definition spake2p.h:130
static SystemParameters rfc9383_p256_sha256()
Definition spake2p.cpp:196
static SystemParameters rfc9383_p384_sha512()
Definition spake2p.cpp:211
const EC_AffinePoint & spake2p_m() const
Definition spake2p.h:120
static SystemParameters custom(const EC_Group &group, std::span< const uint8_t > seed, std::string_view hash_fn)
Definition spake2p.cpp:221
std::vector< uint8_t > process_message(std::span< const uint8_t > peer_message, RandomNumberGenerator &rng)
Definition spake2p.cpp:403
VerifierContext(const SystemParameters &params, const RegistrationRecord &record, std::span< const uint8_t > prover_id, std::span< const uint8_t > verifier_id, std::span< const uint8_t > context={})
Definition spake2p.cpp:392
const SystemParameters & parameters() const
Definition spake2p.h:407
secure_vector< uint8_t > shared_secret() const
Definition spake2p.cpp:461
void verify_confirmation(std::span< const uint8_t > confirmation)
Definition spake2p.cpp:440
std::vector< T, secure_allocator< T > > secure_vector
Definition secmem.h:128