use num_bigint::BigUint;
use sha2::{Digest, Sha512};
const N_HEX: &str = "\
FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74\
020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437\
4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED\
EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05\
98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB\
9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B\
E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718\
3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33\
A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7\
ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864\
D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2\
08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF";
const G: u32 = 5;
const USER: &[u8] = b"Pair-Setup";
fn n() -> BigUint {
BigUint::parse_bytes(N_HEX.as_bytes(), 16).expect("the group's prime")
}
fn hash(parts: &[&[u8]]) -> Vec<u8> {
let mut h = Sha512::new();
for p in parts {
h.update(p);
}
h.finalize().to_vec()
}
fn int(bytes: &[u8]) -> BigUint {
BigUint::from_bytes_be(bytes)
}
fn pad(v: &BigUint, len: usize) -> Vec<u8> {
let b = v.to_bytes_be();
let mut out = vec![0; len.saturating_sub(b.len())];
out.extend_from_slice(&b);
out
}
pub struct Exchange {
pub public: Vec<u8>,
pub proof: Vec<u8>,
pub key: Vec<u8>,
expected: Vec<u8>,
}
impl Exchange {
pub fn new(pin: &str, salt: &[u8], b: &[u8], private: &[u8]) -> Option<Self> {
let n = n();
let len = n.to_bytes_be().len();
let g = BigUint::from(G);
let b_int = int(b);
if (&b_int % &n) == BigUint::ZERO {
return None;
}
let a = int(private);
let a_pub = g.modpow(&a, &n);
let k = int(&hash(&[&n.to_bytes_be(), &pad(&g, len)]));
let u = int(&hash(&[&pad(&a_pub, len), &pad(&b_int, len)]));
let inner = hash(&[USER, b":", pin.as_bytes()]);
let x = int(&hash(&[&int(salt).to_bytes_be(), &inner]));
let v = g.modpow(&x, &n);
let base = (&b_int + &n - (&k * &v) % &n) % &n;
let s = base.modpow(&(&a + &u * &x), &n);
let key = hash(&[&s.to_bytes_be()]);
let hn = hash(&[&n.to_bytes_be()]);
let hg = hash(&[&g.to_bytes_be()]);
let xor: Vec<u8> = hn.iter().zip(&hg).map(|(a, b)| a ^ b).collect();
let proof = hash(&[
&int(&xor).to_bytes_be(),
&int(&hash(&[USER])).to_bytes_be(),
&int(salt).to_bytes_be(),
&a_pub.to_bytes_be(),
&b_int.to_bytes_be(),
&key,
]);
let expected = hash(&[&a_pub.to_bytes_be(), &proof, &key]);
Some(Self {
public: a_pub.to_bytes_be(),
proof,
key,
expected,
})
}
pub fn verify(&self, server_proof: &[u8]) -> bool {
use subtle::ConstantTimeEq;
bool::from(self.expected.as_slice().ct_eq(server_proof))
}
}
#[cfg(test)]
pub(crate) fn server(
pin: &str,
salt: &[u8],
private: &[u8],
) -> (Vec<u8>, impl Fn(&[u8]) -> Vec<u8>) {
let n = n();
let len = n.to_bytes_be().len();
let g = BigUint::from(G);
let inner = hash(&[USER, b":", pin.as_bytes()]);
let x = int(&hash(&[&int(salt).to_bytes_be(), &inner]));
let v = g.modpow(&x, &n);
let k = int(&hash(&[&n.to_bytes_be(), &pad(&g, len)]));
let b = int(private);
let b_pub = (&k * &v + g.modpow(&b, &n)) % &n;
let b_bytes = b_pub.to_bytes_be();
let key = move |a: &[u8]| {
let a_int = int(a);
let u = int(&hash(&[&pad(&a_int, len), &pad(&b_pub, len)]));
let s = (&a_int * v.modpow(&u, &n)).modpow(&b, &n);
hash(&[&s.to_bytes_be()])
};
(b_bytes, key)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn both_sides_derive_one_key_from_one_pin() {
let salt = [0x5a; 16];
let (b, server_key) = server("1026", &salt, &[0x42; 32]);
let client = Exchange::new("1026", &salt, &b, &[0x17; 32]).unwrap();
assert_eq!(client.key, server_key(&client.public));
}
#[test]
fn a_wrong_pin_derives_another_key() {
let salt = [0x5a; 16];
let (b, server_key) = server("1026", &salt, &[0x42; 32]);
let client = Exchange::new("5094", &salt, &b, &[0x17; 32]).unwrap();
assert_ne!(client.key, server_key(&client.public));
}
#[test]
fn the_proof_and_key_match_pyatvs() {
let hex = |s: &str| {
(0..s.len())
.step_by(2)
.map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
.collect::<Vec<u8>>()
};
let b = hex("\
c22e2b1c850c4d2ae0bf9ed8b5c516ea0dec3ad122abe36b9ce593e461385d84\
cb13fb644dedbfd1162cea8cb18f60b6e9b0df5429a1e2d81d6ad3458cc7b056\
301608437ae4ae9002fbfd03dbe7c072180e34a1226fa7131d22a23264c55b26\
259c9b778641aece991a02c871f557fed03b65a12c4a4d41b064593f564a60a0\
5be16093330a94afe9b52f354dc7d4455b3ead0c2802e919abc74ea7ca383689\
2091b3bab621d2d4df4af5d0d0b5e02bbfb3bae2e98f43efec099355411447b8\
619d9755059642e17bf8b417a02ce1679230efd5f4bdcfbedd5fd77c03e77558\
d92bc18870d603a3860bf033590fe81253d409685ecf9b170ebba3973fae2984\
d29dcc199921c327f2a95fc1002e0d437d25a2f15f3dad6ad022cb57926b88c7\
ff92252dfe757d1c6c4ea1614da5840e8d2e10079a3871dd7a912552e2e15fbb\
7f45d69cf2e43491274f78e2e4f29d780bdb66ea172b593d4080055c80437de4\
ccc86bd33570317716714d6280a2a82365c50793a26bf38905830430d7cd41c6");
let client = Exchange::new("1026", &[0x5a; 16], &b, &[0x17; 32]).unwrap();
assert_eq!(
client.proof,
hex("\
8d4f1ddd5a0d47a27db0fadd94d84cc0f27e593b8d9d3c55effb35f28596f36b\
575e7a8468e6aaba17606ee1ce89e228f8ee8b7f1dd9b48252b73ddb302a8220")
);
assert_eq!(
client.key,
hex("\
0f306ad27976a11255cb7a030d512c11721a5124e0144af9943b81035c63201d\
e37730d424f01d3082557b5b84c5a7639b952505ae17d0bb35a6de9176ba3cf9")
);
}
#[test]
fn a_zero_key_from_the_server_is_refused() {
assert!(Exchange::new("1026", &[1; 16], &[0], &[0x17; 32]).is_none());
}
}