]> git.lizzy.rs Git - PAKEs.git/commitdiff
slow progress, got password-to-scalar working
authorBrian Warner <warner@lothar.com>
Thu, 25 May 2017 21:32:19 +0000 (14:32 -0700)
committerBrian Warner <warner@lothar.com>
Thu, 25 May 2017 21:32:19 +0000 (14:32 -0700)
* ported spake2.py password-to-scalar function, since dalek's built-in one
  does it differently
* added "side" byte in messages: emit in start(), strip+check in input()
* rewrote transcript-hashing (since transcript is fixed-size)

This adds a lot of debug prints, and moves a bunch of test-only code into the
top level, all of which will need to be undone eventually.

Cargo.toml
src/lib.rs
src/spake2.rs

index bf860d33100b8327e0849ab18918becf70d3d529..06e3c4e3c8b73dde536f3bf118a01308fbe7a5a9 100644 (file)
@@ -7,8 +7,10 @@ authors = ["Brian Warner <warner@lothar.com>"]
 #rust-crypto = "^0.2"
 curve25519-dalek = "0.8.0"
 rand = "0.3.0"
-sha2 = "0.4.0"
+#sha2 = "0.4.0"
+rust-crypto = "0.2"
+num-bigint = "0.1"
+hex = "0.2"
 
 [dev-dependencies]
-num-bigint = "0.1"
 hex = "0.2"
index 4342a7464430801dc4d17be0b84c45aac238fcf7..9083473c43ef190bf2ec8fe82748c67d56071056 100644 (file)
@@ -1,15 +1,17 @@
 
 extern crate rand;
 extern crate curve25519_dalek;
-extern crate sha2;
+//extern crate sha2;
+extern crate crypto;
+extern crate num_bigint;
+
+extern crate hex;
 
 mod spake2;
 pub use spake2::*;
 
-#[cfg(test)]
-extern crate num_bigint;
-#[cfg(test)]
-extern crate hex;
+//#[cfg(test)]
+//extern crate hex;
 
 #[cfg(test)]
 mod tests {
index d46c92e12a5d2438dd1f33b27dab53d31ebf306f..43b81dc3f1957af84768b6c5639e7e7c1efc3ef6 100644 (file)
@@ -1,10 +1,17 @@
+#![allow(dead_code)]
 
 use curve25519_dalek::scalar::Scalar as c2_Scalar;
 use curve25519_dalek::curve::ExtendedPoint as c2_Element;
 use curve25519_dalek::constants::ED25519_BASEPOINT;
 use curve25519_dalek::curve::CompressedEdwardsY;
 use rand::{Rng, OsRng};
-use sha2::{Sha256, Sha512, Digest};
+//use sha2::{Sha256, Sha512, Digest};
+use crypto::sha2::Sha256;
+use crypto::digest::Digest;
+use crypto::hkdf;
+use num_bigint::BigUint;
+
+use hex::ToHex;
 
 #[derive(Debug)]
 pub struct SPAKEErr ( String );
@@ -74,7 +81,29 @@ impl Group for Ed25519Group {
     }
 
     fn hash_to_scalar(s: &[u8]) -> c2_Scalar {
-        c2_Scalar::hash_from_bytes::<Sha512>(&s)
+        //c2_Scalar::hash_from_bytes::<Sha512>(&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 prk = [0u8; 32];
+        let digest = Sha256::new();
+        hkdf::hkdf_extract(digest, b"", s, &mut prk);
+        let mut okm = [0u8; 32+16];
+        hkdf::hkdf_expand(digest, &prk, b"SPAKE2 pw", &mut okm);
+        //okm[32+16-2] = 1;
+        println!("expanded:   {}{}", "................................", okm.iter().to_hex()); // ok
+
+        let mut reducible = [0u8; 64]; // little-endian
+        for i in 0..32+16 {
+            reducible[32+16-1-i] = okm[i];
+        }
+        println!("reducible:  {}", reducible.iter().to_hex());
+        let reduced = c2_Scalar::reduce(&reducible);
+        println!("reduced:    {}", reduced.as_bytes().to_hex());
+        println!("done");
+        reduced
     }
     fn random_scalar<T: Rng>(cspring: &mut T) -> c2_Scalar {
         c2_Scalar::random(cspring)
@@ -112,6 +141,14 @@ impl Group for Ed25519Group {
     }
 }
 
+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 s = c2_Scalar([0u8; 32]);
+    s.0.copy_from_slice(&bytes);
+    s
+}
+
 
 /* "session type pattern" */
 
@@ -226,6 +263,21 @@ impl<G: Group> SPAKE2<G> {
         }
         let msg_side = msg2[0];
 
+        match self.side {
+            Side::A => match msg_side {
+                0x42 => (), // 'B'
+                _ => return Err(SPAKEErr(String::from("bad side"))),
+            },
+            Side::B => match msg_side {
+                0x41 => (), // 'A'
+                _ => return Err(SPAKEErr(String::from("bad side"))),
+            },
+            Side::Symmetric => match msg_side {
+                0x53 => (), // 'S'
+                _ => return Err(SPAKEErr(String::from("bad side"))),
+            },
+        }
+
         let msg2_element = match G::bytes_to_element(&msg2[1..]) {
             Some(x) => x,
             None => {return Err(SPAKEErr(String::from("message corrupted")))},
@@ -259,30 +311,39 @@ impl<G: Group> SPAKE2<G> {
 
     fn hash_ab(&self, first_msg: &[u8], second_msg: &[u8],
                key_element: &G::Element) -> Vec<u8> {
-        let mut transcript = Vec::<u8>::new();
+        // 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.input(&self.password_vec);
-        transcript.extend_from_slice(pw_hash.result().as_slice());
+        pw_hash.result(&mut transcript[0..32]);
 
         let mut ida_hash = Sha256::new();
         ida_hash.input(&self.id_a);
-        transcript.extend_from_slice(ida_hash.result().as_slice());
+        ida_hash.result(&mut transcript[32..64]);
 
         let mut idb_hash = Sha256::new();
         idb_hash.input(&self.id_b);
-        transcript.extend_from_slice(idb_hash.result().as_slice());
+        idb_hash.result(&mut transcript[64..96]);
 
-        transcript.extend_from_slice(first_msg);
-        transcript.extend_from_slice(second_msg);
+        transcript[96..128].copy_from_slice(first_msg);
+        transcript[128..160].copy_from_slice(second_msg);
 
         let k_bytes = G::element_to_bytes(&key_element);
-        transcript.extend_from_slice(k_bytes.as_slice());
+        transcript[160..192].copy_from_slice(k_bytes.as_slice());
 
         //let mut hash = G::TranscriptHash::default();
-        let mut hash = Sha256::default();
-        hash.input(transcript.as_slice());
-        hash.result().to_vec()
+        let mut hash = Sha256::new();
+        hash.input(&transcript);
+        let mut out = [0u8; 32];
+        hash.result(&mut out);
+        out.to_vec()
     }
 
     fn hash_symmetric(&self, msg2: &[u8], key_element: &G::Element) -> Vec<u8> {
@@ -291,32 +352,41 @@ impl<G: Group> SPAKE2<G> {
         // transcript = b"".join([sha256(pw).digest(),
         //                        sha256(idSymmetric).digest(),
         //                        first_msg, second_msg, K_bytes])
-        let mut transcript = Vec::<u8>::new();
+
+        // 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.input(&self.password_vec);
-        transcript.extend_from_slice(pw_hash.result().as_slice());
+        pw_hash.result(&mut transcript[0..32]);
 
         let mut ids_hash = Sha256::new();
         ids_hash.input(&self.id_s);
-        transcript.extend_from_slice(ids_hash.result().as_slice());
+        ids_hash.result(&mut transcript[32..64]);
 
         let msg_u = self.msg1.as_slice();
         let msg_v = msg2;
         if msg_u < msg_v {
-            transcript.extend_from_slice(&msg_u);
-            transcript.extend_from_slice(msg_v);
+            transcript[64..96].copy_from_slice(&msg_u);
+            transcript[96..128].copy_from_slice(msg_v);
         } else {
-            transcript.extend_from_slice(msg_v);
-            transcript.extend_from_slice(&msg_u);
+            transcript[64..96].copy_from_slice(msg_v);
+            transcript[96..128].copy_from_slice(&msg_u);
         }
 
         let k_bytes = G::element_to_bytes(&key_element);
-        transcript.extend_from_slice(k_bytes.as_slice());
+        transcript[128..160].copy_from_slice(k_bytes.as_slice());
 
-        let mut hash = Sha256::default();
-        hash.input(transcript.as_slice());
-        hash.result().to_vec()
+        let mut hash = Sha256::new();
+        hash.input(&transcript);
+        let mut out = [0u8; 32];
+        hash.result(&mut out);
+        out.to_vec()
     }
 }
 
@@ -328,22 +398,16 @@ mod test {
     deterministic RNG (used only for tests, of course) into the per-Group
     "random_scalar()" function, which results in some particular scalar.
      */
-    use num_bigint::BigUint;
     use curve25519_dalek::scalar::Scalar;
+    use curve25519_dalek::constants::ED25519_BASEPOINT;
     use spake2::{SPAKE2, Ed25519Group};
+    use hex::ToHex;
+    use super::*;
 
     // 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]) -> Scalar {
-        let bytes = BigUint::parse_bytes(d, 10).unwrap().to_bytes_le();
-        assert_eq!(bytes.len(), 32);
-        let mut s = Scalar([0u8; 32]);
-        s.0.copy_from_slice(&bytes);
-        s
-    }
-
     #[test]
     fn test_convert() {
         let t1_decimal = b"2238329342913194256032495932344128051776374960164957527413114840482143558222";
@@ -357,8 +421,6 @@ mod test {
         //println!("t1_scalar is {:?}", t1_scalar);
     }
 
-    use hex::ToHex;
-    use curve25519_dalek::constants::ED25519_BASEPOINT;
     #[test]
     fn test_serialize_basepoint() {
         // make sure elements are serialized same as the python library
@@ -370,6 +432,16 @@ mod test {
         assert_eq!(exp, base_hex);
     }
 
+    #[test]
+    fn test_password_to_scalar() {
+        let password = b"password";
+        let expected_pw_scalar = decimal_to_scalar(b"3515301705789368674385125653994241092664323519848410154015274772661223168839");
+        let pw_scalar = Ed25519Group::hash_to_scalar(password);
+        println!("exp: {:?}", expected_pw_scalar.as_bytes().to_hex());
+        println!("got: {:?}", pw_scalar.as_bytes().to_hex());
+        assert_eq!(&pw_scalar, &expected_pw_scalar);
+    }
+
     #[test]
     fn test_sizes() {
         let (s1, msg1) = SPAKE2::<Ed25519Group>::start_a(b"password", b"idA",
@@ -407,8 +479,15 @@ mod test {
             b"password", b"idA", b"idB", scalar_a);
         let expected_msg1 = "416fc960df73c9cf8ed7198b0c9534e2e96a5984bfc5edc023fd24dacf371f2af9";
 
+        println!();
+        println!("xys1: {:?}", s1.xy_scalar.as_bytes().to_hex());
+        println!();
+        println!("pws1: {:?}", s1.password_scalar.as_bytes().to_hex());
+        println!("exp : {:?}", expected_pw_scalar.as_bytes().to_hex());
+        println!();
         println!("msg1: {:?}", msg1.to_hex());
         println!("exp : {:?}", expected_msg1);
+        println!();
 
         assert_eq!(expected_pw_scalar.as_bytes().to_hex(),
                    s1.xy_scalar.as_bytes().to_hex());