1use core::fmt;
39
40use crate::public_key::bigint::BigUint;
41use crate::public_key::io::{
42 decode_biguints, encode_biguints, pem_unwrap, pem_wrap, xml_unwrap, xml_wrap,
43};
44use crate::public_key::primes::{
45 generate_prime_order_group, is_probable_prime, mod_pow, random_nonzero_below,
46};
47use crate::Csprng;
48
49const DH_PARAMS_LABEL: &str = "CRYPTOGRAPHY DH PARAMETERS";
50const DH_PUBLIC_LABEL: &str = "CRYPTOGRAPHY DH PUBLIC KEY";
51const DH_PRIVATE_LABEL: &str = "CRYPTOGRAPHY DH PRIVATE KEY";
52
53#[derive(Clone, Debug, Eq, PartialEq)]
59pub struct DhParams {
60 pub p: BigUint,
62 pub q: BigUint,
64 pub g: BigUint,
66}
67
68#[derive(Clone, Debug, Eq, PartialEq)]
70pub struct DhPublicKey {
71 p: BigUint,
72 q: BigUint,
73 g: BigUint,
74 y: BigUint,
76}
77
78#[derive(Clone, Eq, PartialEq)]
80pub struct DhPrivateKey {
81 p: BigUint,
82 q: BigUint,
83 g: BigUint,
84 x: BigUint,
86 y: BigUint,
88}
89
90pub struct Dh;
91
92impl DhParams {
95 #[must_use]
97 pub fn to_key_blob(&self) -> Vec<u8> {
98 encode_biguints(&[&self.p, &self.q, &self.g])
99 }
100
101 #[must_use]
103 pub fn from_key_blob(blob: &[u8]) -> Option<Self> {
104 let mut fields = decode_biguints(blob)?.into_iter();
105 let p = fields.next()?;
106 let q = fields.next()?;
107 let g = fields.next()?;
108 if fields.next().is_some() || !validate_domain(&p, &q, &g) {
109 return None;
110 }
111 Some(Self { p, q, g })
112 }
113
114 #[must_use]
115 pub fn to_pem(&self) -> String {
116 pem_wrap(DH_PARAMS_LABEL, &self.to_key_blob())
117 }
118
119 #[must_use]
122 pub fn from_pem(pem: &str) -> Option<Self> {
123 Self::from_key_blob(&pem_unwrap(DH_PARAMS_LABEL, pem)?)
124 }
125
126 #[must_use]
127 pub fn to_xml(&self) -> String {
128 xml_wrap(
129 "DhParams",
130 &[("p", &self.p), ("q", &self.q), ("g", &self.g)],
131 )
132 }
133
134 #[must_use]
137 pub fn from_xml(xml: &str) -> Option<Self> {
138 let mut fields = xml_unwrap("DhParams", &["p", "q", "g"], xml)?.into_iter();
139 let p = fields.next()?;
140 let q = fields.next()?;
141 let g = fields.next()?;
142 if fields.next().is_some() || !validate_domain(&p, &q, &g) {
143 return None;
144 }
145 Some(Self { p, q, g })
146 }
147}
148
149impl DhPublicKey {
152 #[must_use]
153 pub fn modulus(&self) -> &BigUint {
154 &self.p
155 }
156
157 #[must_use]
158 pub fn subgroup_order(&self) -> &BigUint {
159 &self.q
160 }
161
162 #[must_use]
163 pub fn generator(&self) -> &BigUint {
164 &self.g
165 }
166
167 #[must_use]
169 pub fn public_component(&self) -> &BigUint {
170 &self.y
171 }
172
173 #[must_use]
174 pub fn params(&self) -> DhParams {
175 DhParams {
176 p: self.p.clone(),
177 q: self.q.clone(),
178 g: self.g.clone(),
179 }
180 }
181
182 #[must_use]
186 pub fn to_key_blob(&self) -> Vec<u8> {
187 encode_biguints(&[&self.p, &self.q, &self.g, &self.y])
188 }
189
190 #[must_use]
192 pub fn from_key_blob(blob: &[u8]) -> Option<Self> {
193 let mut fields = decode_biguints(blob)?.into_iter();
194 let p = fields.next()?;
195 let q = fields.next()?;
196 let g = fields.next()?;
197 let y = fields.next()?;
198 if fields.next().is_some() || !validate_domain(&p, &q, &g) || y <= BigUint::one() || y >= p
199 {
200 return None;
201 }
202 Some(Self { p, q, g, y })
203 }
204
205 #[must_use]
206 pub fn to_pem(&self) -> String {
207 pem_wrap(DH_PUBLIC_LABEL, &self.to_key_blob())
208 }
209
210 #[must_use]
212 pub fn from_pem(pem: &str) -> Option<Self> {
213 Self::from_key_blob(&pem_unwrap(DH_PUBLIC_LABEL, pem)?)
214 }
215
216 #[must_use]
217 pub fn to_xml(&self) -> String {
218 xml_wrap(
219 "DhPublicKey",
220 &[
221 ("p", &self.p),
222 ("q", &self.q),
223 ("g", &self.g),
224 ("y", &self.y),
225 ],
226 )
227 }
228
229 #[must_use]
231 pub fn from_xml(xml: &str) -> Option<Self> {
232 let mut fields = xml_unwrap("DhPublicKey", &["p", "q", "g", "y"], xml)?.into_iter();
233 let p = fields.next()?;
234 let q = fields.next()?;
235 let g = fields.next()?;
236 let y = fields.next()?;
237 if fields.next().is_some() || !validate_domain(&p, &q, &g) || y <= BigUint::one() || y >= p
238 {
239 return None;
240 }
241 Some(Self { p, q, g, y })
242 }
243}
244
245impl DhPrivateKey {
248 #[must_use]
249 pub fn modulus(&self) -> &BigUint {
250 &self.p
251 }
252
253 #[must_use]
254 pub fn subgroup_order(&self) -> &BigUint {
255 &self.q
256 }
257
258 #[must_use]
259 pub fn generator(&self) -> &BigUint {
260 &self.g
261 }
262
263 #[must_use]
265 pub fn exponent(&self) -> &BigUint {
266 &self.x
267 }
268
269 #[must_use]
271 pub fn to_public_key(&self) -> DhPublicKey {
272 DhPublicKey {
273 p: self.p.clone(),
274 q: self.q.clone(),
275 g: self.g.clone(),
276 y: self.y.clone(),
277 }
278 }
279
280 #[must_use]
281 pub fn params(&self) -> DhParams {
282 DhParams {
283 p: self.p.clone(),
284 q: self.q.clone(),
285 g: self.g.clone(),
286 }
287 }
288
289 #[must_use]
297 pub fn agree_element(&self, peer: &DhPublicKey) -> Option<BigUint> {
298 if peer.p != self.p || peer.q != self.q || peer.g != self.g {
300 return None;
301 }
302 if peer.y <= BigUint::one() || peer.y >= self.p {
304 return None;
305 }
306 let check = mod_pow(&peer.y, &self.q, &self.p);
307 if check != BigUint::one() {
308 return None;
309 }
310
311 Some(mod_pow(&peer.y, &self.x, &self.p))
312 }
313
314 #[must_use]
318 pub fn to_key_blob(&self) -> Vec<u8> {
319 encode_biguints(&[&self.p, &self.q, &self.g, &self.x])
320 }
321
322 #[must_use]
324 pub fn from_key_blob(blob: &[u8]) -> Option<Self> {
325 let mut fields = decode_biguints(blob)?.into_iter();
326 let p = fields.next()?;
327 let q = fields.next()?;
328 let g = fields.next()?;
329 let x = fields.next()?;
330 if fields.next().is_some() || !validate_domain(&p, &q, &g) || x.is_zero() || x >= q {
331 return None;
332 }
333 let y = mod_pow(&g, &x, &p);
334 Some(Self { p, q, g, x, y })
335 }
336
337 #[must_use]
338 pub fn to_pem(&self) -> String {
339 pem_wrap(DH_PRIVATE_LABEL, &self.to_key_blob())
340 }
341
342 #[must_use]
344 pub fn from_pem(pem: &str) -> Option<Self> {
345 Self::from_key_blob(&pem_unwrap(DH_PRIVATE_LABEL, pem)?)
346 }
347
348 #[must_use]
349 pub fn to_xml(&self) -> String {
350 xml_wrap(
351 "DhPrivateKey",
352 &[
353 ("p", &self.p),
354 ("q", &self.q),
355 ("g", &self.g),
356 ("x", &self.x),
357 ],
358 )
359 }
360
361 #[must_use]
363 pub fn from_xml(xml: &str) -> Option<Self> {
364 let mut fields = xml_unwrap("DhPrivateKey", &["p", "q", "g", "x"], xml)?.into_iter();
365 let p = fields.next()?;
366 let q = fields.next()?;
367 let g = fields.next()?;
368 let x = fields.next()?;
369 if fields.next().is_some() || !validate_domain(&p, &q, &g) || x.is_zero() || x >= q {
370 return None;
371 }
372 let y = mod_pow(&g, &x, &p);
373 Some(Self { p, q, g, x, y })
374 }
375}
376
377impl fmt::Debug for DhPrivateKey {
378 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
379 f.write_str("DhPrivateKey(<redacted>)")
380 }
381}
382
383impl Dh {
386 #[must_use]
391 pub fn generate_params<R: Csprng>(rng: &mut R, bits: usize) -> Option<DhParams> {
392 let (p, q, _cofactor, g) = generate_prime_order_group(rng, bits)?;
393 Some(DhParams { p, q, g })
394 }
395
396 #[must_use]
398 pub fn generate<R: Csprng>(params: &DhParams, rng: &mut R) -> (DhPublicKey, DhPrivateKey) {
399 let x = random_nonzero_below(rng, ¶ms.q)
400 .expect("subgroup order is always > 1 for valid parameters");
401 let y = mod_pow(¶ms.g, &x, ¶ms.p);
402 (
403 DhPublicKey {
404 p: params.p.clone(),
405 q: params.q.clone(),
406 g: params.g.clone(),
407 y: y.clone(),
408 },
409 DhPrivateKey {
410 p: params.p.clone(),
411 q: params.q.clone(),
412 g: params.g.clone(),
413 x,
414 y,
415 },
416 )
417 }
418}
419
420fn validate_domain(p: &BigUint, q: &BigUint, g: &BigUint) -> bool {
425 if !is_probable_prime(p) || !is_probable_prime(q) {
426 return false;
427 }
428 if q >= p {
429 return false;
430 }
431 let p_minus_one = p.sub_ref(&BigUint::one());
432 if !p_minus_one.modulo(q).is_zero() {
433 return false;
434 }
435 if g <= &BigUint::one() || g >= p {
436 return false;
437 }
438 mod_pow(g, q, p) == BigUint::one()
439}
440
441#[cfg(test)]
444mod tests {
445 use super::{Dh, DhParams, DhPrivateKey, DhPublicKey};
446 use crate::public_key::bigint::BigUint;
447 use crate::CtrDrbgAes256;
448
449 fn rng() -> CtrDrbgAes256 {
450 CtrDrbgAes256::new(&[0x33; 48])
451 }
452
453 fn toy_params() -> super::DhParams {
455 DhParams {
456 p: BigUint::from_u64(23),
457 q: BigUint::from_u64(11),
458 g: BigUint::from_u64(4),
459 }
460 }
461
462 #[test]
465 fn agreement_toy_params() {
466 let params = toy_params();
467 let mut rng = rng();
468 let (pub_a, priv_a) = Dh::generate(¶ms, &mut rng);
469 let (pub_b, priv_b) = Dh::generate(¶ms, &mut rng);
470 let s_a = priv_a.agree_element(&pub_b).expect("agree A→B");
471 let s_b = priv_b.agree_element(&pub_a).expect("agree B→A");
472 assert_eq!(s_a, s_b);
473 }
474
475 #[test]
476 fn agreement_generated_params() {
477 let mut rng = rng();
478 let params = Dh::generate_params(&mut rng, 512).expect("params");
479 let (pub_a, priv_a) = Dh::generate(¶ms, &mut rng);
480 let (pub_b, priv_b) = Dh::generate(¶ms, &mut rng);
481 let s_a = priv_a.agree_element(&pub_b).expect("agree A");
482 let s_b = priv_b.agree_element(&pub_a).expect("agree B");
483 assert_eq!(s_a, s_b);
484 }
485
486 #[test]
487 fn to_public_key_matches() {
488 let params = toy_params();
489 let mut rng = rng();
490 let (public, private) = Dh::generate(¶ms, &mut rng);
491 let derived = private.to_public_key();
492 assert_eq!(derived.y, public.y);
493 }
494
495 #[test]
498 fn mismatched_params_rejected() {
499 let p1 = toy_params();
500 let p2 = DhParams {
502 p: BigUint::from_u64(23),
503 q: BigUint::from_u64(11),
504 g: BigUint::from_u64(2), };
506 let mut rng = rng();
507 let (pub_a, _) = Dh::generate(&p1, &mut rng);
508 let (_, priv_b) = Dh::generate(&p2, &mut rng);
509 assert!(priv_b.agree_element(&pub_a).is_none());
510 }
511
512 #[test]
515 fn params_binary_roundtrip() {
516 let params = toy_params();
517 let blob = params.to_key_blob();
518 let recovered = DhParams::from_key_blob(&blob).expect("from_binary");
519 assert_eq!(recovered, params);
520 }
521
522 #[test]
523 fn params_pem_roundtrip() {
524 let params = toy_params();
525 let pem = params.to_pem();
526 assert!(pem.contains("DH PARAMETERS"));
527 let recovered = DhParams::from_pem(&pem).expect("from_pem");
528 assert_eq!(recovered, params);
529 }
530
531 #[test]
532 fn params_xml_roundtrip() {
533 let params = toy_params();
534 let xml = params.to_xml();
535 assert!(xml.contains("DhParams"));
536 let recovered = DhParams::from_xml(&xml).expect("from_xml");
537 assert_eq!(recovered, params);
538 }
539
540 #[test]
541 fn public_key_binary_roundtrip() {
542 let params = toy_params();
543 let mut rng = rng();
544 let (public, _) = Dh::generate(¶ms, &mut rng);
545 let blob = public.to_key_blob();
546 let recovered = DhPublicKey::from_key_blob(&blob).expect("from_binary");
547 assert_eq!(recovered.y, public.y);
548 }
549
550 #[test]
551 fn private_key_binary_roundtrip() {
552 let params = toy_params();
553 let mut rng = rng();
554 let (_, private) = Dh::generate(¶ms, &mut rng);
555 let blob = private.to_key_blob();
556 let recovered = DhPrivateKey::from_key_blob(&blob).expect("from_binary");
557 assert_eq!(recovered.x, private.x);
558 }
559
560 #[test]
561 fn public_key_pem_roundtrip() {
562 let params = toy_params();
563 let mut rng = rng();
564 let (public, _) = Dh::generate(¶ms, &mut rng);
565 let pem = public.to_pem();
566 assert!(pem.contains("DH PUBLIC KEY"));
567 let recovered = DhPublicKey::from_pem(&pem).expect("from_pem");
568 assert_eq!(recovered.y, public.y);
569 }
570
571 #[test]
572 fn private_key_pem_roundtrip() {
573 let params = toy_params();
574 let mut rng = rng();
575 let (_, private) = Dh::generate(¶ms, &mut rng);
576 let pem = private.to_pem();
577 assert!(pem.contains("DH PRIVATE KEY"));
578 let recovered = DhPrivateKey::from_pem(&pem).expect("from_pem");
579 assert_eq!(recovered.x, private.x);
580 }
581
582 #[test]
583 fn public_key_xml_roundtrip() {
584 let params = toy_params();
585 let mut rng = rng();
586 let (public, _) = Dh::generate(¶ms, &mut rng);
587 let xml = public.to_xml();
588 assert!(xml.contains("DhPublicKey"));
589 let recovered = DhPublicKey::from_xml(&xml).expect("from_xml");
590 assert_eq!(recovered.y, public.y);
591 }
592
593 #[test]
594 fn private_key_xml_roundtrip() {
595 let params = toy_params();
596 let mut rng = rng();
597 let (_, private) = Dh::generate(¶ms, &mut rng);
598 let xml = private.to_xml();
599 let recovered = DhPrivateKey::from_xml(&xml).expect("from_xml");
600 assert_eq!(recovered.x, private.x);
601 }
602
603 #[test]
604 fn debug_private_key_redacted() {
605 let params = toy_params();
606 let mut rng = rng();
607 let (_, private) = Dh::generate(¶ms, &mut rng);
608 assert_eq!(format!("{private:?}"), "DhPrivateKey(<redacted>)");
609 }
610}