2 #![cfg_attr(docsrs, feature(doc_cfg))]
3 #![doc = include_str!("../README.md")]
5 html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg",
6 html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg"
8 #![forbid(unsafe_code)]
9 #![warn(missing_docs, rust_2018_idioms, unused_qualifications)]
13 //! Alice and Bob both initialize their SPAKE2 instances with the same (weak)
14 //! password. They will exchange messages to (hopefully) derive a shared secret
15 //! key. The protocol is symmetric: for each operation that Alice does, Bob will
18 //! However, there are two roles in the SPAKE2 protocol, "A" and "B". The two
19 //! sides must agree ahead of time which one will play which role (the
20 //! messages they generate depend upon which side they play). There are two
21 //! separate constructor functions, `start_a()` and `start_b()`, and a
22 //! complete interaction will use one of each (one `start_a` on one computer,
23 //! and one `start_b` on the other computer).
25 //! Each instance of a SPAKE2 protocol uses a set of shared parameters. These
26 //! include a group, a generator, and a pair of arbitrary group elements.
27 //! This library comes a single pre-generated parameter set, but could be
28 //! extended with others.
30 //! You start by calling `start_a()` (or `_b)` with the password and identity
31 //! strings for both sides. This gives you back a state object and the first
32 //! message, which you must send to your partner. Once you receive the
33 //! corresponding inbound message, you pass it into the state object
34 //! (consuming both in the process) by calling `s.finish()`, and you get back
35 //! the shared key as a bytestring.
37 //! The password and identity strings must each be wrapped in a "newtype",
38 //! which is a simple `struct` that protects against swapping the different
39 //! types of bytestrings.
41 //! Thus a client-side program start with:
44 //! use spake2::{Ed25519Group, Identity, Password, Spake2};
45 //! # fn send(msg: &[u8]) {}
46 //! let (s1, outbound_msg) = Spake2::<Ed25519Group>::start_a(
47 //! &Password::new(b"password"),
48 //! &Identity::new(b"client id string"),
49 //! &Identity::new(b"server id string"));
50 //! send(&outbound_msg);
52 //! # fn receive() -> Vec<u8> { let (s2, i2) = Spake2::<Ed25519Group>::start_b(&Password::new(b"password"), &Identity::new(b"client id string"), &Identity::new(b"server id string")); i2 }
53 //! let inbound_msg = receive();
54 //! let key1 = s1.finish(&inbound_msg).unwrap();
57 //! while the server-side might do:
60 //! # fn send(msg: &[u8]) {}
61 //! use spake2::{Ed25519Group, Identity, Password, Spake2};
62 //! let (s1, outbound_msg) = Spake2::<Ed25519Group>::start_b(
63 //! &Password::new(b"password"),
64 //! &Identity::new(b"client id string"),
65 //! &Identity::new(b"server id string"));
66 //! send(&outbound_msg);
68 //! # fn receive() -> Vec<u8> { let (s2, i2) = Spake2::<Ed25519Group>::start_a(&Password::new(b"password"), &Identity::new(b"client id string"), &Identity::new(b"server id string")); i2 }
69 //! let inbound_msg = receive();
70 //! let key2 = s1.finish(&inbound_msg).unwrap();
73 //! If both sides used the same password, and there is no man-in-the-middle,
74 //! then `key1` and `key2` will be identical. If not, the two sides will get
75 //! different keys. When one side encrypts with `key1`, and the other side
76 //! attempts to decrypt with `key2`, they'll get nothing but garbled noise.
78 //! The shared key can be used as an HMAC key to provide data integrity on
79 //! subsequent messages, or as an authenticated-encryption key (e.g.
80 //! nacl.secretbox). It can also be fed into [HKDF][1] to derive other
81 //! session keys as necessary.
83 //! The `SPAKE2` instances, and the messages they create, are single-use. Create
84 //! a new one for each new session. `finish` consumes the instance.
88 //! A single SPAKE2 instance must be used asymmetrically: the two sides must
89 //! somehow decide (ahead of time) which role they will each play. The
90 //! implementation includes the side identifier in the exchanged message to
91 //! guard against a `start_a` talking to another `start_a`. Typically a
92 //! "client" will take on the `A` role, and the "server" will be `B`.
94 //! This is a nuisance for more egalitarian protocols, where there's no clear
95 //! way to assign these roles ahead of time. In this case, use
96 //! `start_symmetric()` on both sides. This uses a different set of
97 //! parameters (so it is not interoperable with `start_A` or `start_b`), but
98 //! should otherwise behave the same way. The symmetric mode uses only one
99 //! identity string, not two.
104 //! # fn send(msg: &[u8]) {}
105 //! use spake2::{Ed25519Group, Identity, Password, Spake2};
106 //! let (s1, outbound_msg) = Spake2::<Ed25519Group>::start_symmetric(
107 //! &Password::new(b"password"),
108 //! &Identity::new(b"shared id string"));
109 //! send(&outbound_msg);
111 //! # fn receive() -> Vec<u8> { let (s2, i2) = Spake2::<Ed25519Group>::start_symmetric(&Password::new(b"password"), &Identity::new(b"shared id string")); i2 }
112 //! let inbound_msg = receive();
113 //! let key1 = s1.finish(&inbound_msg).unwrap();
116 //! Dave does exactly the same:
119 //! # fn send(msg: &[u8]) {}
120 //! use spake2::{Ed25519Group, Identity, Password, Spake2};
121 //! let (s1, outbound_msg) = Spake2::<Ed25519Group>::start_symmetric(
122 //! &Password::new(b"password"),
123 //! &Identity::new(b"shared id string"));
124 //! send(&outbound_msg);
126 //! # fn receive() -> Vec<u8> { let (s2, i2) = Spake2::<Ed25519Group>::start_symmetric(&Password::new(b"password"), &Identity::new(b"shared id string")); i2 }
127 //! let inbound_msg = receive();
128 //! let key1 = s1.finish(&inbound_msg).unwrap();
131 //! # Identifier Strings
133 //! The SPAKE2 protocol includes a pair of "identity strings" `idA` and `idB`
134 //! that are included in the final key-derivation hash. This binds the key to a
135 //! single pair of parties, or for some specific purpose.
137 //! For example, when user "alice" logs into "example.com", both sides should set
138 //! `idA = b"alice"` and `idB = b"example.com"`. This prevents an attacker from
139 //! substituting messages from unrelated login sessions (other users on the same
140 //! server, or other servers for the same user).
142 //! This also makes sure the session is established with the correct service. If
143 //! Alice has one password for "example.com" but uses it for both login and
144 //! file-transfer services, `idB` should be different for the two services.
145 //! Otherwise if Alice is simultaneously connecting to both services, and
146 //! attacker could rearrange the messages and cause her login client to connect
147 //! to the file-transfer server, and vice versa.
149 //! `idA` and `idB` must be bytestrings (slices of `<u8>`).
151 //! `start_symmetric` uses a single `idSymmetric=` string, instead of `idA`
152 //! and `idB`. Both sides must provide the same `idSymmetric=`, or leave it
157 //! Sometimes, you can't hold the SPAKE2 instance in memory for the whole
158 //! negotiation: perhaps all your program state is stored in a database, and
159 //! nothing lives in RAM for more than a few moments.
161 //! Unfortunately the Rust implementation does not yet provide serialization
162 //! of the state object. A future version should correct this.
166 //! This library is probably not constant-time, and does not protect against
167 //! timing attacks. Do not allow attackers to measure how long it takes you
168 //! to create or respond to a message. This matters somewhat less for pairing
169 //! protocols, because their passwords are single-use randomly-generated
170 //! keys, so an attacker has much less to work with.
172 //! This library depends upon a strong source of random numbers. Do not use it on
173 //! a system where os.urandom() is weak.
177 //! To run the built-in speed tests, just run `cargo bench`.
179 //! SPAKE2 consists of two phases, separated by a single message exchange.
180 //! The time these phases take is split roughly 50/50. On my 2.8GHz Core-i7
181 //! (i7-7600U) cpu, the built-in Ed25519Group parameters take about 112
182 //! microseconds for each phase, and the message exchanged is 33 bytes long.
186 //! Run `cargo test` to run the built-in test suite.
190 //! The protocol was described as "PAKE2" in ["cryptobook"] [2] from Dan Boneh
191 //! and Victor Shoup. This is a form of "SPAKE2", defined by Abdalla and
192 //! Pointcheval at [RSA 2005] [3]. Additional recommendations for groups and
193 //! distinguished elements were published in [Ladd's IETF draft] [4].
195 //! The Ed25519 implementation uses code adapted from Daniel Bernstein (djb),
196 //! Matthew Dempsky, Daniel Holth, Ron Garret, with further optimizations by
197 //! Brian Warner[5]. The "arbitrary element" computation, which must be the same
198 //! for both participants, is from python-pure25519 version 0.5.
200 //! The Boneh/Shoup chapter that defines PAKE2 also defines an augmented variant
201 //! named "PAKE2+", which changes one side (typically a server) to record a
202 //! derivative of the password instead of the actual password. In PAKE2+, a
203 //! server compromise does not immediately give access to the passwords: instead,
204 //! the attacker must perform an offline dictionary attack against the stolen
205 //! data before they can learn the passwords. PAKE2+ support is planned, but not
208 //! The security of the symmetric case was proved by Kobara/Imai[6] in 2003, and
209 //! uses different (slightly weaker?) reductions than that of the asymmetric
210 //! form. See also Mike Hamburg's analysis[7] from 2015.
212 //! Brian Warner first wrote the Python version in July 2010. He wrote this
213 //! Rust version in in May 2017.
217 //! [1]: https://tools.ietf.org/html/rfc5869 "HKDF"
218 //! [2]: http://crypto.stanford.edu/~dabo/cryptobook/ "cryptobook"
219 //! [3]: http://www.di.ens.fr/~pointche/Documents/Papers/2005_rsa.pdf "RSA 2005"
220 //! [4]: https://tools.ietf.org/html/draft-ladd-spake2-01 "Ladd's IETF draft"
221 //! [5]: https://github.com/warner/python-pure25519
222 //! [6]: http://eprint.iacr.org/2003/038.pdf "Pretty-Simple Password-Authenticated Key-Exchange Under Standard Assumptions"
223 //! [7]: https://moderncrypto.org/mail-archive/curves/2015/000419.html "PAKE questions"
225 #[allow(unused_imports)]
229 #[cfg(feature = "std")]
230 #[cfg_attr(test, macro_use)]
234 use core::{fmt, ops::Deref, str};
235 use curve25519_dalek::{
236 constants::ED25519_BASEPOINT_POINT,
237 edwards::{CompressedEdwardsY, EdwardsPoint as c2_Element},
238 scalar::Scalar as c2_Scalar,
241 use rand_core::{CryptoRng, RngCore};
242 use sha2::{Digest, Sha256};
244 #[cfg(feature = "getrandom")]
245 use rand_core::OsRng;
248 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
260 impl fmt::Display for Error {
261 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
263 Error::BadSide => fmt.write_str("bad side"),
264 Error::CorruptMessage => fmt.write_str("corrupt message"),
265 Error::WrongLength => fmt.write_str("invalid length"),
270 #[cfg(feature = "std")]
271 impl std::error::Error for Error {}
274 // TODO(tarcieri): avoid allocation?
275 #[derive(PartialEq, Eq, Clone)]
276 pub struct Password(Vec<u8>);
279 /// Create a new password
280 pub fn new(p: &[u8]) -> Password {
285 impl Deref for Password {
286 type Target = Vec<u8>;
288 fn deref(&self) -> &Vec<u8> {
294 // TODO(tarcieri): avoid allocation?
295 #[derive(PartialEq, Eq, Clone)]
296 pub struct Identity(Vec<u8>);
298 impl Deref for Identity {
299 type Target = Vec<u8>;
301 fn deref(&self) -> &Vec<u8> {
307 /// Create a new identity
308 pub fn new(p: &[u8]) -> Identity {
314 // TODO(tarcieri): replace with `group` crate?
319 /// Base field element
326 fn name() -> &'static str;
329 fn const_m() -> Self::Element;
332 fn const_n() -> Self::Element;
335 fn const_s() -> Self::Element;
338 fn hash_to_scalar(s: &[u8]) -> Self::Scalar;
340 /// Generate a random scalar
341 fn random_scalar<T>(cspring: &mut T) -> Self::Scalar
343 T: RngCore + CryptoRng;
346 fn scalar_neg(s: &Self::Scalar) -> Self::Scalar;
348 /// Convert base field element to bytes
349 fn element_to_bytes(e: &Self::Element) -> Vec<u8>;
351 /// Convert bytes to base field element
352 fn bytes_to_element(b: &[u8]) -> Option<Self::Element>;
354 /// Length of a base field element
355 fn element_length() -> usize;
357 /// Fixed-base scalar multiplication
358 fn basepoint_mult(s: &Self::Scalar) -> Self::Element;
360 /// Variable-base scalar multiplication
361 fn scalarmult(e: &Self::Element, s: &Self::Scalar) -> Self::Element;
364 fn add(a: &Self::Element, b: &Self::Element) -> Self::Element;
367 /// Ed25519 elliptic curve group
368 #[derive(Debug, PartialEq, Eq)]
369 pub struct Ed25519Group;
371 impl Group for Ed25519Group {
372 type Scalar = c2_Scalar;
373 type Element = c2_Element;
374 type TranscriptHash = Sha256;
376 fn name() -> &'static str {
380 fn const_m() -> c2_Element {
381 // python -c "import binascii, spake2; b=binascii.hexlify(spake2.ParamsEd25519.M.to_bytes()); print(', '.join(['0x'+b[i:i+2] for i in range(0,len(b),2)]))"
382 // 15cfd18e385952982b6a8f8c7854963b58e34388c8e6dae891db756481a02312
384 0x15, 0xcf, 0xd1, 0x8e, 0x38, 0x59, 0x52, 0x98, 0x2b, 0x6a, 0x8f, 0x8c, 0x78, 0x54,
385 0x96, 0x3b, 0x58, 0xe3, 0x43, 0x88, 0xc8, 0xe6, 0xda, 0xe8, 0x91, 0xdb, 0x75, 0x64,
386 0x81, 0xa0, 0x23, 0x12,
392 fn const_n() -> c2_Element {
393 // python -c "import binascii, spake2; b=binascii.hexlify(spake2.ParamsEd25519.N.to_bytes()); print(', '.join(['0x'+b[i:i+2] for i in range(0,len(b),2)]))"
394 // f04f2e7eb734b2a8f8b472eaf9c3c632576ac64aea650b496a8a20ff00e583c3
396 0xf0, 0x4f, 0x2e, 0x7e, 0xb7, 0x34, 0xb2, 0xa8, 0xf8, 0xb4, 0x72, 0xea, 0xf9, 0xc3,
397 0xc6, 0x32, 0x57, 0x6a, 0xc6, 0x4a, 0xea, 0x65, 0x0b, 0x49, 0x6a, 0x8a, 0x20, 0xff,
398 0x00, 0xe5, 0x83, 0xc3,
404 fn const_s() -> c2_Element {
405 // python -c "import binascii, spake2; b=binascii.hexlify(spake2.ParamsEd25519.S.to_bytes()); print(', '.join(['0x'+b[i:i+2] for i in range(0,len(b),2)]))"
406 // 6f00dae87c1be1a73b5922ef431cd8f57879569c222d22b1cd71e8546ab8e6f1
408 0x6f, 0x00, 0xda, 0xe8, 0x7c, 0x1b, 0xe1, 0xa7, 0x3b, 0x59, 0x22, 0xef, 0x43, 0x1c,
409 0xd8, 0xf5, 0x78, 0x79, 0x56, 0x9c, 0x22, 0x2d, 0x22, 0xb1, 0xcd, 0x71, 0xe8, 0x54,
410 0x6a, 0xb8, 0xe6, 0xf1,
416 fn hash_to_scalar(s: &[u8]) -> c2_Scalar {
417 ed25519_hash_to_scalar(s)
420 fn random_scalar<T>(cspring: &mut T) -> c2_Scalar
422 T: RngCore + CryptoRng,
424 c2_Scalar::random(cspring)
427 fn scalar_neg(s: &c2_Scalar) -> c2_Scalar {
431 fn element_to_bytes(s: &c2_Element) -> Vec<u8> {
432 s.compress().as_bytes().to_vec()
435 fn element_length() -> usize {
439 fn bytes_to_element(b: &[u8]) -> Option<c2_Element> {
444 let mut bytes = [0u8; 32];
445 bytes.copy_from_slice(b);
447 let cey = CompressedEdwardsY(bytes);
451 fn basepoint_mult(s: &c2_Scalar) -> c2_Element {
452 ED25519_BASEPOINT_POINT * s
454 fn scalarmult(e: &c2_Element, s: &c2_Scalar) -> c2_Element {
458 fn add(a: &c2_Element, b: &c2_Element) -> c2_Element {
463 fn ed25519_hash_to_scalar(s: &[u8]) -> c2_Scalar {
464 //c2_Scalar::hash_from_bytes::<Sha512>(&s)
466 // h = HKDF(salt=b"", ikm=s, hash=SHA256, info=b"SPAKE2 pw", len=32+16)
470 let mut okm = [0u8; 32 + 16];
471 Hkdf::<Sha256>::new(Some(b""), s)
472 .expand(b"SPAKE2 pw", &mut okm)
474 //println!("expanded: {}{}", "................................", okm.iter().to_hex()); // ok
476 let mut reducible = [0u8; 64]; // little-endian
477 for (i, x) in okm.iter().enumerate().take(32 + 16) {
478 reducible[32 + 16 - 1 - i] = *x;
480 //println!("reducible: {}", reducible.iter().to_hex());
481 c2_Scalar::from_bytes_mod_order_wide(&reducible)
482 //let reduced = c2_Scalar::reduce(&reducible);
483 //println!("reduced: {}", reduced.as_bytes().to_hex());
496 assert_eq!(first_msg.len(), 32);
497 assert_eq!(second_msg.len(), 32);
498 // the transcript is fixed-length, made up of 6 32-byte values:
499 // byte 0-31 : sha256(pw)
500 // byte 32-63 : sha256(idA)
501 // byte 64-95 : sha256(idB)
502 // byte 96-127 : X_msg
503 // byte 128-159: Y_msg
504 // byte 160-191: K_bytes
505 let mut transcript = [0u8; 6 * 32];
507 let mut pw_hash = Sha256::new();
508 pw_hash.update(password_vec);
509 transcript[0..32].copy_from_slice(&pw_hash.finalize());
511 let mut ida_hash = Sha256::new();
512 ida_hash.update(id_a);
513 transcript[32..64].copy_from_slice(&ida_hash.finalize());
515 let mut idb_hash = Sha256::new();
516 idb_hash.update(id_b);
517 transcript[64..96].copy_from_slice(&idb_hash.finalize());
519 transcript[96..128].copy_from_slice(first_msg);
520 transcript[128..160].copy_from_slice(second_msg);
521 transcript[160..192].copy_from_slice(key_bytes);
523 //println!("transcript: {:?}", transcript.iter().to_hex());
525 //let mut hash = G::TranscriptHash::default();
526 let mut hash = Sha256::new();
527 hash.update(transcript.to_vec());
528 hash.finalize().to_vec()
531 fn ed25519_hash_symmetric(
538 assert_eq!(msg_u.len(), 32);
539 assert_eq!(msg_v.len(), 32);
540 // # since we don't know which side is which, we must sort the messages
541 // first_msg, second_msg = sorted([msg1, msg2])
542 // transcript = b"".join([sha256(pw).digest(),
543 // sha256(idSymmetric).digest(),
544 // first_msg, second_msg, K_bytes])
546 // the transcript is fixed-length, made up of 5 32-byte values:
547 // byte 0-31 : sha256(pw)
548 // byte 32-63 : sha256(idSymmetric)
549 // byte 64-95 : X_msg
550 // byte 96-127 : Y_msg
551 // byte 128-159: K_bytes
552 let mut transcript = [0u8; 5 * 32];
554 let mut pw_hash = Sha256::new();
555 pw_hash.update(password_vec);
556 transcript[0..32].copy_from_slice(&pw_hash.finalize());
558 let mut ids_hash = Sha256::new();
559 ids_hash.update(id_s);
560 transcript[32..64].copy_from_slice(&ids_hash.finalize());
563 transcript[64..96].copy_from_slice(msg_u);
564 transcript[96..128].copy_from_slice(msg_v);
566 transcript[64..96].copy_from_slice(msg_v);
567 transcript[96..128].copy_from_slice(msg_u);
569 transcript[128..160].copy_from_slice(key_bytes);
571 let mut hash = Sha256::new();
572 hash.update(transcript.to_vec());
573 hash.finalize().to_vec()
576 /* "session type pattern" */
578 #[derive(Debug, PartialEq, Eq)]
585 /// SPAKE2 algorithm.
586 #[derive(Eq, PartialEq)]
587 pub struct Spake2<G: Group> {
588 //where &G::Scalar: Neg {
590 xy_scalar: G::Scalar,
591 password_vec: Vec<u8>,
596 password_scalar: G::Scalar,
599 impl<G: Group> Spake2<G> {
606 xy_scalar: G::Scalar,
607 ) -> (Spake2<G>, Vec<u8>) {
608 //let password_scalar: G::Scalar = hash_to_scalar::<G::Scalar>(password);
609 let password_scalar: G::Scalar = G::hash_to_scalar(password);
613 // sym: X = B*x * S*pw
614 let blinding = match side {
615 Side::A => G::const_m(),
616 Side::B => G::const_n(),
617 Side::Symmetric => G::const_s(),
619 let m1: G::Element = G::add(
620 &G::basepoint_mult(&xy_scalar),
621 &G::scalarmult(&blinding, &password_scalar),
623 //let m1: G::Element = &G::basepoint_mult(&x) + &(blinding * &password_scalar);
624 let msg1: Vec<u8> = G::element_to_bytes(&m1);
625 let mut password_vec = Vec::new();
626 password_vec.extend_from_slice(password);
627 let mut id_a_copy = Vec::new();
628 id_a_copy.extend_from_slice(id_a);
629 let mut id_b_copy = Vec::new();
630 id_b_copy.extend_from_slice(id_b);
631 let mut id_s_copy = Vec::new();
632 id_s_copy.extend_from_slice(id_s);
634 let mut msg_and_side = vec![match side {
635 Side::A => 0x41, // 'A'
636 Side::B => 0x42, // 'B'
637 Side::Symmetric => 0x53, // 'S'
639 msg_and_side.extend_from_slice(&msg1);
645 password_vec, // string
650 password_scalar, // scalar
660 xy_scalar: G::Scalar,
661 ) -> (Spake2<G>, Vec<u8>) {
662 Self::start_internal(
676 xy_scalar: G::Scalar,
677 ) -> (Spake2<G>, Vec<u8>) {
678 Self::start_internal(
688 fn start_symmetric_internal(
691 xy_scalar: G::Scalar,
692 ) -> (Spake2<G>, Vec<u8>) {
693 Self::start_internal(
703 /// Start with identity `a`.
705 /// Uses the system RNG.
706 #[cfg(feature = "getrandom")]
707 #[cfg_attr(docsrs, doc(cfg(feature = "getrandom")))]
708 pub fn start_a(password: &Password, id_a: &Identity, id_b: &Identity) -> (Spake2<G>, Vec<u8>) {
709 Self::start_a_with_rng(password, id_a, id_b, OsRng)
712 /// Start with identity `b`.
714 /// Uses the system RNG.
715 #[cfg(feature = "getrandom")]
716 #[cfg_attr(docsrs, doc(cfg(feature = "getrandom")))]
717 pub fn start_b(password: &Password, id_a: &Identity, id_b: &Identity) -> (Spake2<G>, Vec<u8>) {
718 Self::start_b_with_rng(password, id_a, id_b, OsRng)
721 /// Start with symmetric identity.
723 /// Uses the system RNG.
724 #[cfg(feature = "getrandom")]
725 #[cfg_attr(docsrs, doc(cfg(feature = "getrandom")))]
726 pub fn start_symmetric(password: &Password, id_s: &Identity) -> (Spake2<G>, Vec<u8>) {
727 Self::start_symmetric_with_rng(password, id_s, OsRng)
730 /// Start with identity `a` and the provided cryptographically secure RNG.
731 pub fn start_a_with_rng(
735 mut csrng: impl CryptoRng + RngCore,
736 ) -> (Spake2<G>, Vec<u8>) {
737 let xy_scalar: G::Scalar = G::random_scalar(&mut csrng);
738 Self::start_a_internal(password, id_a, id_b, xy_scalar)
741 /// Start with identity `b` and the provided cryptographically secure RNG.
742 pub fn start_b_with_rng(
746 mut csrng: impl CryptoRng + RngCore,
747 ) -> (Spake2<G>, Vec<u8>) {
748 let xy_scalar: G::Scalar = G::random_scalar(&mut csrng);
749 Self::start_b_internal(password, id_a, id_b, xy_scalar)
752 /// Start with symmetric identity and the provided cryptographically secure RNG.
753 pub fn start_symmetric_with_rng(
756 mut csrng: impl CryptoRng + RngCore,
757 ) -> (Spake2<G>, Vec<u8>) {
758 let xy_scalar: G::Scalar = G::random_scalar(&mut csrng);
759 Self::start_symmetric_internal(password, id_s, xy_scalar)
763 pub fn finish(self, msg2: &[u8]) -> Result<Vec<u8>, Error> {
764 if msg2.len() != 1 + G::element_length() {
765 return Err(Error::WrongLength);
767 let msg_side = msg2[0];
770 Side::A => match msg_side {
772 _ => return Err(Error::BadSide),
774 Side::B => match msg_side {
776 _ => return Err(Error::BadSide),
778 Side::Symmetric => match msg_side {
780 _ => return Err(Error::BadSide),
784 let msg2_element = match G::bytes_to_element(&msg2[1..]) {
786 None => return Err(Error::CorruptMessage),
789 // a: K = (Y+N*(-pw))*x
790 // b: K = (X+M*(-pw))*y
791 let unblinding = match self.side {
792 Side::A => G::const_n(),
793 Side::B => G::const_m(),
794 Side::Symmetric => G::const_s(),
796 let tmp1 = G::scalarmult(&unblinding, &G::scalar_neg(&self.password_scalar));
797 let tmp2 = G::add(&msg2_element, &tmp1);
798 let key_element = G::scalarmult(&tmp2, &self.xy_scalar);
799 let key_bytes = G::element_to_bytes(&key_element);
801 // key = H(H(pw) + H(idA) + H(idB) + X + Y + K)
802 //transcript = b"".join([sha256(pw).digest(),
803 // sha256(idA).digest(), sha256(idB).digest(),
804 // X_msg, Y_msg, K_bytes])
805 //key = sha256(transcript).digest()
806 // note that both sides must use the same order
809 Side::A => ed25519_hash_ab(
813 self.msg1.as_slice(),
817 Side::B => ed25519_hash_ab(
822 self.msg1.as_slice(),
825 Side::Symmetric => ed25519_hash_symmetric(
836 impl<G: Group> fmt::Debug for Spake2<G> {
837 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
838 fmt.debug_struct("SPAKE2")
839 .field("group", &G::name())
840 .field("side", &self.side)
841 .field("idA", &MaybeUtf8(&self.id_a))
842 .field("idB", &MaybeUtf8(&self.id_b))
843 .field("idS", &MaybeUtf8(&self.id_s))
848 struct MaybeUtf8<'a>(&'a [u8]);
850 impl fmt::Debug for MaybeUtf8<'_> {
851 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
852 if let Ok(s) = str::from_utf8(self.0) {
853 write!(fmt, "(s={})", s)
855 write!(fmt, "(hex=")?;
858 write!(fmt, "{:x}", byte)?;