spake2-0.4.0/.cargo_vcs_info.json0000644000000001440000000000100122210ustar { "git": { "sha1": "3e126d74b692a20d88f6ec62c770ac0c93ab002c" }, "path_in_vcs": "spake2" }spake2-0.4.0/CHANGELOG.md000064400000000000000000000031771046102023000126330ustar 00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## 0.4.0 (2023-07-23) ### Changed - Move IDs to relevant `Side` enum variants ([#114]) - MSRV 1.60 ([#115]) - Bump `curve25519-dalek` to v4.0 release ([#138]) [#114]: https://github.com/RustCrypto/PAKEs/pull/114 [#115]: https://github.com/RustCrypto/PAKEs/pull/115 [#138]: https://github.com/RustCrypto/PAKEs/pull/138 ## 0.3.1 (2022-01-22) ### Changed - Refactor internals ([#91]) [#91]: https://github.com/RustCrypto/PAKEs/pull/91 ## 0.3.0 (2022-01-22) [YANKED] ### Added - Initial `no_std` support ([#87]) - `getrandom` feature ([#88]) ### Changed - 2021 edition upgrade; MSRV 1.56 ([#80]) - Bump `curve25519-dalek` to v3.0 ([#85]) - Replace `rand` with `rand_core` v0.5 ([#85]) - Bump `hkdf` to v0.12 ([#86]) - Bump `sha2` to v0.10 ([#86]) - Renamed `SPAKE2` => `Spake2` ([#89]) - Renamed `SPAKEErr` => `Error` ([#89]) [#80]: https://github.com/RustCrypto/PAKEs/pull/80 [#85]: https://github.com/RustCrypto/PAKEs/pull/85 [#86]: https://github.com/RustCrypto/PAKEs/pull/86 [#87]: https://github.com/RustCrypto/PAKEs/pull/87 [#88]: https://github.com/RustCrypto/PAKEs/pull/88 [#89]: https://github.com/RustCrypto/PAKEs/pull/89 ## 0.2.0 (2018-12-20) ## 0.1.1 (2018-10-16) ## 0.1.0 (2018-08-21) ## 0.0.9 (2018-08-21) ## 0.0.8 (2018-05-26) ## 0.0.7 (2018-05-25) ## 0.0.6 (2018-05-23) ## 0.0.5 (2018-04-29) ## 0.0.4 (2018-01-28) ## 0.0.3 (2017-11-29) ## 0.0.2 (2017-09-21) ## 0.0.1 (2017-08-01) spake2-0.4.0/Cargo.toml0000644000000032040000000000100102170ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.60" name = "spake2" version = "0.4.0" authors = ["Brian Warner "] exclude = [".gitignore"] description = "The SPAKE2 password-authenticated key-exchange algorithm." homepage = "https://github.com/RustCrypto/PAKEs" documentation = "https://docs.rs/spake2" readme = "README.md" keywords = [ "crypto", "pake", "authentication", ] categories = [ "cryptography", "authentication", ] license = "MIT OR Apache-2.0" repository = "https://github.com/RustCrypto/PAKEs/tree/master/spake2" [package.metadata.docs.rs] all-features = true rustdoc-args = [ "--cfg", "docsrs", ] [[bench]] name = "spake2" harness = false [dependencies.curve25519-dalek] version = "4" features = ["rand_core"] default-features = false [dependencies.hkdf] version = "0.12" default-features = false [dependencies.rand_core] version = "0.6" default-features = false [dependencies.sha2] version = "0.10" default-features = false [dev-dependencies.bencher] version = "0.1" [dev-dependencies.hex] version = "0.4" [dev-dependencies.num-bigint] version = "0.4" [features] default = ["getrandom"] getrandom = ["rand_core/getrandom"] std = [] spake2-0.4.0/Cargo.toml.orig0000644000000020710000000000100111570ustar [package] name = "spake2" version = "0.4.0" authors = ["Brian Warner "] description = "The SPAKE2 password-authenticated key-exchange algorithm." documentation = "https://docs.rs/spake2" homepage = "https://github.com/RustCrypto/PAKEs" repository = "https://github.com/RustCrypto/PAKEs/tree/master/spake2" license = "MIT OR Apache-2.0" keywords = ["crypto", "pake", "authentication"] categories = ["cryptography", "authentication"] exclude = [".gitignore"] readme = "README.md" edition = "2021" rust-version = "1.60" [dependencies] curve25519-dalek = { version = "4", default-features = false, features = ["rand_core"] } rand_core = { version = "0.6", default-features = false } sha2 = { version = "0.10", default-features = false } hkdf = { version = "0.12", default-features = false } [dev-dependencies] bencher = "0.1" hex = "0.4" num-bigint = "0.4" [features] default = ["getrandom"] getrandom = ["rand_core/getrandom"] std = [] [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] [[bench]] name = "spake2" harness = false spake2-0.4.0/Cargo.toml.orig000064400000000000000000000020711046102023000137010ustar 00000000000000[package] name = "spake2" version = "0.4.0" authors = ["Brian Warner "] description = "The SPAKE2 password-authenticated key-exchange algorithm." documentation = "https://docs.rs/spake2" homepage = "https://github.com/RustCrypto/PAKEs" repository = "https://github.com/RustCrypto/PAKEs/tree/master/spake2" license = "MIT OR Apache-2.0" keywords = ["crypto", "pake", "authentication"] categories = ["cryptography", "authentication"] exclude = [".gitignore"] readme = "README.md" edition = "2021" rust-version = "1.60" [dependencies] curve25519-dalek = { version = "4", default-features = false, features = ["rand_core"] } rand_core = { version = "0.6", default-features = false } sha2 = { version = "0.10", default-features = false } hkdf = { version = "0.12", default-features = false } [dev-dependencies] bencher = "0.1" hex = "0.4" num-bigint = "0.4" [features] default = ["getrandom"] getrandom = ["rand_core/getrandom"] std = [] [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] [[bench]] name = "spake2" harness = false spake2-0.4.0/LICENSE-APACHE000064400000000000000000000251411046102023000127410ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. spake2-0.4.0/LICENSE-MIT000064400000000000000000000020621046102023000124460ustar 00000000000000MIT License Copyright (c) 2017-2023 Brian Warner Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. spake2-0.4.0/README.md000064400000000000000000000117721046102023000123010ustar 00000000000000# [RustCrypto]: SPAKE2 [![crate][crate-image]][crate-link] [![Docs][docs-image]][docs-link] ![Apache2/MIT licensed][license-image] ![Rust Version][rustc-image] [![Project Chat][chat-image]][chat-link] [![Build Status][build-image]][build-link] Pure Rust implementation of the [SPAKE2] password-authenticated key-exchange algorithm. [Documentation][docs-link] ## About This library implements the SPAKE2 password-authenticated key exchange ("PAKE") algorithm. This allows two parties, who share a weak password, to safely derive a strong shared secret (and therefore build an encrypted+authenticated channel). A passive attacker who eavesdrops on the connection learns no information about the password or the generated secret. An active attacker (man-in-the-middle) gets exactly one guess at the password, and unless they get it right, they learn no information about the password or the generated secret. Each execution of the protocol enables one guess. The use of a weak password is made safer by the rate-limiting of guesses: no off-line dictionary attack is available to the network-level attacker, and the protocol does not depend upon having previously-established confidentiality of the network (unlike e.g. sending a plaintext password over TLS). The protocol requires the exchange of one pair of messages, so only one round trip is necessary to establish the session key. If key-confirmation is necessary, that will require a second round trip. All messages are bytestrings. For the default security level (using the Ed25519 elliptic curve, roughly equivalent to an 128-bit symmetric key), the message is 33 bytes long. This implementation is generic over a `Group`, which defines the cyclic group to use, the functions which convert group elements and scalars to and from bytestrings, and the three distinctive group elements used in the blinding process. Only one such Group is implemented, named `Ed25519Group`, which provides fast operations and high security, and is compatible with my [python implementation](https://github.com/warner/python-spake2). # What Is It Good For? PAKE can be used in a pairing protocol, like the initial version of Firefox Sync (the one with J-PAKE), to introduce one device to another and help them share secrets. In this mode, one device creates a random code, the user copies that code to the second device, then both devices use the code as a one-time password and run the PAKE protocol. Once both devices have a shared strong key, they can exchange other secrets safely. PAKE can also be used (carefully) in a login protocol, where SRP is perhaps the best-known approach. Traditional non-PAKE login consists of sending a plaintext password through a TLS-encrypted channel, to a server which then checks it (by hashing/stretching and comparing against a stored verifier). In a PAKE login, both sides put the password into their PAKE protocol, and then confirm that their generated key is the same. This nominally does not require the initial TLS-protected channel. However note that it requires other, deeper design considerations (the PAKE protocol must be bound to whatever protected channel you end up using, else the attacker can wait for PAKE to complete normally and then steal the channel), and is not simply a drop-in replacement. In addition, the server cannot hash/stretch the password very much (see the note on "Augmented PAKE" below), so unless the client is willing to perform key-stretching before running PAKE, the server's stored verifier will be vulnerable to a low-cost dictionary attack. ## ⚠️ Security Warning This crate has never received an independent third party audit for security and correctness. USE AT YOUR OWN RISK! ## Minimum Supported Rust Version Rust **1.60** or higher. Minimum supported Rust version can be changed in the future, but it will be done with a minor version bump. ## License Licensed under either of: * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) * [MIT license](http://opensource.org/licenses/MIT) at your option. ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. [//]: # (badges) [crate-image]: https://img.shields.io/crates/v/spake2.svg [crate-link]: https://crates.io/crates/spake2 [docs-image]: https://docs.rs/spake2/badge.svg [docs-link]: https://docs.rs/spake2/ [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg [rustc-image]: https://img.shields.io/badge/rustc-1.60+-blue.svg [chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg [chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260045-PAKEs [build-image]: https://github.com/RustCrypto/PAKEs/actions/workflows/spake2.yml/badge.svg [build-link]: https://github.com/RustCrypto/PAKEs/actions/workflows/spake2.yml [//]: # (general links) [RustCrypto]: https://github.com/RustCrypto [SPAKE2]: https://tools.ietf.org/id/draft-irtf-cfrg-spake2-10.html spake2-0.4.0/src/ed25519.rs000064400000000000000000000155051046102023000131530ustar 00000000000000//! "Edwards25519" elliptic curve group. use crate::{c2_Element, c2_Scalar, Group}; use alloc::vec::Vec; use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, edwards::CompressedEdwardsY}; use hkdf::Hkdf; use rand_core::{CryptoRng, RngCore}; use sha2::{Digest, Sha256}; /// Ed25519 elliptic curve group. #[derive(Debug, PartialEq, Eq)] pub struct Ed25519Group; impl Group for Ed25519Group { type Scalar = c2_Scalar; type Element = c2_Element; type TranscriptHash = Sha256; fn name() -> &'static str { "Ed25519" } fn const_m() -> c2_Element { // 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)]))" // 15cfd18e385952982b6a8f8c7854963b58e34388c8e6dae891db756481a02312 CompressedEdwardsY([ 0x15, 0xcf, 0xd1, 0x8e, 0x38, 0x59, 0x52, 0x98, 0x2b, 0x6a, 0x8f, 0x8c, 0x78, 0x54, 0x96, 0x3b, 0x58, 0xe3, 0x43, 0x88, 0xc8, 0xe6, 0xda, 0xe8, 0x91, 0xdb, 0x75, 0x64, 0x81, 0xa0, 0x23, 0x12, ]) .decompress() .unwrap() } fn const_n() -> c2_Element { // 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)]))" // f04f2e7eb734b2a8f8b472eaf9c3c632576ac64aea650b496a8a20ff00e583c3 CompressedEdwardsY([ 0xf0, 0x4f, 0x2e, 0x7e, 0xb7, 0x34, 0xb2, 0xa8, 0xf8, 0xb4, 0x72, 0xea, 0xf9, 0xc3, 0xc6, 0x32, 0x57, 0x6a, 0xc6, 0x4a, 0xea, 0x65, 0x0b, 0x49, 0x6a, 0x8a, 0x20, 0xff, 0x00, 0xe5, 0x83, 0xc3, ]) .decompress() .unwrap() } fn const_s() -> c2_Element { // 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)]))" // 6f00dae87c1be1a73b5922ef431cd8f57879569c222d22b1cd71e8546ab8e6f1 CompressedEdwardsY([ 0x6f, 0x00, 0xda, 0xe8, 0x7c, 0x1b, 0xe1, 0xa7, 0x3b, 0x59, 0x22, 0xef, 0x43, 0x1c, 0xd8, 0xf5, 0x78, 0x79, 0x56, 0x9c, 0x22, 0x2d, 0x22, 0xb1, 0xcd, 0x71, 0xe8, 0x54, 0x6a, 0xb8, 0xe6, 0xf1, ]) .decompress() .unwrap() } fn hash_to_scalar(s: &[u8]) -> c2_Scalar { ed25519_hash_to_scalar(s) } fn random_scalar(cspring: &mut T) -> c2_Scalar where T: RngCore + CryptoRng, { c2_Scalar::random(cspring) } fn scalar_neg(s: &c2_Scalar) -> c2_Scalar { -s } fn element_to_bytes(s: &c2_Element) -> Vec { s.compress().as_bytes().to_vec() } fn element_length() -> usize { 32 } fn bytes_to_element(b: &[u8]) -> Option { if b.len() != 32 { return None; } let mut bytes = [0u8; 32]; bytes.copy_from_slice(b); let cey = CompressedEdwardsY(bytes); cey.decompress() } fn basepoint_mult(s: &c2_Scalar) -> c2_Element { ED25519_BASEPOINT_POINT * s } fn scalarmult(e: &c2_Element, s: &c2_Scalar) -> c2_Element { e * s } fn add(a: &c2_Element, b: &c2_Element) -> c2_Element { a + b } } fn ed25519_hash_to_scalar(s: &[u8]) -> c2_Scalar { //c2_Scalar::hash_from_bytes::(&s) // spake2.py does: // h = HKDF(salt=b"", ikm=s, hash=SHA256, info=b"SPAKE2 pw", len=32+16) // i = int(h, 16) // i % q let mut okm = [0u8; 32 + 16]; Hkdf::::new(Some(b""), s) .expand(b"SPAKE2 pw", &mut okm) .unwrap(); //println!("expanded: {}{}", "................................", okm.iter().to_hex()); // ok let mut reducible = [0u8; 64]; // little-endian for (i, x) in okm.iter().enumerate().take(32 + 16) { reducible[32 + 16 - 1 - i] = *x; } //println!("reducible: {}", reducible.iter().to_hex()); c2_Scalar::from_bytes_mod_order_wide(&reducible) //let reduced = c2_Scalar::reduce(&reducible); //println!("reduced: {}", reduced.as_bytes().to_hex()); //println!("done"); //reduced } /// Hash `idA` and `idB` identities. pub(crate) fn hash_ab( password_vec: &[u8], id_a: &[u8], id_b: &[u8], first_msg: &[u8], second_msg: &[u8], key_bytes: &[u8], ) -> Vec { assert_eq!(first_msg.len(), 32); assert_eq!(second_msg.len(), 32); // the transcript is fixed-length, made up of 6 32-byte values: // byte 0-31 : sha256(pw) // byte 32-63 : sha256(idA) // byte 64-95 : sha256(idB) // byte 96-127 : X_msg // byte 128-159: Y_msg // byte 160-191: K_bytes let mut transcript = [0u8; 6 * 32]; let mut pw_hash = Sha256::new(); pw_hash.update(password_vec); transcript[0..32].copy_from_slice(&pw_hash.finalize()); let mut ida_hash = Sha256::new(); ida_hash.update(id_a); transcript[32..64].copy_from_slice(&ida_hash.finalize()); let mut idb_hash = Sha256::new(); idb_hash.update(id_b); transcript[64..96].copy_from_slice(&idb_hash.finalize()); transcript[96..128].copy_from_slice(first_msg); transcript[128..160].copy_from_slice(second_msg); transcript[160..192].copy_from_slice(key_bytes); //println!("transcript: {:?}", transcript.iter().to_hex()); //let mut hash = G::TranscriptHash::default(); let mut hash = Sha256::new(); hash.update(transcript); hash.finalize().to_vec() } /// Hash symmetric identities. pub(crate) fn hash_symmetric( password_vec: &[u8], id_s: &[u8], msg_u: &[u8], msg_v: &[u8], key_bytes: &[u8], ) -> Vec { assert_eq!(msg_u.len(), 32); assert_eq!(msg_v.len(), 32); // # since we don't know which side is which, we must sort the messages // first_msg, second_msg = sorted([msg1, msg2]) // transcript = b"".join([sha256(pw).digest(), // sha256(idSymmetric).digest(), // first_msg, second_msg, K_bytes]) // the transcript is fixed-length, made up of 5 32-byte values: // byte 0-31 : sha256(pw) // byte 32-63 : sha256(idSymmetric) // byte 64-95 : X_msg // byte 96-127 : Y_msg // byte 128-159: K_bytes let mut transcript = [0u8; 5 * 32]; let mut pw_hash = Sha256::new(); pw_hash.update(password_vec); transcript[0..32].copy_from_slice(&pw_hash.finalize()); let mut ids_hash = Sha256::new(); ids_hash.update(id_s); transcript[32..64].copy_from_slice(&ids_hash.finalize()); if msg_u < msg_v { transcript[64..96].copy_from_slice(msg_u); transcript[96..128].copy_from_slice(msg_v); } else { transcript[64..96].copy_from_slice(msg_v); transcript[96..128].copy_from_slice(msg_u); } transcript[128..160].copy_from_slice(key_bytes); let mut hash = Sha256::new(); hash.update(transcript); hash.finalize().to_vec() } spake2-0.4.0/src/error.rs000064400000000000000000000013601046102023000133000ustar 00000000000000//! Error types. use core::fmt; /// [`Result`][`core::result::Result`] type with `spake2`'s [`Error`] type. pub type Result = core::result::Result; /// SPAKE2 errors. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Error { /// Bad side BadSide, /// Corrupt message CorruptMessage, /// Wrong length WrongLength, } impl fmt::Display for Error { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Error::BadSide => fmt.write_str("bad side"), Error::CorruptMessage => fmt.write_str("corrupt message"), Error::WrongLength => fmt.write_str("invalid length"), } } } #[cfg(feature = "std")] impl std::error::Error for Error {} spake2-0.4.0/src/group.rs000064400000000000000000000025521046102023000133070ustar 00000000000000//! Group trait. use alloc::vec::Vec; use rand_core::{CryptoRng, RngCore}; /// Group trait. // TODO(tarcieri): replace with `group` crate? pub trait Group { /// Scalar element type Scalar; /// Base field element type Element; /// Transcript hash type TranscriptHash; /// Name fn name() -> &'static str; /// `m` constant fn const_m() -> Self::Element; /// `n` constant fn const_n() -> Self::Element; /// `s` constant fn const_s() -> Self::Element; /// Hash to scalar fn hash_to_scalar(s: &[u8]) -> Self::Scalar; /// Generate a random scalar fn random_scalar(cspring: &mut T) -> Self::Scalar where T: RngCore + CryptoRng; /// Scalar negation fn scalar_neg(s: &Self::Scalar) -> Self::Scalar; /// Convert base field element to bytes fn element_to_bytes(e: &Self::Element) -> Vec; /// Convert bytes to base field element fn bytes_to_element(b: &[u8]) -> Option; /// Length of a base field element fn element_length() -> usize; /// Fixed-base scalar multiplication fn basepoint_mult(s: &Self::Scalar) -> Self::Element; /// Variable-base scalar multiplication fn scalarmult(e: &Self::Element, s: &Self::Scalar) -> Self::Element; /// Group operation fn add(a: &Self::Element, b: &Self::Element) -> Self::Element; } spake2-0.4.0/src/lib.rs000064400000000000000000000670231046102023000127250ustar 00000000000000#![no_std] #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![doc = include_str!("../README.md")] #![doc( html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg" )] #![forbid(unsafe_code)] #![warn(missing_docs, rust_2018_idioms, unused_qualifications)] //! # Usage //! //! Alice and Bob both initialize their SPAKE2 instances with the same (weak) //! password. They will exchange messages to (hopefully) derive a shared secret //! key. The protocol is symmetric: for each operation that Alice does, Bob will //! do the same. //! //! However, there are two roles in the SPAKE2 protocol, "A" and "B". The two //! sides must agree ahead of time which one will play which role (the //! messages they generate depend upon which side they play). There are two //! separate constructor functions, `start_a()` and `start_b()`, and a //! complete interaction will use one of each (one `start_a` on one computer, //! and one `start_b` on the other computer). //! //! Each instance of a SPAKE2 protocol uses a set of shared parameters. These //! include a group, a generator, and a pair of arbitrary group elements. //! This library comes a single pre-generated parameter set, but could be //! extended with others. //! //! You start by calling `start_a()` (or `_b)` with the password and identity //! strings for both sides. This gives you back a state object and the first //! message, which you must send to your partner. Once you receive the //! corresponding inbound message, you pass it into the state object //! (consuming both in the process) by calling `s.finish()`, and you get back //! the shared key as a bytestring. //! //! The password and identity strings must each be wrapped in a "newtype", //! which is a simple `struct` that protects against swapping the different //! types of bytestrings. //! //! Thus a client-side program start with: //! //! ```rust //! use spake2::{Ed25519Group, Identity, Password, Spake2}; //! # fn send(msg: &[u8]) {} //! let (s1, outbound_msg) = Spake2::::start_a( //! &Password::new(b"password"), //! &Identity::new(b"client id string"), //! &Identity::new(b"server id string")); //! send(&outbound_msg); //! //! # fn receive() -> Vec { let (s2, i2) = Spake2::::start_b(&Password::new(b"password"), &Identity::new(b"client id string"), &Identity::new(b"server id string")); i2 } //! let inbound_msg = receive(); //! let key1 = s1.finish(&inbound_msg).unwrap(); //! ``` //! //! while the server-side might do: //! //! ```rust //! # fn send(msg: &[u8]) {} //! use spake2::{Ed25519Group, Identity, Password, Spake2}; //! let (s1, outbound_msg) = Spake2::::start_b( //! &Password::new(b"password"), //! &Identity::new(b"client id string"), //! &Identity::new(b"server id string")); //! send(&outbound_msg); //! //! # fn receive() -> Vec { let (s2, i2) = Spake2::::start_a(&Password::new(b"password"), &Identity::new(b"client id string"), &Identity::new(b"server id string")); i2 } //! let inbound_msg = receive(); //! let key2 = s1.finish(&inbound_msg).unwrap(); //! ``` //! //! If both sides used the same password, and there is no man-in-the-middle, //! then `key1` and `key2` will be identical. If not, the two sides will get //! different keys. When one side encrypts with `key1`, and the other side //! attempts to decrypt with `key2`, they'll get nothing but garbled noise. //! //! The shared key can be used as an HMAC key to provide data integrity on //! subsequent messages, or as an authenticated-encryption key (e.g. //! nacl.secretbox). It can also be fed into [HKDF][1] to derive other //! session keys as necessary. //! //! The `SPAKE2` instances, and the messages they create, are single-use. Create //! a new one for each new session. `finish` consumes the instance. //! //! # Symmetric Usage //! //! A single SPAKE2 instance must be used asymmetrically: the two sides must //! somehow decide (ahead of time) which role they will each play. The //! implementation includes the side identifier in the exchanged message to //! guard against a `start_a` talking to another `start_a`. Typically a //! "client" will take on the `A` role, and the "server" will be `B`. //! //! This is a nuisance for more egalitarian protocols, where there's no clear //! way to assign these roles ahead of time. In this case, use //! `start_symmetric()` on both sides. This uses a different set of //! parameters (so it is not interoperable with `start_A` or `start_b`), but //! should otherwise behave the same way. The symmetric mode uses only one //! identity string, not two. //! //! Carol does: //! //! ```rust //! # fn send(msg: &[u8]) {} //! use spake2::{Ed25519Group, Identity, Password, Spake2}; //! let (s1, outbound_msg) = Spake2::::start_symmetric( //! &Password::new(b"password"), //! &Identity::new(b"shared id string")); //! send(&outbound_msg); //! //! # fn receive() -> Vec { let (s2, i2) = Spake2::::start_symmetric(&Password::new(b"password"), &Identity::new(b"shared id string")); i2 } //! let inbound_msg = receive(); //! let key1 = s1.finish(&inbound_msg).unwrap(); //! ``` //! //! Dave does exactly the same: //! //! ```rust //! # fn send(msg: &[u8]) {} //! use spake2::{Ed25519Group, Identity, Password, Spake2}; //! let (s1, outbound_msg) = Spake2::::start_symmetric( //! &Password::new(b"password"), //! &Identity::new(b"shared id string")); //! send(&outbound_msg); //! //! # fn receive() -> Vec { let (s2, i2) = Spake2::::start_symmetric(&Password::new(b"password"), &Identity::new(b"shared id string")); i2 } //! let inbound_msg = receive(); //! let key1 = s1.finish(&inbound_msg).unwrap(); //! ``` //! //! # Identifier Strings //! //! The SPAKE2 protocol includes a pair of "identity strings" `idA` and `idB` //! that are included in the final key-derivation hash. This binds the key to a //! single pair of parties, or for some specific purpose. //! //! For example, when user "alice" logs into "example.com", both sides should set //! `idA = b"alice"` and `idB = b"example.com"`. This prevents an attacker from //! substituting messages from unrelated login sessions (other users on the same //! server, or other servers for the same user). //! //! This also makes sure the session is established with the correct service. If //! Alice has one password for "example.com" but uses it for both login and //! file-transfer services, `idB` should be different for the two services. //! Otherwise if Alice is simultaneously connecting to both services, and //! attacker could rearrange the messages and cause her login client to connect //! to the file-transfer server, and vice versa. //! //! `idA` and `idB` must be bytestrings (slices of ``). //! //! `start_symmetric` uses a single `idSymmetric=` string, instead of `idA` //! and `idB`. Both sides must provide the same `idSymmetric=`, or leave it //! empty. //! //! # Serialization //! //! Sometimes, you can't hold the SPAKE2 instance in memory for the whole //! negotiation: perhaps all your program state is stored in a database, and //! nothing lives in RAM for more than a few moments. //! //! Unfortunately the Rust implementation does not yet provide serialization //! of the state object. A future version should correct this. //! //! # Security //! //! This library is probably not constant-time, and does not protect against //! timing attacks. Do not allow attackers to measure how long it takes you //! to create or respond to a message. This matters somewhat less for pairing //! protocols, because their passwords are single-use randomly-generated //! keys, so an attacker has much less to work with. //! //! This library depends upon a strong source of random numbers. Do not use it on //! a system where os.urandom() is weak. //! //! # Speed //! //! To run the built-in speed tests, just run `cargo bench`. //! //! SPAKE2 consists of two phases, separated by a single message exchange. //! The time these phases take is split roughly 50/50. On my 2.8GHz Core-i7 //! (i7-7600U) cpu, the built-in Ed25519Group parameters take about 112 //! microseconds for each phase, and the message exchanged is 33 bytes long. //! //! # Testing //! //! Run `cargo test` to run the built-in test suite. //! //! # History //! //! The protocol was described as "PAKE2" in ["cryptobook"] [2] from Dan Boneh //! and Victor Shoup. This is a form of "SPAKE2", defined by Abdalla and //! Pointcheval at [RSA 2005] [3]. Additional recommendations for groups and //! distinguished elements were published in [Ladd's IETF draft] [4]. //! //! The Ed25519 implementation uses code adapted from Daniel Bernstein (djb), //! Matthew Dempsky, Daniel Holth, Ron Garret, with further optimizations by //! Brian Warner[5]. The "arbitrary element" computation, which must be the same //! for both participants, is from python-pure25519 version 0.5. //! //! The Boneh/Shoup chapter that defines PAKE2 also defines an augmented variant //! named "PAKE2+", which changes one side (typically a server) to record a //! derivative of the password instead of the actual password. In PAKE2+, a //! server compromise does not immediately give access to the passwords: instead, //! the attacker must perform an offline dictionary attack against the stolen //! data before they can learn the passwords. PAKE2+ support is planned, but not //! yet implemented. //! //! The security of the symmetric case was proved by Kobara/Imai[6] in 2003, and //! uses different (slightly weaker?) reductions than that of the asymmetric //! form. See also Mike Hamburg's analysis[7] from 2015. //! //! Brian Warner first wrote the Python version in July 2010. He wrote this //! Rust version in in May 2017. //! //! ### footnotes //! //! [1]: https://tools.ietf.org/html/rfc5869 "HKDF" //! [2]: http://crypto.stanford.edu/~dabo/cryptobook/ "cryptobook" //! [3]: http://www.di.ens.fr/~pointche/Documents/Papers/2005_rsa.pdf "RSA 2005" //! [4]: https://tools.ietf.org/html/draft-ladd-spake2-01 "Ladd's IETF draft" //! [5]: https://github.com/warner/python-pure25519 //! [6]: http://eprint.iacr.org/2003/038.pdf "Pretty-Simple Password-Authenticated Key-Exchange Under Standard Assumptions" //! [7]: https://moderncrypto.org/mail-archive/curves/2015/000419.html "PAKE questions" #[allow(unused_imports)] #[macro_use] extern crate alloc; #[cfg(feature = "std")] #[cfg_attr(test, macro_use)] extern crate std; mod ed25519; mod error; mod group; pub use self::{ ed25519::Ed25519Group, error::{Error, Result}, group::Group, }; use alloc::{borrow::ToOwned, vec::Vec}; use core::{fmt, ops::Deref, str}; use curve25519_dalek::{edwards::EdwardsPoint as c2_Element, scalar::Scalar as c2_Scalar}; use rand_core::{CryptoRng, RngCore}; #[cfg(feature = "getrandom")] use rand_core::OsRng; /// Password type. // TODO(tarcieri): avoid allocation? #[derive(PartialEq, Eq, Clone)] pub struct Password(Vec); impl Password { /// Create a new password. pub fn new(p: impl AsRef<[u8]>) -> Password { Password(p.as_ref().to_vec()) } } impl Deref for Password { type Target = Vec; fn deref(&self) -> &Vec { &self.0 } } /// SPAKE2 identity. // TODO(tarcieri): avoid allocation? #[derive(PartialEq, Eq, Clone)] pub struct Identity(Vec); impl Deref for Identity { type Target = Vec; fn deref(&self) -> &Vec { &self.0 } } impl Identity { /// Create a new identity. pub fn new(p: &[u8]) -> Identity { Identity(p.to_vec()) } } /// Session type identifying the "side" in a SPAKE2 exchange. #[derive(PartialEq, Eq)] enum Side { A { id_a: Vec, id_b: Vec }, B { id_a: Vec, id_b: Vec }, Symmetric { id_s: Vec }, } /// SPAKE2 algorithm. #[derive(Eq, PartialEq)] pub struct Spake2 { //where &G::Scalar: Neg { side: Side, xy_scalar: G::Scalar, password_vec: Vec, msg1: Vec, password_scalar: G::Scalar, } impl Spake2 { /// Start with identity `idA`. /// /// Uses the system RNG. #[cfg(feature = "getrandom")] pub fn start_a(password: &Password, id_a: &Identity, id_b: &Identity) -> (Spake2, Vec) { Self::start_a_with_rng(password, id_a, id_b, OsRng) } /// Start with identity `idB`. /// /// Uses the system RNG. #[cfg(feature = "getrandom")] pub fn start_b(password: &Password, id_a: &Identity, id_b: &Identity) -> (Spake2, Vec) { Self::start_b_with_rng(password, id_a, id_b, OsRng) } /// Start with symmetric identity. /// /// Uses the system RNG. #[cfg(feature = "getrandom")] pub fn start_symmetric(password: &Password, id_s: &Identity) -> (Spake2, Vec) { Self::start_symmetric_with_rng(password, id_s, OsRng) } /// Start with identity `idA` and the provided cryptographically secure RNG. pub fn start_a_with_rng( password: &Password, id_a: &Identity, id_b: &Identity, mut csrng: impl CryptoRng + RngCore, ) -> (Spake2, Vec) { let xy_scalar: G::Scalar = G::random_scalar(&mut csrng); Self::start_a_internal(password, id_a, id_b, xy_scalar) } /// Start with identity `idB` and the provided cryptographically secure RNG. pub fn start_b_with_rng( password: &Password, id_a: &Identity, id_b: &Identity, mut csrng: impl CryptoRng + RngCore, ) -> (Spake2, Vec) { let xy_scalar: G::Scalar = G::random_scalar(&mut csrng); Self::start_b_internal(password, id_a, id_b, xy_scalar) } /// Start with symmetric identity and the provided cryptographically secure RNG. pub fn start_symmetric_with_rng( password: &Password, id_s: &Identity, mut csrng: impl CryptoRng + RngCore, ) -> (Spake2, Vec) { let xy_scalar: G::Scalar = G::random_scalar(&mut csrng); Self::start_symmetric_internal(password, id_s, xy_scalar) } /// Finish SPAKE2. pub fn finish(self, msg2: &[u8]) -> Result> { if msg2.len() != 1 + G::element_length() { return Err(Error::WrongLength); } let msg_side = msg2[0]; match self.side { Side::A { id_a: _, id_b: _ } => match msg_side { 0x42 => (), // 'B' _ => return Err(Error::BadSide), }, Side::B { id_a: _, id_b: _ } => match msg_side { 0x41 => (), // 'A' _ => return Err(Error::BadSide), }, Side::Symmetric { id_s: _ } => match msg_side { 0x53 => (), // 'S' _ => return Err(Error::BadSide), }, } let msg2_element = match G::bytes_to_element(&msg2[1..]) { Some(x) => x, None => return Err(Error::CorruptMessage), }; // a: K = (Y+N*(-pw))*x // b: K = (X+M*(-pw))*y let unblinding = match self.side { Side::A { id_a: _, id_b: _ } => G::const_n(), Side::B { id_a: _, id_b: _ } => G::const_m(), Side::Symmetric { id_s: _ } => G::const_s(), }; let tmp1 = G::scalarmult(&unblinding, &G::scalar_neg(&self.password_scalar)); let tmp2 = G::add(&msg2_element, &tmp1); let key_element = G::scalarmult(&tmp2, &self.xy_scalar); let key_bytes = G::element_to_bytes(&key_element); // key = H(H(pw) + H(idA) + H(idB) + X + Y + K) //transcript = b"".join([sha256(pw).digest(), // sha256(idA).digest(), sha256(idB).digest(), // X_msg, Y_msg, K_bytes]) //key = sha256(transcript).digest() // note that both sides must use the same order Ok(match self.side { Side::A { id_a, id_b } => ed25519::hash_ab( &self.password_vec, &id_a, &id_b, self.msg1.as_slice(), &msg2[1..], &key_bytes, ), Side::B { id_a, id_b } => ed25519::hash_ab( &self.password_vec, &id_a, &id_b, &msg2[1..], self.msg1.as_slice(), &key_bytes, ), Side::Symmetric { id_s } => ed25519::hash_symmetric( &self.password_vec, &id_s, &self.msg1, &msg2[1..], &key_bytes, ), }) } fn start_internal( side: Side, password: &Password, xy_scalar: G::Scalar, ) -> (Spake2, Vec) { //let password_scalar: G::Scalar = hash_to_scalar::(password); let password_scalar: G::Scalar = G::hash_to_scalar(password); // a: X = B*x + M*pw // b: Y = B*y + N*pw // sym: X = B*x * S*pw let blinding = match side { Side::A { id_a: _, id_b: _ } => G::const_m(), Side::B { id_a: _, id_b: _ } => G::const_n(), Side::Symmetric { id_s: _ } => G::const_s(), }; let m1: G::Element = G::add( &G::basepoint_mult(&xy_scalar), &G::scalarmult(&blinding, &password_scalar), ); //let m1: G::Element = &G::basepoint_mult(&x) + &(blinding * &password_scalar); let msg1: Vec = G::element_to_bytes(&m1); let mut password_vec = Vec::new(); password_vec.extend_from_slice(password); let mut msg_and_side = vec![match side { Side::A { id_a: _, id_b: _ } => 0x41, // 'A' Side::B { id_a: _, id_b: _ } => 0x42, // 'B' Side::Symmetric { id_s: _ } => 0x53, // 'S' }]; msg_and_side.extend_from_slice(&msg1); ( Spake2 { side, xy_scalar, password_vec, // string msg1, password_scalar, // scalar }, msg_and_side, ) } fn start_a_internal( password: &Password, id_a: &Identity, id_b: &Identity, xy_scalar: G::Scalar, ) -> (Spake2, Vec) { Self::start_internal( Side::A { id_a: id_a.to_owned().0, id_b: id_b.to_owned().0, }, password, xy_scalar, ) } fn start_b_internal( password: &Password, id_a: &Identity, id_b: &Identity, xy_scalar: G::Scalar, ) -> (Spake2, Vec) { Self::start_internal( Side::B { id_a: id_a.to_owned().0, id_b: id_b.to_owned().0, }, password, xy_scalar, ) } fn start_symmetric_internal( password: &Password, id_s: &Identity, xy_scalar: G::Scalar, ) -> (Spake2, Vec) { Self::start_internal( Side::Symmetric { id_s: id_s.to_owned().0, }, password, xy_scalar, ) } } impl fmt::Debug for Side { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Side::A { id_a, id_b } => fmt .debug_struct("Side::A") .field("idA", &MaybeUtf8(id_a)) .field("idB", &MaybeUtf8(id_b)) .finish(), Side::B { id_a, id_b } => fmt .debug_struct("Side::B") .field("idA", &MaybeUtf8(id_a)) .field("idB", &MaybeUtf8(id_b)) .finish(), Side::Symmetric { id_s } => fmt .debug_struct("Side::Symmetric") .field("idS", &MaybeUtf8(id_s)) .finish(), } } } impl fmt::Debug for Spake2 { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt.debug_struct("SPAKE2") .field("group", &G::name()) .field("side", &self.side) .finish() } } struct MaybeUtf8<'a>(&'a [u8]); impl fmt::Debug for MaybeUtf8<'_> { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { if let Ok(s) = str::from_utf8(self.0) { write!(fmt, "(s={})", s) } else { write!(fmt, "(hex=")?; for byte in self.0 { write!(fmt, "{:x}", byte)?; } write!(fmt, ")") } } } /// This compares results against the python compatibility tests: /// spake2.test.test_compat.SPAKE2.test_asymmetric . The python test passes a /// deterministic RNG (used only for tests, of course) into the per-Group /// "random_scalar()" function, which results in some particular scalar. #[cfg(all(test, feature = "std"))] mod tests { use crate::*; use curve25519_dalek::constants::ED25519_BASEPOINT_POINT; use num_bigint::BigUint; // the python tests show the long-integer form of scalars. the rust code // wants an array of bytes (little-endian). Make sure the way we convert // things works correctly. fn decimal_to_scalar(d: &[u8]) -> c2_Scalar { let bytes = BigUint::parse_bytes(d, 10).unwrap().to_bytes_le(); assert_eq!(bytes.len(), 32); let mut b2 = [0u8; 32]; b2.copy_from_slice(&bytes); c2_Scalar::from_bytes_mod_order(b2) } #[test] fn test_convert() { let t1_decimal = b"2238329342913194256032495932344128051776374960164957527413114840482143558222"; let t1_scalar = decimal_to_scalar(t1_decimal); let t1_bytes = t1_scalar.to_bytes(); let expected = [ 0x4e, 0x5a, 0xb4, 0x34, 0x5d, 0x47, 0x08, 0x84, 0x59, 0x13, 0xb4, 0x64, 0x1b, 0xc2, 0x7d, 0x52, 0x52, 0xa5, 0x85, 0x10, 0x1b, 0xcc, 0x42, 0x44, 0xd4, 0x49, 0xf4, 0xa8, 0x79, 0xd9, 0xf2, 0x04, ]; assert_eq!(t1_bytes, expected); //println!("t1_scalar is {:?}", t1_scalar); } #[test] fn test_serialize_basepoint() { // make sure elements are serialized same as the python library let exp = "5866666666666666666666666666666666666666666666666666666666666666"; let base_vec = ED25519_BASEPOINT_POINT.compress().as_bytes().to_vec(); let base_hex = hex::encode(base_vec); println!("exp: {:?}", exp); println!("got: {:?}", base_hex); assert_eq!(exp, base_hex); } #[test] fn test_password_to_scalar() { let password = Password::new(b"password"); let expected_pw_scalar = decimal_to_scalar( b"3515301705789368674385125653994241092664323519848410154015274772661223168839", ); let pw_scalar = Ed25519Group::hash_to_scalar(&password); println!("exp: {:?}", hex::encode(expected_pw_scalar.as_bytes())); println!("got: {:?}", hex::encode(pw_scalar.as_bytes())); assert_eq!(&pw_scalar, &expected_pw_scalar); } #[test] fn test_sizes() { let (s1, msg1) = Spake2::::start_a( &Password::new(b"password"), &Identity::new(b"idA"), &Identity::new(b"idB"), ); assert_eq!(msg1.len(), 1 + 32); let (s2, msg2) = Spake2::::start_b( &Password::new(b"password"), &Identity::new(b"idA"), &Identity::new(b"idB"), ); assert_eq!(msg2.len(), 1 + 32); let key1 = s1.finish(&msg2).unwrap(); let key2 = s2.finish(&msg1).unwrap(); assert_eq!(key1.len(), 32); assert_eq!(key2.len(), 32); let (s1, msg1) = Spake2::::start_symmetric( &Password::new(b"password"), &Identity::new(b"idS"), ); assert_eq!(msg1.len(), 1 + 32); let (s2, msg2) = Spake2::::start_symmetric( &Password::new(b"password"), &Identity::new(b"idS"), ); assert_eq!(msg2.len(), 1 + 32); let key1 = s1.finish(&msg2).unwrap(); let key2 = s2.finish(&msg1).unwrap(); assert_eq!(key1.len(), 32); assert_eq!(key2.len(), 32); } #[test] fn test_hash_ab() { let key = ed25519::hash_ab( b"pw", b"idA", b"idB", b"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", // len=32 b"YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY", b"KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK", ); let expected_key = "d59d9ba920f7092565cec747b08d5b2e981d553ac32fde0f25e5b4a4cfca3efd"; assert_eq!(hex::encode(key), expected_key); } #[test] fn test_hash_symmetric() { let key = ed25519::hash_symmetric( b"pw", b"idSymmetric", b"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", b"YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY", b"KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK", ); let expected_key = "b0b31e4401aae37d91a9a8bf6fbb1298cafc005ff9142e3ffc5b9799fb11128b"; assert_eq!(hex::encode(key), expected_key); } #[test] fn test_asymmetric() { let scalar_a = decimal_to_scalar( b"2611694063369306139794446498317402240796898290761098242657700742213257926693", ); let scalar_b = decimal_to_scalar( b"7002393159576182977806091886122272758628412261510164356026361256515836884383", ); let expected_pw_scalar = decimal_to_scalar( b"3515301705789368674385125653994241092664323519848410154015274772661223168839", ); println!("scalar_a is {}", hex::encode(scalar_a.as_bytes())); let (s1, msg1) = Spake2::::start_a_internal( &Password::new(b"password"), &Identity::new(b"idA"), &Identity::new(b"idB"), scalar_a, ); let expected_msg1 = "416fc960df73c9cf8ed7198b0c9534e2e96a5984bfc5edc023fd24dacf371f2af9"; println!(); println!("xys1: {:?}", hex::encode(s1.xy_scalar.as_bytes())); println!(); println!("pws1: {:?}", hex::encode(s1.password_scalar.as_bytes())); println!("exp : {:?}", hex::encode(expected_pw_scalar.as_bytes())); println!(); println!("msg1: {:?}", hex::encode(&msg1)); println!("exp : {:?}", expected_msg1); println!(); assert_eq!( hex::encode(expected_pw_scalar.as_bytes()), hex::encode(s1.password_scalar.as_bytes()) ); assert_eq!(hex::encode(&msg1), expected_msg1); let (s2, msg2) = Spake2::::start_b_internal( &Password::new(b"password"), &Identity::new(b"idA"), &Identity::new(b"idB"), scalar_b, ); assert_eq!(expected_pw_scalar, s2.password_scalar); assert_eq!( hex::encode(&msg2), "42354e97b88406922b1df4bea1d7870f17aed3dba7c720b313edae315b00959309" ); let key1 = s1.finish(&msg2).unwrap(); let key2 = s2.finish(&msg1).unwrap(); assert_eq!(key1, key2); assert_eq!( hex::encode(key1), "712295de7219c675ddd31942184aa26e0a957cf216bc230d165b215047b520c1" ); } #[test] fn test_debug() { let (s1, _msg1) = Spake2::::start_a( &Password::new(b"password"), &Identity::new(b"idA"), &Identity::new(b"idB"), ); println!("s1: {:?}", s1); assert_eq!( format!("{:?}", s1), "SPAKE2 { group: \"Ed25519\", side: Side::A { idA: (s=idA), idB: (s=idB) } }" ); let (s2, _msg1) = Spake2::::start_symmetric( &Password::new(b"password"), &Identity::new(b"idS"), ); println!("s2: {:?}", s2); assert_eq!( format!("{:?}", s2), "SPAKE2 { group: \"Ed25519\", side: Side::Symmetric { idS: (s=idS) } }" ); } } spake2-0.4.0/tests/spake2.rs000064400000000000000000000041501046102023000137070ustar 00000000000000use spake2::{Ed25519Group, Error, Identity, Password, Spake2}; #[test] fn test_basic() { let (s1, msg1) = Spake2::::start_a( &Password::new(b"password"), &Identity::new(b"idA"), &Identity::new(b"idB"), ); let (s2, msg2) = Spake2::::start_b( &Password::new(b"password"), &Identity::new(b"idA"), &Identity::new(b"idB"), ); let key1 = s1.finish(msg2.as_slice()).unwrap(); let key2 = s2.finish(msg1.as_slice()).unwrap(); assert_eq!(key1, key2); } #[test] fn test_mismatch() { let (s1, msg1) = Spake2::::start_a( &Password::new(b"password"), &Identity::new(b"idA"), &Identity::new(b"idB"), ); let (s2, msg2) = Spake2::::start_b( &Password::new(b"password2"), &Identity::new(b"idA"), &Identity::new(b"idB"), ); let key1 = s1.finish(msg2.as_slice()).unwrap(); let key2 = s2.finish(msg1.as_slice()).unwrap(); assert_ne!(key1, key2); } #[test] fn test_reflected_message() { let (s1, msg1) = Spake2::::start_a( &Password::new(b"password"), &Identity::new(b"idA"), &Identity::new(b"idB"), ); let r = s1.finish(msg1.as_slice()); assert_eq!(r.unwrap_err(), Error::BadSide); } #[test] fn test_bad_length() { let (s1, msg1) = Spake2::::start_a( &Password::new(b"password"), &Identity::new(b"idA"), &Identity::new(b"idB"), ); let mut msg2 = Vec::::with_capacity(msg1.len() + 1); msg2.resize(msg1.len() + 1, 0u8); let r = s1.finish(&msg2); assert_eq!(r.unwrap_err(), Error::WrongLength); } #[test] fn test_basic_symmetric() { let (s1, msg1) = Spake2::::start_symmetric( &Password::new(b"password"), &Identity::new(b"idS"), ); let (s2, msg2) = Spake2::::start_symmetric( &Password::new(b"password"), &Identity::new(b"idS"), ); let key1 = s1.finish(msg2.as_slice()).unwrap(); let key2 = s2.finish(msg1.as_slice()).unwrap(); assert_eq!(key1, key2); }