Skip to main content

native_ossl/
kdf.rs

1//! Key derivation — `KdfAlg`, `KdfCtx`, and typed builders.
2//!
3//! Phase 6 delivers a low-level `EVP_KDF` wrapper and three typed builders:
4//!
5//! | Builder           | Algorithm   | RFC       |
6//! |-------------------|-------------|-----------|
7//! | [`HkdfBuilder`]   | HKDF        | RFC 5869  |
8//! | [`Pbkdf2Builder`] | PBKDF2      | PKCS #5   |
9//! | [`ScryptBuilder`] | scrypt      | RFC 7914  |
10
11use crate::error::ErrorStack;
12use native_ossl_sys as sys;
13use std::ffi::CStr;
14
15// ── KdfAlg — algorithm descriptor ────────────────────────────────────────────
16
17/// An OpenSSL KDF algorithm descriptor (`EVP_KDF*`).
18///
19/// Fetched once and reused to create [`KdfCtx`] instances.
20pub struct KdfAlg {
21    ptr: *mut sys::EVP_KDF,
22}
23
24impl KdfAlg {
25    /// Fetch a KDF algorithm from the global default library context.
26    ///
27    /// Common names: `c"HKDF"`, `c"PBKDF2"`, `c"SCRYPT"`, `c"TLS13-KDF"`.
28    ///
29    /// # Errors
30    ///
31    /// Returns `Err` if the algorithm is not available.
32    pub fn fetch(name: &CStr) -> Result<Self, ErrorStack> {
33        let ptr = unsafe {
34            sys::EVP_KDF_fetch(std::ptr::null_mut(), name.as_ptr(), std::ptr::null())
35        };
36        if ptr.is_null() {
37            return Err(ErrorStack::drain());
38        }
39        Ok(KdfAlg { ptr })
40    }
41
42    fn as_ptr(&self) -> *mut sys::EVP_KDF {
43        self.ptr
44    }
45}
46
47impl Drop for KdfAlg {
48    fn drop(&mut self) {
49        unsafe { sys::EVP_KDF_free(self.ptr) };
50    }
51}
52
53// SAFETY: `EVP_KDF*` is reference-counted and immutable after fetch.
54unsafe impl Send for KdfAlg {}
55unsafe impl Sync for KdfAlg {}
56
57// ── KdfCtx — stateful context ─────────────────────────────────────────────────
58
59/// A key-derivation context (`EVP_KDF_CTX*`).
60///
61/// `!Clone` — create a new context for each derive operation.
62pub struct KdfCtx {
63    ptr: *mut sys::EVP_KDF_CTX,
64}
65
66impl KdfCtx {
67    /// Create a new context from an algorithm descriptor.
68    ///
69    /// # Errors
70    pub fn new(alg: &KdfAlg) -> Result<Self, ErrorStack> {
71        let ptr = unsafe { sys::EVP_KDF_CTX_new(alg.as_ptr()) };
72        if ptr.is_null() {
73            return Err(ErrorStack::drain());
74        }
75        Ok(KdfCtx { ptr })
76    }
77
78    /// Derive key material into `out`.
79    ///
80    /// Parameters are supplied at derive time via the `params` argument.
81    ///
82    /// # Errors
83    pub fn derive(
84        &mut self,
85        out: &mut [u8],
86        params: &crate::params::Params<'_>,
87    ) -> Result<(), ErrorStack> {
88        crate::ossl_call!(sys::EVP_KDF_derive(
89            self.ptr,
90            out.as_mut_ptr(),
91            out.len(),
92            params.as_ptr()
93        ))
94    }
95}
96
97impl Drop for KdfCtx {
98    fn drop(&mut self) {
99        unsafe { sys::EVP_KDF_CTX_free(self.ptr) };
100    }
101}
102
103unsafe impl Send for KdfCtx {}
104
105// ── HKDF mode ─────────────────────────────────────────────────────────────────
106
107/// HKDF extraction/expansion mode.
108#[derive(Default, Clone, Copy, PartialEq, Eq)]
109pub enum HkdfMode {
110    /// Extract a pseudorandom key (PRK) from the IKM, then expand it.
111    ///
112    /// This is the standard two-step mode from RFC 5869 §2.
113    #[default]
114    ExtractAndExpand,
115    /// Extract phase only — output is the PRK.
116    ExtractOnly,
117    /// Expand phase only — input key must already be a PRK.
118    ExpandOnly,
119}
120
121impl HkdfMode {
122    fn as_uint(self) -> u32 {
123        match self {
124            HkdfMode::ExtractAndExpand => 0,
125            HkdfMode::ExtractOnly => 1,
126            HkdfMode::ExpandOnly => 2,
127        }
128    }
129}
130
131// ── HkdfBuilder ───────────────────────────────────────────────────────────────
132
133/// HKDF key-derivation builder (RFC 5869).
134///
135/// ```ignore
136/// let okm = HkdfBuilder::new(&sha256)
137///     .key(ikm)
138///     .salt(salt)
139///     .info(info)
140///     .derive_to_vec(32)?;
141/// ```
142pub struct HkdfBuilder<'a> {
143    digest: &'a crate::digest::DigestAlg,
144    key: Option<&'a [u8]>,
145    salt: Option<&'a [u8]>,
146    info: Option<&'a [u8]>,
147    mode: HkdfMode,
148}
149
150impl<'a> HkdfBuilder<'a> {
151    /// Create an `HKDF` builder bound to `digest`.
152    ///
153    /// The mode defaults to `ExtractAndExpand`.
154    #[must_use]
155    pub fn new(digest: &'a crate::digest::DigestAlg) -> Self {
156        HkdfBuilder {
157            digest,
158            key: None,
159            salt: None,
160            info: None,
161            mode: HkdfMode::default(),
162        }
163    }
164
165    /// Set the input keying material (IKM).
166    #[must_use]
167    pub fn key(mut self, key: &'a [u8]) -> Self {
168        self.key = Some(key);
169        self
170    }
171
172    /// Set the salt (optional; defaults to zero-length within OpenSSL).
173    #[must_use]
174    pub fn salt(mut self, salt: &'a [u8]) -> Self {
175        self.salt = Some(salt);
176        self
177    }
178
179    /// Set the context/application-specific information bytes.
180    #[must_use]
181    pub fn info(mut self, info: &'a [u8]) -> Self {
182        self.info = Some(info);
183        self
184    }
185
186    /// Override the default extract-and-expand mode.
187    #[must_use]
188    pub fn mode(mut self, mode: HkdfMode) -> Self {
189        self.mode = mode;
190        self
191    }
192
193    /// Derive key material, writing into `out`.
194    ///
195    /// # Errors
196    pub fn derive(self, out: &mut [u8]) -> Result<(), ErrorStack> {
197        let name_ptr = unsafe { sys::OBJ_nid2sn(self.digest.nid()) };
198        if name_ptr.is_null() {
199            return Err(ErrorStack::drain());
200        }
201        let name = unsafe { CStr::from_ptr(name_ptr) };
202
203        let mut builder = crate::params::ParamBuilder::new()?
204            .push_utf8_string(c"digest", name)?
205            .push_uint(c"mode", self.mode.as_uint())?;
206
207        if let Some(k) = self.key {
208            builder = builder.push_octet_slice(c"key", k)?;
209        }
210        if let Some(s) = self.salt {
211            builder = builder.push_octet_slice(c"salt", s)?;
212        }
213        if let Some(i) = self.info {
214            builder = builder.push_octet_slice(c"info", i)?;
215        }
216
217        let params = builder.build()?;
218        let alg = KdfAlg::fetch(c"HKDF")?;
219        KdfCtx::new(&alg)?.derive(out, &params)
220    }
221
222    /// Derive `len` bytes of key material, returning them in a freshly allocated `Vec<u8>`.
223    ///
224    /// # Errors
225    pub fn derive_to_vec(self, len: usize) -> Result<Vec<u8>, ErrorStack> {
226        let mut out = vec![0u8; len];
227        self.derive(&mut out)?;
228        Ok(out)
229    }
230}
231
232// ── Pbkdf2Builder ─────────────────────────────────────────────────────────────
233
234/// `PBKDF2` key-derivation builder (PKCS #5).
235///
236/// ```ignore
237/// let dk = Pbkdf2Builder::new(&sha256, b"password", b"salt")
238///     .iterations(600_000)
239///     .derive_to_vec(32)?;
240/// ```
241pub struct Pbkdf2Builder<'a> {
242    digest: &'a crate::digest::DigestAlg,
243    password: &'a [u8],
244    salt: &'a [u8],
245    iterations: u32,
246}
247
248impl<'a> Pbkdf2Builder<'a> {
249    /// Create a `PBKDF2` builder.
250    ///
251    /// The iteration count defaults to 600 000 (NIST SP 800-132 minimum for
252    /// SHA-256 as of 2023).
253    #[must_use]
254    pub fn new(
255        digest: &'a crate::digest::DigestAlg,
256        password: &'a [u8],
257        salt: &'a [u8],
258    ) -> Self {
259        Pbkdf2Builder {
260            digest,
261            password,
262            salt,
263            iterations: 600_000,
264        }
265    }
266
267    /// Override the iteration count.
268    #[must_use]
269    pub fn iterations(mut self, n: u32) -> Self {
270        self.iterations = n;
271        self
272    }
273
274    /// Derive key material, writing into `out`.
275    ///
276    /// # Errors
277    pub fn derive(self, out: &mut [u8]) -> Result<(), ErrorStack> {
278        let name_ptr = unsafe { sys::OBJ_nid2sn(self.digest.nid()) };
279        if name_ptr.is_null() {
280            return Err(ErrorStack::drain());
281        }
282        let name = unsafe { CStr::from_ptr(name_ptr) };
283
284        let params = crate::params::ParamBuilder::new()?
285            .push_octet_slice(c"pass", self.password)?
286            .push_octet_slice(c"salt", self.salt)?
287            .push_uint(c"iter", self.iterations)?
288            .push_utf8_string(c"digest", name)?
289            .build()?;
290
291        let alg = KdfAlg::fetch(c"PBKDF2")?;
292        KdfCtx::new(&alg)?.derive(out, &params)
293    }
294
295    /// Derive `len` bytes of key material, returning them in a freshly allocated `Vec<u8>`.
296    ///
297    /// # Errors
298    pub fn derive_to_vec(self, len: usize) -> Result<Vec<u8>, ErrorStack> {
299        let mut out = vec![0u8; len];
300        self.derive(&mut out)?;
301        Ok(out)
302    }
303}
304
305// ── ScryptParams / ScryptBuilder ──────────────────────────────────────────────
306
307/// Scrypt cost parameters.
308///
309/// Use struct-update syntax for partial overrides:
310/// ```ignore
311/// ScryptParams { n: 1 << 20, ..ScryptParams::default() }
312/// ```
313pub struct ScryptParams {
314    /// CPU/memory cost factor — must be a power of two and greater than 1.
315    pub n: u64,
316    /// Block size factor.
317    pub r: u32,
318    /// Parallelisation factor.
319    pub p: u32,
320}
321
322impl Default for ScryptParams {
323    fn default() -> Self {
324        // RFC 7914 §2 interactive-login recommendation.
325        ScryptParams { n: 16_384, r: 8, p: 1 }
326    }
327}
328
329/// Scrypt key-derivation builder (RFC 7914).
330///
331/// ```ignore
332/// let dk = ScryptBuilder::new(b"password", b"NaCl")
333///     .params(ScryptParams { n: 1 << 20, ..ScryptParams::default() })
334///     .derive_to_vec(64)?;
335/// ```
336pub struct ScryptBuilder<'a> {
337    password: &'a [u8],
338    salt: &'a [u8],
339    params: ScryptParams,
340}
341
342impl<'a> ScryptBuilder<'a> {
343    /// Create a scrypt builder.
344    ///
345    /// Cost parameters default to `ScryptParams::default()`.
346    #[must_use]
347    pub fn new(password: &'a [u8], salt: &'a [u8]) -> Self {
348        ScryptBuilder {
349            password,
350            salt,
351            params: ScryptParams::default(),
352        }
353    }
354
355    /// Override the cost parameters.
356    #[must_use]
357    pub fn params(mut self, params: ScryptParams) -> Self {
358        self.params = params;
359        self
360    }
361
362    /// Derive key material, writing into `out`.
363    ///
364    /// # Errors
365    pub fn derive(self, out: &mut [u8]) -> Result<(), ErrorStack> {
366        let params = crate::params::ParamBuilder::new()?
367            .push_octet_slice(c"pass", self.password)?
368            .push_octet_slice(c"salt", self.salt)?
369            .push_uint64(c"n", self.params.n)?
370            .push_uint(c"r", self.params.r)?
371            .push_uint(c"p", self.params.p)?
372            .build()?;
373
374        let alg = KdfAlg::fetch(c"SCRYPT")?;
375        KdfCtx::new(&alg)?.derive(out, &params)
376    }
377
378    /// Derive `len` bytes of key material, returning them in a freshly allocated `Vec<u8>`.
379    ///
380    /// # Errors
381    pub fn derive_to_vec(self, len: usize) -> Result<Vec<u8>, ErrorStack> {
382        let mut out = vec![0u8; len];
383        self.derive(&mut out)?;
384        Ok(out)
385    }
386}
387
388// ── SSH-KDF (OpenSSL 3.5+) ────────────────────────────────────────────────────
389
390/// Purpose codes for SSH key derivation (RFC 4253 §7.2).
391#[cfg(ossl350)]
392#[derive(Clone, Copy, Debug, PartialEq, Eq)]
393pub enum SshkdfKeyType {
394    /// Initial IV, client to server.
395    InitialIvClientToServer,
396    /// Initial IV, server to client.
397    InitialIvServerToClient,
398    /// Encryption key, client to server.
399    EncryptionKeyClientToServer,
400    /// Encryption key, server to client.
401    EncryptionKeyServerToClient,
402    /// Integrity key, client to server.
403    IntegrityKeyClientToServer,
404    /// Integrity key, server to client.
405    IntegrityKeyServerToClient,
406}
407
408#[cfg(ossl350)]
409impl SshkdfKeyType {
410    fn as_cstr(self) -> &'static CStr {
411        match self {
412            Self::InitialIvClientToServer     => c"A",
413            Self::InitialIvServerToClient     => c"B",
414            Self::EncryptionKeyClientToServer => c"C",
415            Self::EncryptionKeyServerToClient => c"D",
416            Self::IntegrityKeyClientToServer  => c"E",
417            Self::IntegrityKeyServerToClient  => c"F",
418        }
419    }
420}
421
422/// SSH key-derivation builder (RFC 4253 §7.2).
423///
424/// ```ignore
425/// let iv = SshkdfBuilder::new(&sha256, &shared_secret, &exchange_hash, &session_id,
426///                             SshkdfKeyType::InitialIvClientToServer)
427///     .derive_to_vec(16)?;
428/// ```
429#[cfg(ossl350)]
430pub struct SshkdfBuilder<'a> {
431    digest: &'a crate::digest::DigestAlg,
432    key: &'a [u8],
433    xcghash: &'a [u8],
434    session_id: &'a [u8],
435    key_type: SshkdfKeyType,
436}
437
438#[cfg(ossl350)]
439impl<'a> SshkdfBuilder<'a> {
440    /// Create an SSH-KDF builder.
441    ///
442    /// - `digest` — hash algorithm (e.g. SHA-256).
443    /// - `key` — the shared secret `K` from the Diffie-Hellman exchange.
444    /// - `xcghash` — exchange hash `H`.
445    /// - `session_id` — the session identifier (= first `H` for the session).
446    /// - `key_type` — which key/IV component to derive (A–F).
447    #[must_use]
448    pub fn new(
449        digest: &'a crate::digest::DigestAlg,
450        key: &'a [u8],
451        xcghash: &'a [u8],
452        session_id: &'a [u8],
453        key_type: SshkdfKeyType,
454    ) -> Self {
455        SshkdfBuilder { digest, key, xcghash, session_id, key_type }
456    }
457
458    /// Derive key material, writing into `out`.
459    ///
460    /// # Errors
461    pub fn derive(self, out: &mut [u8]) -> Result<(), ErrorStack> {
462        let name_ptr = unsafe { sys::OBJ_nid2sn(self.digest.nid()) };
463        if name_ptr.is_null() {
464            return Err(ErrorStack::drain());
465        }
466        let name = unsafe { CStr::from_ptr(name_ptr) };
467
468        let params = crate::params::ParamBuilder::new()?
469            .push_utf8_string(c"digest", name)?
470            .push_octet_slice(c"key", self.key)?
471            .push_octet_slice(c"xcghash", self.xcghash)?
472            .push_octet_slice(c"session-id", self.session_id)?
473            .push_utf8_string(c"type", self.key_type.as_cstr())?
474            .build()?;
475
476        let alg = KdfAlg::fetch(c"SSHKDF")?;
477        KdfCtx::new(&alg)?.derive(out, &params)
478    }
479
480    /// Derive `len` bytes, returning them in a freshly allocated `Vec<u8>`.
481    ///
482    /// # Errors
483    pub fn derive_to_vec(self, len: usize) -> Result<Vec<u8>, ErrorStack> {
484        let mut out = vec![0u8; len];
485        self.derive(&mut out)?;
486        Ok(out)
487    }
488}
489
490// ── KBKDF (OpenSSL 3.5+) ─────────────────────────────────────────────────────
491
492/// KBKDF derivation mode (SP 800-108).
493#[cfg(ossl350)]
494#[derive(Clone, Copy, Debug, PartialEq, Eq)]
495pub enum KbkdfMode {
496    /// Counter mode — a counter is appended to each PRF input block.
497    Counter,
498    /// Feedback mode — the previous PRF output is fed into the next block.
499    Feedback,
500}
501
502#[cfg(ossl350)]
503impl KbkdfMode {
504    fn as_cstr(self) -> &'static CStr {
505        match self {
506            KbkdfMode::Counter  => c"counter",
507            KbkdfMode::Feedback => c"feedback",
508        }
509    }
510}
511
512/// Counter field length (in bits) for KBKDF counter mode.
513#[cfg(ossl350)]
514#[derive(Clone, Copy, Debug, PartialEq, Eq)]
515pub enum KbkdfCounterLen {
516    /// 8-bit counter.
517    Bits8  = 8,
518    /// 16-bit counter.
519    Bits16 = 16,
520    /// 24-bit counter.
521    Bits24 = 24,
522    /// 32-bit counter (default).
523    Bits32 = 32,
524}
525
526#[cfg(ossl350)]
527impl Default for KbkdfCounterLen {
528    fn default() -> Self { KbkdfCounterLen::Bits32 }
529}
530
531/// KBKDF key-derivation builder (NIST SP 800-108).
532///
533/// Supports both HMAC-based and CMAC-based PRFs.
534///
535/// ```ignore
536/// let key = KbkdfBuilder::new(KbkdfMode::Counter, &hmac_alg, &master_key)
537///     .digest(&sha256)
538///     .label(b"my label")
539///     .context(b"my context")
540///     .derive_to_vec(32)?;
541/// ```
542#[cfg(ossl350)]
543pub struct KbkdfBuilder<'a> {
544    mode: KbkdfMode,
545    mac: &'a crate::mac::MacAlg,
546    digest: Option<&'a crate::digest::DigestAlg>,
547    key: &'a [u8],
548    label: Option<&'a [u8]>,
549    context: Option<&'a [u8]>,
550    /// Feedback mode only: IV / salt fed into the first PRF block.
551    salt: Option<&'a [u8]>,
552    counter_len: KbkdfCounterLen,
553    use_l: Option<bool>,
554    use_separator: Option<bool>,
555}
556
557#[cfg(ossl350)]
558impl<'a> KbkdfBuilder<'a> {
559    /// Create a KBKDF builder.
560    ///
561    /// - `mode` — counter or feedback.
562    /// - `mac` — MAC algorithm (HMAC or CMAC).
563    /// - `key` — the key derivation key (KDK).
564    #[must_use]
565    pub fn new(mode: KbkdfMode, mac: &'a crate::mac::MacAlg, key: &'a [u8]) -> Self {
566        KbkdfBuilder {
567            mode,
568            mac,
569            digest: None,
570            key,
571            label: None,
572            context: None,
573            salt: None,
574            counter_len: KbkdfCounterLen::default(),
575            use_l: None,
576            use_separator: None,
577        }
578    }
579
580    /// Set the hash digest for HMAC-based derivation.
581    #[must_use]
582    pub fn digest(mut self, digest: &'a crate::digest::DigestAlg) -> Self {
583        self.digest = Some(digest);
584        self
585    }
586
587    /// Set the label (identifies the purpose of the derived key).
588    #[must_use]
589    pub fn label(mut self, label: &'a [u8]) -> Self {
590        self.label = Some(label);
591        self
592    }
593
594    /// Set the context (caller-specific data bound into the derivation).
595    #[must_use]
596    pub fn context(mut self, context: &'a [u8]) -> Self {
597        self.context = Some(context);
598        self
599    }
600
601    /// Set the salt / feedback IV (feedback mode only).
602    #[must_use]
603    pub fn salt(mut self, salt: &'a [u8]) -> Self {
604        self.salt = Some(salt);
605        self
606    }
607
608    /// Override the counter field length (default: 32 bits).
609    #[must_use]
610    pub fn counter_len(mut self, len: KbkdfCounterLen) -> Self {
611        self.counter_len = len;
612        self
613    }
614
615    /// Control whether the length field `L` is included (default: true).
616    #[must_use]
617    pub fn use_l(mut self, enabled: bool) -> Self {
618        self.use_l = Some(enabled);
619        self
620    }
621
622    /// Control whether the zero-byte separator is included (default: true).
623    #[must_use]
624    pub fn use_separator(mut self, enabled: bool) -> Self {
625        self.use_separator = Some(enabled);
626        self
627    }
628
629    /// Derive key material, writing into `out`.
630    ///
631    /// # Errors
632    pub fn derive(self, out: &mut [u8]) -> Result<(), ErrorStack> {
633        let mut builder = crate::params::ParamBuilder::new()?
634            .push_utf8_string(c"mode", self.mode.as_cstr())?
635            .push_utf8_string(c"mac", self.mac.name())?
636            .push_octet_slice(c"key", self.key)?
637            .push_uint(c"r", self.counter_len as u32)?;
638
639        if let Some(d) = self.digest {
640            let name_ptr = unsafe { sys::OBJ_nid2sn(d.nid()) };
641            if name_ptr.is_null() {
642                return Err(ErrorStack::drain());
643            }
644            let name = unsafe { CStr::from_ptr(name_ptr) };
645            builder = builder.push_utf8_string(c"digest", name)?;
646        }
647        if let Some(l) = self.label {
648            builder = builder.push_octet_slice(c"label", l)?;
649        }
650        if let Some(c) = self.context {
651            builder = builder.push_octet_slice(c"data", c)?;
652        }
653        if let Some(s) = self.salt {
654            builder = builder.push_octet_slice(c"salt", s)?;
655        }
656        if let Some(v) = self.use_l {
657            builder = builder.push_int(c"use-l", i32::from(v))?;
658        }
659        if let Some(v) = self.use_separator {
660            builder = builder.push_int(c"use-separator", i32::from(v))?;
661        }
662
663        let params = builder.build()?;
664        let alg = KdfAlg::fetch(c"KBKDF")?;
665        KdfCtx::new(&alg)?.derive(out, &params)
666    }
667
668    /// Derive `len` bytes, returning them in a freshly allocated `Vec<u8>`.
669    ///
670    /// # Errors
671    pub fn derive_to_vec(self, len: usize) -> Result<Vec<u8>, ErrorStack> {
672        let mut out = vec![0u8; len];
673        self.derive(&mut out)?;
674        Ok(out)
675    }
676}
677
678// ── Tests ─────────────────────────────────────────────────────────────────────
679
680#[cfg(test)]
681mod tests {
682    use super::*;
683    use crate::digest::DigestAlg;
684
685    /// RFC 5869 Test Case 1 — `HKDF-SHA-256`, known-answer test.
686    ///
687    /// IKM  = 0x0b0b...0b (22 bytes)
688    /// salt = 0x000102...0c (13 bytes)
689    /// info = 0xf0f1...f9 (10 bytes)
690    /// L    = 42
691    #[test]
692    fn hkdf_sha256_rfc5869_tc1() {
693        let digest = DigestAlg::fetch(c"SHA2-256", None).unwrap();
694
695        let ikm  = [0x0b_u8; 22];
696        let salt = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
697                    0x08, 0x09, 0x0a, 0x0b, 0x0c_u8];
698        let info = [0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
699                    0xf8, 0xf9_u8];
700
701        let okm = HkdfBuilder::new(&digest)
702            .key(&ikm)
703            .salt(&salt)
704            .info(&info)
705            .derive_to_vec(42)
706            .unwrap();
707
708        assert_eq!(
709            hex::encode(&okm),
710            "3cb25f25faacd57a90434f64d0362f2a\
711             2d2d0a90cf1a5a4c5db02d56ecc4c5bf\
712             34007208d5b887185865"
713        );
714    }
715
716    /// HKDF without salt — RFC 5869 Test Case 3.
717    ///
718    /// IKM  = 0x0b0b...0b (22 bytes)
719    /// salt = (not provided)
720    /// info = (not provided)
721    /// L    = 42
722    #[test]
723    fn hkdf_sha256_no_salt_no_info() {
724        let digest = DigestAlg::fetch(c"SHA2-256", None).unwrap();
725        let ikm = [0x0b_u8; 22];
726
727        let okm = HkdfBuilder::new(&digest)
728            .key(&ikm)
729            .derive_to_vec(42)
730            .unwrap();
731
732        assert_eq!(
733            hex::encode(&okm),
734            "8da4e775a563c18f715f802a063c5a31\
735             b8a11f5c5ee1879ec3454e5f3c738d2d\
736             9d201395faa4b61a96c8"
737        );
738    }
739
740    /// `PBKDF2-SHA256` known-answer test.
741    ///
742    /// Password   = "password"
743    /// Salt       = "salt"
744    /// Iterations = 1
745    /// dkLen      = 32
746    #[test]
747    fn pbkdf2_sha256_known_answer() {
748        let digest = DigestAlg::fetch(c"SHA2-256", None).unwrap();
749
750        let dk = Pbkdf2Builder::new(&digest, b"password", b"salt")
751            .iterations(1)
752            .derive_to_vec(32)
753            .unwrap();
754
755        assert_eq!(
756            hex::encode(&dk),
757            "120fb6cffcf8b32c43e7225256c4f837\
758             a86548c92ccc35480805987cb70be17b"
759        );
760    }
761
762    /// Scrypt functional test — verify correct output length and non-zeroness.
763    ///
764    /// Uses tiny cost parameters (N=32, r=1, p=1) so the test is fast.
765    #[test]
766    fn scrypt_derives_nonzero_output() {
767        let dk = ScryptBuilder::new(b"password", b"salt")
768            .params(ScryptParams { n: 32, r: 1, p: 1 })
769            .derive_to_vec(32)
770            .unwrap();
771
772        assert_eq!(dk.len(), 32);
773        assert_ne!(dk, vec![0u8; 32]);
774    }
775
776    /// Two different passwords produce different keys.
777    #[test]
778    fn scrypt_different_passwords_differ() {
779        let p = ScryptParams { n: 32, r: 1, p: 1 };
780
781        let dk1 = ScryptBuilder::new(b"pass1", b"salt")
782            .params(ScryptParams { n: p.n, r: p.r, p: p.p })
783            .derive_to_vec(32)
784            .unwrap();
785        let dk2 = ScryptBuilder::new(b"pass2", b"salt")
786            .params(ScryptParams { n: p.n, r: p.r, p: p.p })
787            .derive_to_vec(32)
788            .unwrap();
789
790        assert_ne!(dk1, dk2);
791    }
792}