use core::fmt;
use crate::public_key::bigint::BigUint;
use crate::public_key::io::{
decode_biguints, encode_biguints, pem_unwrap, pem_wrap, xml_unwrap, xml_wrap,
};
use crate::public_key::primes::{
generate_prime_order_group, is_probable_prime, mod_pow, random_nonzero_below,
};
use crate::Csprng;
const DH_PARAMS_LABEL: &str = "CRYPTOGRAPHY DH PARAMETERS";
const DH_PUBLIC_LABEL: &str = "CRYPTOGRAPHY DH PUBLIC KEY";
const DH_PRIVATE_LABEL: &str = "CRYPTOGRAPHY DH PRIVATE KEY";
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DhParams {
pub p: BigUint,
pub q: BigUint,
pub g: BigUint,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DhPublicKey {
p: BigUint,
q: BigUint,
g: BigUint,
y: BigUint,
}
#[derive(Clone, Eq, PartialEq)]
pub struct DhPrivateKey {
p: BigUint,
q: BigUint,
g: BigUint,
x: BigUint,
y: BigUint,
}
pub struct Dh;
impl DhParams {
#[must_use]
pub fn to_key_blob(&self) -> Vec<u8> {
encode_biguints(&[&self.p, &self.q, &self.g])
}
#[must_use]
pub fn from_key_blob(blob: &[u8]) -> Option<Self> {
let mut fields = decode_biguints(blob)?.into_iter();
let p = fields.next()?;
let q = fields.next()?;
let g = fields.next()?;
if fields.next().is_some() || !validate_domain(&p, &q, &g) {
return None;
}
Some(Self { p, q, g })
}
#[must_use]
pub fn to_pem(&self) -> String {
pem_wrap(DH_PARAMS_LABEL, &self.to_key_blob())
}
#[must_use]
pub fn from_pem(pem: &str) -> Option<Self> {
Self::from_key_blob(&pem_unwrap(DH_PARAMS_LABEL, pem)?)
}
#[must_use]
pub fn to_xml(&self) -> String {
xml_wrap(
"DhParams",
&[("p", &self.p), ("q", &self.q), ("g", &self.g)],
)
}
#[must_use]
pub fn from_xml(xml: &str) -> Option<Self> {
let mut fields = xml_unwrap("DhParams", &["p", "q", "g"], xml)?.into_iter();
let p = fields.next()?;
let q = fields.next()?;
let g = fields.next()?;
if fields.next().is_some() || !validate_domain(&p, &q, &g) {
return None;
}
Some(Self { p, q, g })
}
}
impl DhPublicKey {
#[must_use]
pub fn modulus(&self) -> &BigUint {
&self.p
}
#[must_use]
pub fn subgroup_order(&self) -> &BigUint {
&self.q
}
#[must_use]
pub fn generator(&self) -> &BigUint {
&self.g
}
#[must_use]
pub fn public_component(&self) -> &BigUint {
&self.y
}
#[must_use]
pub fn params(&self) -> DhParams {
DhParams {
p: self.p.clone(),
q: self.q.clone(),
g: self.g.clone(),
}
}
#[must_use]
pub fn to_key_blob(&self) -> Vec<u8> {
encode_biguints(&[&self.p, &self.q, &self.g, &self.y])
}
#[must_use]
pub fn from_key_blob(blob: &[u8]) -> Option<Self> {
let mut fields = decode_biguints(blob)?.into_iter();
let p = fields.next()?;
let q = fields.next()?;
let g = fields.next()?;
let y = fields.next()?;
if fields.next().is_some() || !validate_domain(&p, &q, &g) || y <= BigUint::one() || y >= p
{
return None;
}
Some(Self { p, q, g, y })
}
#[must_use]
pub fn to_pem(&self) -> String {
pem_wrap(DH_PUBLIC_LABEL, &self.to_key_blob())
}
#[must_use]
pub fn from_pem(pem: &str) -> Option<Self> {
Self::from_key_blob(&pem_unwrap(DH_PUBLIC_LABEL, pem)?)
}
#[must_use]
pub fn to_xml(&self) -> String {
xml_wrap(
"DhPublicKey",
&[
("p", &self.p),
("q", &self.q),
("g", &self.g),
("y", &self.y),
],
)
}
#[must_use]
pub fn from_xml(xml: &str) -> Option<Self> {
let mut fields = xml_unwrap("DhPublicKey", &["p", "q", "g", "y"], xml)?.into_iter();
let p = fields.next()?;
let q = fields.next()?;
let g = fields.next()?;
let y = fields.next()?;
if fields.next().is_some() || !validate_domain(&p, &q, &g) || y <= BigUint::one() || y >= p
{
return None;
}
Some(Self { p, q, g, y })
}
}
impl DhPrivateKey {
#[must_use]
pub fn modulus(&self) -> &BigUint {
&self.p
}
#[must_use]
pub fn subgroup_order(&self) -> &BigUint {
&self.q
}
#[must_use]
pub fn generator(&self) -> &BigUint {
&self.g
}
#[must_use]
pub fn exponent(&self) -> &BigUint {
&self.x
}
#[must_use]
pub fn to_public_key(&self) -> DhPublicKey {
DhPublicKey {
p: self.p.clone(),
q: self.q.clone(),
g: self.g.clone(),
y: self.y.clone(),
}
}
#[must_use]
pub fn params(&self) -> DhParams {
DhParams {
p: self.p.clone(),
q: self.q.clone(),
g: self.g.clone(),
}
}
#[must_use]
pub fn agree_element(&self, peer: &DhPublicKey) -> Option<BigUint> {
if peer.p != self.p || peer.q != self.q || peer.g != self.g {
return None;
}
if peer.y <= BigUint::one() || peer.y >= self.p {
return None;
}
let check = mod_pow(&peer.y, &self.q, &self.p);
if check != BigUint::one() {
return None;
}
Some(mod_pow(&peer.y, &self.x, &self.p))
}
#[must_use]
pub fn to_key_blob(&self) -> Vec<u8> {
encode_biguints(&[&self.p, &self.q, &self.g, &self.x])
}
#[must_use]
pub fn from_key_blob(blob: &[u8]) -> Option<Self> {
let mut fields = decode_biguints(blob)?.into_iter();
let p = fields.next()?;
let q = fields.next()?;
let g = fields.next()?;
let x = fields.next()?;
if fields.next().is_some() || !validate_domain(&p, &q, &g) || x.is_zero() || x >= q {
return None;
}
let y = mod_pow(&g, &x, &p);
Some(Self { p, q, g, x, y })
}
#[must_use]
pub fn to_pem(&self) -> String {
pem_wrap(DH_PRIVATE_LABEL, &self.to_key_blob())
}
#[must_use]
pub fn from_pem(pem: &str) -> Option<Self> {
Self::from_key_blob(&pem_unwrap(DH_PRIVATE_LABEL, pem)?)
}
#[must_use]
pub fn to_xml(&self) -> String {
xml_wrap(
"DhPrivateKey",
&[
("p", &self.p),
("q", &self.q),
("g", &self.g),
("x", &self.x),
],
)
}
#[must_use]
pub fn from_xml(xml: &str) -> Option<Self> {
let mut fields = xml_unwrap("DhPrivateKey", &["p", "q", "g", "x"], xml)?.into_iter();
let p = fields.next()?;
let q = fields.next()?;
let g = fields.next()?;
let x = fields.next()?;
if fields.next().is_some() || !validate_domain(&p, &q, &g) || x.is_zero() || x >= q {
return None;
}
let y = mod_pow(&g, &x, &p);
Some(Self { p, q, g, x, y })
}
}
impl fmt::Debug for DhPrivateKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("DhPrivateKey(<redacted>)")
}
}
impl Dh {
#[must_use]
pub fn generate_params<R: Csprng>(rng: &mut R, bits: usize) -> Option<DhParams> {
let (p, q, _cofactor, g) = generate_prime_order_group(rng, bits)?;
Some(DhParams { p, q, g })
}
#[must_use]
pub fn generate<R: Csprng>(params: &DhParams, rng: &mut R) -> (DhPublicKey, DhPrivateKey) {
let x = random_nonzero_below(rng, ¶ms.q)
.expect("subgroup order is always > 1 for valid parameters");
let y = mod_pow(¶ms.g, &x, ¶ms.p);
(
DhPublicKey {
p: params.p.clone(),
q: params.q.clone(),
g: params.g.clone(),
y: y.clone(),
},
DhPrivateKey {
p: params.p.clone(),
q: params.q.clone(),
g: params.g.clone(),
x,
y,
},
)
}
}
fn validate_domain(p: &BigUint, q: &BigUint, g: &BigUint) -> bool {
if !is_probable_prime(p) || !is_probable_prime(q) {
return false;
}
if q >= p {
return false;
}
let p_minus_one = p.sub_ref(&BigUint::one());
if !p_minus_one.modulo(q).is_zero() {
return false;
}
if g <= &BigUint::one() || g >= p {
return false;
}
mod_pow(g, q, p) == BigUint::one()
}
#[cfg(test)]
mod tests {
use super::{Dh, DhParams, DhPrivateKey, DhPublicKey};
use crate::public_key::bigint::BigUint;
use crate::CtrDrbgAes256;
fn rng() -> CtrDrbgAes256 {
CtrDrbgAes256::new(&[0x33; 48])
}
fn toy_params() -> super::DhParams {
DhParams {
p: BigUint::from_u64(23),
q: BigUint::from_u64(11),
g: BigUint::from_u64(4),
}
}
#[test]
fn agreement_toy_params() {
let params = toy_params();
let mut rng = rng();
let (pub_a, priv_a) = Dh::generate(¶ms, &mut rng);
let (pub_b, priv_b) = Dh::generate(¶ms, &mut rng);
let s_a = priv_a.agree_element(&pub_b).expect("agree A→B");
let s_b = priv_b.agree_element(&pub_a).expect("agree B→A");
assert_eq!(s_a, s_b);
}
#[test]
fn agreement_generated_params() {
let mut rng = rng();
let params = Dh::generate_params(&mut rng, 512).expect("params");
let (pub_a, priv_a) = Dh::generate(¶ms, &mut rng);
let (pub_b, priv_b) = Dh::generate(¶ms, &mut rng);
let s_a = priv_a.agree_element(&pub_b).expect("agree A");
let s_b = priv_b.agree_element(&pub_a).expect("agree B");
assert_eq!(s_a, s_b);
}
#[test]
fn to_public_key_matches() {
let params = toy_params();
let mut rng = rng();
let (public, private) = Dh::generate(¶ms, &mut rng);
let derived = private.to_public_key();
assert_eq!(derived.y, public.y);
}
#[test]
fn mismatched_params_rejected() {
let p1 = toy_params();
let p2 = DhParams {
p: BigUint::from_u64(23),
q: BigUint::from_u64(11),
g: BigUint::from_u64(2), };
let mut rng = rng();
let (pub_a, _) = Dh::generate(&p1, &mut rng);
let (_, priv_b) = Dh::generate(&p2, &mut rng);
assert!(priv_b.agree_element(&pub_a).is_none());
}
#[test]
fn params_binary_roundtrip() {
let params = toy_params();
let blob = params.to_key_blob();
let recovered = DhParams::from_key_blob(&blob).expect("from_binary");
assert_eq!(recovered, params);
}
#[test]
fn params_pem_roundtrip() {
let params = toy_params();
let pem = params.to_pem();
assert!(pem.contains("DH PARAMETERS"));
let recovered = DhParams::from_pem(&pem).expect("from_pem");
assert_eq!(recovered, params);
}
#[test]
fn params_xml_roundtrip() {
let params = toy_params();
let xml = params.to_xml();
assert!(xml.contains("DhParams"));
let recovered = DhParams::from_xml(&xml).expect("from_xml");
assert_eq!(recovered, params);
}
#[test]
fn public_key_binary_roundtrip() {
let params = toy_params();
let mut rng = rng();
let (public, _) = Dh::generate(¶ms, &mut rng);
let blob = public.to_key_blob();
let recovered = DhPublicKey::from_key_blob(&blob).expect("from_binary");
assert_eq!(recovered.y, public.y);
}
#[test]
fn private_key_binary_roundtrip() {
let params = toy_params();
let mut rng = rng();
let (_, private) = Dh::generate(¶ms, &mut rng);
let blob = private.to_key_blob();
let recovered = DhPrivateKey::from_key_blob(&blob).expect("from_binary");
assert_eq!(recovered.x, private.x);
}
#[test]
fn public_key_pem_roundtrip() {
let params = toy_params();
let mut rng = rng();
let (public, _) = Dh::generate(¶ms, &mut rng);
let pem = public.to_pem();
assert!(pem.contains("DH PUBLIC KEY"));
let recovered = DhPublicKey::from_pem(&pem).expect("from_pem");
assert_eq!(recovered.y, public.y);
}
#[test]
fn private_key_pem_roundtrip() {
let params = toy_params();
let mut rng = rng();
let (_, private) = Dh::generate(¶ms, &mut rng);
let pem = private.to_pem();
assert!(pem.contains("DH PRIVATE KEY"));
let recovered = DhPrivateKey::from_pem(&pem).expect("from_pem");
assert_eq!(recovered.x, private.x);
}
#[test]
fn public_key_xml_roundtrip() {
let params = toy_params();
let mut rng = rng();
let (public, _) = Dh::generate(¶ms, &mut rng);
let xml = public.to_xml();
assert!(xml.contains("DhPublicKey"));
let recovered = DhPublicKey::from_xml(&xml).expect("from_xml");
assert_eq!(recovered.y, public.y);
}
#[test]
fn private_key_xml_roundtrip() {
let params = toy_params();
let mut rng = rng();
let (_, private) = Dh::generate(¶ms, &mut rng);
let xml = private.to_xml();
let recovered = DhPrivateKey::from_xml(&xml).expect("from_xml");
assert_eq!(recovered.x, private.x);
}
#[test]
fn debug_private_key_redacted() {
let params = toy_params();
let mut rng = rng();
let (_, private) = Dh::generate(¶ms, &mut rng);
assert_eq!(format!("{private:?}"), "DhPrivateKey(<redacted>)");
}
}