Skip to main content

rama_boring/
hash.rs

1use crate::{ffi, try_int};
2use openssl_macros::corresponds;
3use std::ffi::c_uint;
4use std::fmt;
5use std::io;
6use std::io::prelude::*;
7use std::ops::{Deref, DerefMut};
8use std::ptr;
9
10use crate::error::ErrorStack;
11use crate::ffi::{EVP_MD_CTX_free, EVP_MD_CTX_new};
12use crate::nid::Nid;
13use crate::{cvt, cvt_p};
14
15#[derive(Copy, Clone, PartialEq, Eq)]
16pub struct MessageDigest(*const ffi::EVP_MD);
17
18impl MessageDigest {
19    /// Creates a `MessageDigest` from a raw OpenSSL pointer.
20    ///
21    /// # Safety
22    ///
23    /// The caller must ensure the pointer is valid.
24    #[must_use]
25    pub unsafe fn from_ptr(x: *const ffi::EVP_MD) -> Self {
26        MessageDigest(x)
27    }
28
29    /// Returns the `MessageDigest` corresponding to an `Nid`.
30    #[corresponds(EVP_get_digestbynid)]
31    #[must_use]
32    pub fn from_nid(type_: Nid) -> Option<MessageDigest> {
33        unsafe {
34            let ptr = ffi::EVP_get_digestbynid(type_.as_raw());
35            if ptr.is_null() {
36                None
37            } else {
38                Some(MessageDigest(ptr))
39            }
40        }
41    }
42
43    #[must_use]
44    pub fn md5() -> MessageDigest {
45        unsafe { MessageDigest(ffi::EVP_md5()) }
46    }
47
48    #[must_use]
49    pub fn sha1() -> MessageDigest {
50        unsafe { MessageDigest(ffi::EVP_sha1()) }
51    }
52
53    #[must_use]
54    pub fn sha224() -> MessageDigest {
55        unsafe { MessageDigest(ffi::EVP_sha224()) }
56    }
57
58    #[must_use]
59    pub fn sha256() -> MessageDigest {
60        unsafe { MessageDigest(ffi::EVP_sha256()) }
61    }
62
63    #[must_use]
64    pub fn sha384() -> MessageDigest {
65        unsafe { MessageDigest(ffi::EVP_sha384()) }
66    }
67
68    #[must_use]
69    pub fn sha512() -> MessageDigest {
70        unsafe { MessageDigest(ffi::EVP_sha512()) }
71    }
72
73    #[must_use]
74    pub fn sha512_256() -> MessageDigest {
75        unsafe { MessageDigest(ffi::EVP_sha512_256()) }
76    }
77
78    #[allow(clippy::trivially_copy_pass_by_ref)]
79    #[must_use]
80    pub fn as_ptr(&self) -> *const ffi::EVP_MD {
81        self.0
82    }
83
84    /// The size of the digest in bytes.
85    #[allow(clippy::trivially_copy_pass_by_ref)]
86    #[must_use]
87    pub fn size(&self) -> usize {
88        unsafe { ffi::EVP_MD_size(self.0) }
89    }
90
91    /// The name of the digest.
92    #[allow(clippy::trivially_copy_pass_by_ref)]
93    #[must_use]
94    pub fn type_(&self) -> Nid {
95        Nid::from_raw(unsafe { ffi::EVP_MD_type(self.0) })
96    }
97}
98
99unsafe impl Sync for MessageDigest {}
100unsafe impl Send for MessageDigest {}
101
102#[derive(PartialEq, Copy, Clone)]
103enum State {
104    Reset,
105    Updated,
106    Finalized,
107}
108
109use self::State::*;
110
111/// Provides message digest (hash) computation.
112///
113/// # Examples
114///
115/// Calculate a hash in one go:
116///
117/// ```
118/// use rama_boring::hash::{hash, MessageDigest};
119///
120/// let data = b"\x42\xF4\x97\xE0";
121/// let spec = b"\x7c\x43\x0f\x17\x8a\xef\xdf\x14\x87\xfe\xe7\x14\x4e\x96\x41\xe2";
122/// let res = hash(MessageDigest::md5(), data).unwrap();
123/// assert_eq!(&*res, spec);
124/// ```
125///
126/// Supply the input in chunks:
127///
128/// ```
129/// use rama_boring::hash::{Hasher, MessageDigest};
130///
131/// let data = [b"\x42\xF4", b"\x97\xE0"];
132/// let spec = b"\x7c\x43\x0f\x17\x8a\xef\xdf\x14\x87\xfe\xe7\x14\x4e\x96\x41\xe2";
133/// let mut h = Hasher::new(MessageDigest::md5()).unwrap();
134/// h.update(data[0]).unwrap();
135/// h.update(data[1]).unwrap();
136/// let res = h.finish().unwrap();
137/// assert_eq!(&*res, spec);
138/// ```
139///
140/// # Warning
141///
142/// Don't actually use MD5 and SHA-1 hashes, they're not secure anymore.
143///
144/// Don't ever hash passwords, use the functions in the `pkcs5` module or bcrypt/scrypt instead.
145///
146/// For extendable output functions (XOFs, i.e. SHAKE128/SHAKE256), you must use finish_xof instead
147/// of finish and provide a buf to store the hash. The hash will be as long as the buf.
148pub struct Hasher {
149    ctx: *mut ffi::EVP_MD_CTX,
150    md: *const ffi::EVP_MD,
151    type_: MessageDigest,
152    state: State,
153}
154
155unsafe impl Sync for Hasher {}
156unsafe impl Send for Hasher {}
157
158impl Hasher {
159    /// Creates a new `Hasher` with the specified hash type.
160    pub fn new(ty: MessageDigest) -> Result<Hasher, ErrorStack> {
161        ffi::init();
162
163        let ctx = unsafe { cvt_p(EVP_MD_CTX_new())? };
164
165        let mut h = Hasher {
166            ctx,
167            md: ty.as_ptr(),
168            type_: ty,
169            state: Finalized,
170        };
171        h.init()?;
172        Ok(h)
173    }
174
175    fn init(&mut self) -> Result<(), ErrorStack> {
176        match self.state {
177            Reset => return Ok(()),
178            Updated => {
179                self.finish()?;
180            }
181            Finalized => (),
182        }
183        unsafe {
184            cvt(ffi::EVP_DigestInit_ex(self.ctx, self.md, ptr::null_mut()))?;
185        }
186        self.state = Reset;
187        Ok(())
188    }
189
190    /// Feeds data into the hasher.
191    pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack> {
192        if self.state == Finalized {
193            self.init()?;
194        }
195        unsafe {
196            cvt(ffi::EVP_DigestUpdate(
197                self.ctx,
198                data.as_ptr().cast_mut().cast(),
199                data.len(),
200            ))?;
201        }
202        self.state = Updated;
203        Ok(())
204    }
205
206    /// Returns the hash of the data written and resets the non-XOF hasher.
207    pub fn finish(&mut self) -> Result<DigestBytes, ErrorStack> {
208        if self.state == Finalized {
209            self.init()?;
210        }
211        unsafe {
212            let mut len = try_int(ffi::EVP_MAX_MD_SIZE)?;
213            let mut buf = [0; ffi::EVP_MAX_MD_SIZE as usize];
214            cvt(ffi::EVP_DigestFinal_ex(
215                self.ctx,
216                buf.as_mut_ptr(),
217                &mut len,
218            ))?;
219            self.state = Finalized;
220            Ok(DigestBytes {
221                buf,
222                len: try_int(len)?,
223            })
224        }
225    }
226
227    /// Writes the hash of the data into the supplied buf and resets the XOF hasher.
228    /// The hash will be as long as the buf.
229    pub fn finish_xof(&mut self, buf: &mut [u8]) -> Result<(), ErrorStack> {
230        if self.state == Finalized {
231            self.init()?;
232        }
233        unsafe {
234            cvt(ffi::EVP_DigestFinalXOF(
235                self.ctx,
236                buf.as_mut_ptr(),
237                buf.len(),
238            ))?;
239            self.state = Finalized;
240            Ok(())
241        }
242    }
243}
244
245impl Write for Hasher {
246    #[inline]
247    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
248        self.update(buf)?;
249        Ok(buf.len())
250    }
251
252    fn flush(&mut self) -> io::Result<()> {
253        Ok(())
254    }
255}
256
257impl Clone for Hasher {
258    fn clone(&self) -> Hasher {
259        let ctx = unsafe {
260            let ctx = EVP_MD_CTX_new();
261            assert!(!ctx.is_null());
262            let r = ffi::EVP_MD_CTX_copy_ex(ctx, self.ctx);
263            assert_eq!(r, 1);
264            ctx
265        };
266        Hasher {
267            ctx,
268            md: self.md,
269            type_: self.type_,
270            state: self.state,
271        }
272    }
273}
274
275impl Drop for Hasher {
276    fn drop(&mut self) {
277        unsafe {
278            if self.state != Finalized {
279                drop(self.finish());
280            }
281            EVP_MD_CTX_free(self.ctx);
282        }
283    }
284}
285
286/// The resulting bytes of a digest.
287///
288/// This type derefs to a byte slice - it exists to avoid allocating memory to
289/// store the digest data.
290#[derive(Copy)]
291pub struct DigestBytes {
292    pub(crate) buf: [u8; ffi::EVP_MAX_MD_SIZE as usize],
293    pub(crate) len: usize,
294}
295
296impl Clone for DigestBytes {
297    #[inline]
298    fn clone(&self) -> DigestBytes {
299        *self
300    }
301}
302
303impl Deref for DigestBytes {
304    type Target = [u8];
305
306    #[inline]
307    fn deref(&self) -> &[u8] {
308        &self.buf[..self.len]
309    }
310}
311
312impl DerefMut for DigestBytes {
313    #[inline]
314    fn deref_mut(&mut self) -> &mut [u8] {
315        &mut self.buf[..self.len]
316    }
317}
318
319impl AsRef<[u8]> for DigestBytes {
320    #[inline]
321    fn as_ref(&self) -> &[u8] {
322        self
323    }
324}
325
326impl fmt::Debug for DigestBytes {
327    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
328        fmt::Debug::fmt(&**self, fmt)
329    }
330}
331
332/// Computes the hash of the `data` with the non-XOF hasher `t`.
333pub fn hash(t: MessageDigest, data: &[u8]) -> Result<DigestBytes, ErrorStack> {
334    let mut h = Hasher::new(t)?;
335    h.update(data)?;
336    h.finish()
337}
338
339/// Computes the hash of the `data` with the XOF hasher `t` and stores it in `buf`.
340pub fn hash_xof(t: MessageDigest, data: &[u8], buf: &mut [u8]) -> Result<(), ErrorStack> {
341    let mut h = Hasher::new(t)?;
342    h.update(data)?;
343    h.finish_xof(buf)
344}
345
346/// Computes HMAC with SHA-256 digest.
347pub fn hmac_sha256(key: &[u8], data: &[u8]) -> Result<[u8; 32], ErrorStack> {
348    hmac(MessageDigest::sha256(), key, data)
349}
350
351/// Computes HMAC with SHA-512 digest.
352pub fn hmac_sha512(key: &[u8], data: &[u8]) -> Result<[u8; 64], ErrorStack> {
353    hmac(MessageDigest::sha512(), key, data)
354}
355
356/// Computes HMAC with SHA-1 digest.
357pub fn hmac_sha1(key: &[u8], data: &[u8]) -> Result<[u8; 20], ErrorStack> {
358    hmac(MessageDigest::sha1(), key, data)
359}
360
361pub(crate) fn hmac<const N: usize>(
362    digest: MessageDigest,
363    key: &[u8],
364    data: &[u8],
365) -> Result<[u8; N], ErrorStack> {
366    let mut out = [0u8; N];
367    let mut out_len: c_uint = 0;
368
369    cvt_p(unsafe {
370        ffi::HMAC(
371            digest.as_ptr(),
372            key.as_ptr().cast(),
373            key.len(),
374            data.as_ptr(),
375            data.len(),
376            out.as_mut_ptr(),
377            &mut out_len,
378        )
379    })?;
380
381    assert_eq!(out_len as usize, N);
382
383    Ok(out)
384}
385
386#[cfg(test)]
387mod tests {
388    use hex::{self, FromHex};
389    use std::io::prelude::*;
390
391    use super::*;
392
393    fn hash_test(hashtype: MessageDigest, hashtest: &(&str, &str)) {
394        let res = hash(hashtype, &Vec::from_hex(hashtest.0).unwrap()).unwrap();
395        assert_eq!(hex::encode(res), hashtest.1);
396    }
397
398    fn hash_recycle_test(h: &mut Hasher, hashtest: &(&str, &str)) {
399        h.write_all(&Vec::from_hex(hashtest.0).unwrap()).unwrap();
400        let res = h.finish().unwrap();
401        assert_eq!(hex::encode(res), hashtest.1);
402    }
403
404    // Test vectors from http://www.nsrl.nist.gov/testdata/
405    const MD5_TESTS: [(&str, &str); 13] = [
406        ("", "d41d8cd98f00b204e9800998ecf8427e"),
407        ("7F", "83acb6e67e50e31db6ed341dd2de1595"),
408        ("EC9C", "0b07f0d4ca797d8ac58874f887cb0b68"),
409        ("FEE57A", "e0d583171eb06d56198fc0ef22173907"),
410        ("42F497E0", "7c430f178aefdf1487fee7144e9641e2"),
411        ("C53B777F1C", "75ef141d64cb37ec423da2d9d440c925"),
412        ("89D5B576327B", "ebbaf15eb0ed784c6faa9dc32831bf33"),
413        ("5D4CCE781EB190", "ce175c4b08172019f05e6b5279889f2c"),
414        ("81901FE94932D7B9", "cd4d2f62b8cdb3a0cf968a735a239281"),
415        ("C9FFDEE7788EFB4EC9", "e0841a231ab698db30c6c0f3f246c014"),
416        ("66AC4B7EBA95E53DC10B", "a3b3cea71910d9af56742aa0bb2fe329"),
417        ("A510CD18F7A56852EB0319", "577e216843dd11573574d3fb209b97d8"),
418        (
419            "AAED18DBE8938C19ED734A8D",
420            "6f80fb775f27e0a4ce5c2f42fc72c5f1",
421        ),
422    ];
423
424    #[test]
425    fn test_hmac_sha256() {
426        let hmac = hmac_sha256(b"That's a secret".as_slice(), b"Hello world!".as_slice()).unwrap();
427
428        assert_eq!(
429            hmac,
430            [
431                0x50, 0xbb, 0x7d, 0xd2, 0xb8, 0xd2, 0x51, 0x5d, 0xb4, 0x2b, 0x70, 0xc3, 0x0b, 0xfd,
432                0xf5, 0x4c, 0x38, 0xa7, 0xae, 0x99, 0x07, 0xe5, 0x80, 0x0f, 0x8b, 0xe8, 0x34, 0x83,
433                0x55, 0x5f, 0xd0, 0xd4
434            ]
435        );
436    }
437
438    #[test]
439    fn test_hmac_sha512() {
440        let hmac = hmac_sha512(b"That's a secret".as_slice(), b"Hello world!".as_slice()).unwrap();
441
442        assert_eq!(
443            hmac,
444            [
445                0xc2, 0x7a, 0x7f, 0x7c, 0x17, 0x4c, 0x87, 0x70, 0x7f, 0x8c, 0xb7, 0x90, 0x01, 0xba,
446                0x23, 0x0e, 0xb7, 0xd6, 0x1a, 0xfd, 0x50, 0xea, 0x40, 0x43, 0x5f, 0x03, 0x25, 0x5a,
447                0x22, 0xb7, 0x8d, 0x0e, 0xba, 0x0d, 0x47, 0xb8, 0xef, 0xaa, 0xbf, 0xb1, 0xe7, 0xad,
448                0xc5, 0xd1, 0xe5, 0xba, 0x4d, 0xa5, 0xd1, 0xbb, 0x5e, 0xe3, 0xc7, 0x27, 0x0c, 0x57,
449                0x76, 0xd4, 0x2f, 0xb6, 0x5c, 0x21, 0xb7, 0x3a
450            ]
451        );
452    }
453
454    #[test]
455    fn test_hmac_sha1() {
456        let hmac = hmac_sha1(b"That's a secret".as_slice(), b"Hello world!".as_slice()).unwrap();
457
458        assert_eq!(
459            hmac,
460            [
461                0xe1, 0x06, 0x76, 0x46, 0x3b, 0x82, 0x67, 0xa1, 0xae, 0xe5, 0x1c, 0xfa, 0xee, 0x36,
462                0x1d, 0x4b, 0xd4, 0x41, 0x6e, 0x37
463            ]
464        );
465    }
466
467    #[test]
468    fn test_md5() {
469        for test in &MD5_TESTS {
470            hash_test(MessageDigest::md5(), test);
471        }
472    }
473
474    #[test]
475    fn test_md5_recycle() {
476        let mut h = Hasher::new(MessageDigest::md5()).unwrap();
477        for test in &MD5_TESTS {
478            hash_recycle_test(&mut h, test);
479        }
480    }
481
482    #[test]
483    fn test_finish_twice() {
484        let mut h = Hasher::new(MessageDigest::md5()).unwrap();
485        h.write_all(&Vec::from_hex(MD5_TESTS[6].0).unwrap())
486            .unwrap();
487        h.finish().unwrap();
488        let res = h.finish().unwrap();
489        let null = hash(MessageDigest::md5(), &[]).unwrap();
490        assert_eq!(&*res, &*null);
491    }
492
493    #[test]
494    #[allow(clippy::redundant_clone)]
495    fn test_clone() {
496        let i = 7;
497        let inp = Vec::from_hex(MD5_TESTS[i].0).unwrap();
498        assert!(inp.len() > 2);
499        let p = inp.len() / 2;
500        let h0 = Hasher::new(MessageDigest::md5()).unwrap();
501
502        println!("Clone a new hasher");
503        let mut h1 = h0.clone();
504        h1.write_all(&inp[..p]).unwrap();
505        {
506            println!("Clone an updated hasher");
507            let mut h2 = h1.clone();
508            h2.write_all(&inp[p..]).unwrap();
509            let res = h2.finish().unwrap();
510            assert_eq!(hex::encode(res), MD5_TESTS[i].1);
511        }
512        h1.write_all(&inp[p..]).unwrap();
513        let res = h1.finish().unwrap();
514        assert_eq!(hex::encode(res), MD5_TESTS[i].1);
515
516        println!("Clone a finished hasher");
517        let mut h3 = h1.clone();
518        h3.write_all(&Vec::from_hex(MD5_TESTS[i + 1].0).unwrap())
519            .unwrap();
520        let res = h3.finish().unwrap();
521        assert_eq!(hex::encode(res), MD5_TESTS[i + 1].1);
522    }
523
524    #[test]
525    fn test_sha1() {
526        let tests = [("616263", "a9993e364706816aba3e25717850c26c9cd0d89d")];
527
528        for test in &tests {
529            hash_test(MessageDigest::sha1(), test);
530        }
531    }
532
533    #[test]
534    fn test_sha224() {
535        let tests = [(
536            "616263",
537            "23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7",
538        )];
539
540        for test in &tests {
541            hash_test(MessageDigest::sha224(), test);
542        }
543    }
544
545    #[test]
546    fn test_sha256() {
547        let tests = [(
548            "616263",
549            "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
550        )];
551
552        for test in &tests {
553            hash_test(MessageDigest::sha256(), test);
554        }
555    }
556
557    #[test]
558    fn test_sha512() {
559        let tests = [(
560            "616263",
561            "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2\
562             192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f",
563        )];
564
565        for test in &tests {
566            hash_test(MessageDigest::sha512(), test);
567        }
568    }
569
570    #[test]
571    fn test_sha512_256() {
572        let tests = [(
573            "616263",
574            "53048e2681941ef99b2e29b76b4c7dabe4c2d0c634fc6d46e0e2f13107e7af23",
575        )];
576
577        for test in &tests {
578            hash_test(MessageDigest::sha512_256(), test);
579        }
580    }
581
582    #[test]
583    fn from_nid() {
584        assert_eq!(
585            MessageDigest::from_nid(Nid::SHA256).unwrap().as_ptr(),
586            MessageDigest::sha256().as_ptr()
587        );
588    }
589}