Skip to main content

libsodium_rs/crypto_auth/
mod.rs

1//! # Secret-Key Authentication
2//!
3//! This module provides functions for message authentication using secret keys.
4//! It allows you to compute an authentication tag for a message and a secret key,
5//! and provides a way to verify that a given tag is valid for a given message and key.
6//!
7//! ## Purpose
8//!
9//! The authentication function is deterministic: the same (message, key) tuple will always
10//! produce the same output. Even if the message is public, knowing the key is required to
11//! compute a valid tag. Therefore, the key should remain confidential. The tag, however,
12//! can be public.
13//!
14//! Typical use cases include:
15//! - A prepares a message, adds an authentication tag, and sends it to B
16//! - A doesn't store the message
17//! - Later on, B sends the message and the authentication tag back to A
18//! - A uses the authentication tag to verify that it created this message
19//!
20//! This operation does not encrypt the message. It only computes and verifies an
21//! authentication tag.
22//!
23//! ## Algorithm
24//!
25//! The default implementation uses HMAC-SHA512-256.
26//!
27//! ## Usage Example
28//!
29//! ```
30//! use libsodium_rs as sodium;
31//! use sodium::crypto_auth;
32//! use sodium::ensure_init;
33//!
34//! // Initialize libsodium
35//! ensure_init().expect("Failed to initialize libsodium");
36//!
37//! // Generate a random key
38//! let key = crypto_auth::Key::generate().unwrap();
39//!
40//! // Message to authenticate
41//! let message = b"Hello, world!";
42//!
43//! // Compute authentication tag
44//! let tag = crypto_auth::auth(message, &key).unwrap();
45//!
46//! // Verify the tag
47//! let verification = crypto_auth::verify(&tag, message, &key);
48//! assert!(verification);
49//!
50//! // Verification fails with a different message
51//! let wrong_message = b"Modified message";
52//! let verification = crypto_auth::verify(&tag, wrong_message, &key);
53//! assert!(!verification);
54//! ```
55
56use crate::{Result, SodiumError};
57use std::convert::TryFrom;
58
59/// Number of bytes in an authentication tag (32 bytes)
60pub const BYTES: usize = libsodium_sys::crypto_auth_BYTES as usize;
61/// Number of bytes in an authentication key (32 bytes)
62pub const KEYBYTES: usize = libsodium_sys::crypto_auth_KEYBYTES as usize;
63
64/// A secret key for authentication
65///
66/// This key is used to compute and verify authentication tags. It should be kept secret,
67/// as anyone with the key can create valid authentication tags.
68#[derive(Debug, zeroize::Zeroize, zeroize::ZeroizeOnDrop)]
69pub struct Key([u8; KEYBYTES]);
70
71impl Key {
72    /// Generates a new random key for authentication
73    ///
74    /// This function generates a cryptographically secure random key that can be used
75    /// for message authentication. The key is `KEYBYTES` (32) bytes long.
76    ///
77    /// # Returns
78    /// * `Result<Self>` - A new randomly generated key
79    ///
80    /// # Example
81    /// ```
82    /// use libsodium_rs as sodium;
83    /// use sodium::crypto_auth;
84    /// use sodium::ensure_init;
85    ///
86    /// // Initialize libsodium
87    /// ensure_init().expect("Failed to initialize libsodium");
88    ///
89    /// // Generate a random key
90    /// let key = crypto_auth::Key::generate().unwrap();
91    /// ```
92    pub fn generate() -> Result<Self> {
93        let mut key = [0u8; KEYBYTES];
94        unsafe {
95            libsodium_sys::crypto_auth_keygen(key.as_mut_ptr());
96        }
97        Ok(Key(key))
98    }
99
100    /// Creates a key from a byte slice
101    ///
102    /// This function creates a key from an existing byte slice, which must be exactly
103    /// `KEYBYTES` (32) bytes long. This is useful when you have an existing key or
104    /// when you need to derive a key from another source.
105    ///
106    /// # Arguments
107    /// * `slice` - Byte slice of exactly `KEYBYTES` (32) bytes length
108    ///
109    /// # Returns
110    /// * `Result<Self>` - A new key or an error if the input is invalid
111    ///
112    /// # Errors
113    /// Returns an error if the input is not exactly `KEYBYTES` bytes long
114    ///
115    /// # Example
116    /// ```
117    /// use libsodium_rs as sodium;
118    /// use sodium::crypto_auth;
119    /// use sodium::ensure_init;
120    ///
121    /// // Initialize libsodium
122    /// ensure_init().expect("Failed to initialize libsodium");
123    ///
124    /// // Create a key from existing bytes
125    /// let key_bytes = [0x42; crypto_auth::KEYBYTES]; // In a real application, use a proper key
126    /// let key = crypto_auth::Key::from_slice(&key_bytes).unwrap();
127    /// ```
128    pub fn from_slice(slice: &[u8]) -> Result<Self> {
129        if slice.len() != KEYBYTES {
130            return Err(SodiumError::InvalidInput(format!(
131                "key must be exactly {KEYBYTES} bytes"
132            )));
133        }
134        let mut key = [0u8; KEYBYTES];
135        key.copy_from_slice(slice);
136        Ok(Key(key))
137    }
138
139    /// Returns a reference to the key as a byte slice
140    ///
141    /// This method provides access to the raw bytes of the key, which can be useful
142    /// when you need to pass the key to other functions or store it securely.
143    ///
144    /// # Returns
145    /// * `&[u8]` - Reference to the key bytes
146    ///
147    /// # Example
148    /// ```
149    /// use libsodium_rs as sodium;
150    /// use sodium::crypto_auth;
151    /// use sodium::ensure_init;
152    ///
153    /// // Initialize libsodium
154    /// ensure_init().expect("Failed to initialize libsodium");
155    ///
156    /// // Generate a random key
157    /// let key = crypto_auth::Key::generate().unwrap();
158    ///
159    /// // Get the raw bytes of the key
160    /// let key_bytes = key.as_bytes();
161    /// assert_eq!(key_bytes.len(), crypto_auth::KEYBYTES);
162    /// ```
163    pub fn as_bytes(&self) -> &[u8] {
164        &self.0
165    }
166}
167
168impl AsRef<[u8]> for Key {
169    fn as_ref(&self) -> &[u8] {
170        &self.0
171    }
172}
173
174impl TryFrom<&[u8]> for Key {
175    type Error = SodiumError;
176
177    fn try_from(slice: &[u8]) -> std::result::Result<Self, Self::Error> {
178        Self::from_slice(slice)
179    }
180}
181
182impl From<[u8; KEYBYTES]> for Key {
183    fn from(bytes: [u8; KEYBYTES]) -> Self {
184        Self(bytes)
185    }
186}
187
188impl From<Key> for [u8; KEYBYTES] {
189    fn from(key: Key) -> [u8; KEYBYTES] {
190        key.0
191    }
192}
193
194/// An authentication tag
195///
196/// This represents an authentication tag computed for a message using a secret key.
197/// The tag can be publicly shared and later verified to ensure the authenticity of
198/// a message.
199#[derive(Debug, Clone, Eq, PartialEq)]
200pub struct Tag([u8; BYTES]);
201
202impl Tag {
203    /// Creates a tag from a byte slice
204    ///
205    /// This function creates a tag from an existing byte slice, which must be exactly
206    /// `BYTES` (32) bytes long. This is useful when you receive a tag from another party
207    /// and need to verify it.
208    ///
209    /// # Arguments
210    /// * `slice` - Byte slice of exactly `BYTES` (32) bytes length
211    ///
212    /// # Returns
213    /// * `Result<Self>` - A new tag or an error if the input is invalid
214    ///
215    /// # Errors
216    /// Returns an error if the input is not exactly `BYTES` bytes long
217    ///
218    /// # Example
219    /// ```
220    /// use libsodium_rs as sodium;
221    /// use sodium::crypto_auth;
222    /// use sodium::ensure_init;
223    ///
224    /// // Initialize libsodium
225    /// ensure_init().expect("Failed to initialize libsodium");
226    ///
227    /// // Create a tag from existing bytes
228    /// let tag_bytes = [0x42; crypto_auth::BYTES]; // In a real application, this would be a real tag
229    /// let tag = crypto_auth::Tag::from_slice(&tag_bytes).unwrap();
230    /// ```
231    pub fn from_slice(slice: &[u8]) -> Result<Self> {
232        if slice.len() != BYTES {
233            return Err(SodiumError::InvalidInput(format!(
234                "tag must be exactly {BYTES} bytes"
235            )));
236        }
237        let mut tag = [0u8; BYTES];
238        tag.copy_from_slice(slice);
239        Ok(Tag(tag))
240    }
241
242    /// Returns a reference to the tag as a byte slice
243    ///
244    /// This method provides access to the raw bytes of the tag, which can be useful
245    /// when you need to transmit the tag or store it.
246    ///
247    /// # Returns
248    /// * `&[u8]` - Reference to the tag bytes
249    ///
250    /// # Example
251    /// ```
252    /// use libsodium_rs as sodium;
253    /// use sodium::crypto_auth;
254    /// use sodium::ensure_init;
255    ///
256    /// // Initialize libsodium
257    /// ensure_init().expect("Failed to initialize libsodium");
258    ///
259    /// // Generate a random key
260    /// let key = crypto_auth::Key::generate().unwrap();
261    ///
262    /// // Compute authentication tag for a message
263    /// let message = b"Hello, world!";
264    /// let tag = crypto_auth::auth(message, &key).unwrap();
265    ///
266    /// // Get the raw bytes of the tag
267    /// let tag_bytes = tag.as_bytes();
268    /// assert_eq!(tag_bytes.len(), crypto_auth::BYTES);
269    /// ```
270    pub fn as_bytes(&self) -> &[u8] {
271        &self.0
272    }
273}
274
275impl AsRef<[u8]> for Tag {
276    fn as_ref(&self) -> &[u8] {
277        &self.0
278    }
279}
280
281impl TryFrom<&[u8]> for Tag {
282    type Error = SodiumError;
283
284    fn try_from(slice: &[u8]) -> std::result::Result<Self, Self::Error> {
285        Self::from_slice(slice)
286    }
287}
288
289impl From<[u8; BYTES]> for Tag {
290    fn from(bytes: [u8; BYTES]) -> Self {
291        Self(bytes)
292    }
293}
294
295impl From<Tag> for [u8; BYTES] {
296    fn from(tag: Tag) -> [u8; BYTES] {
297        tag.0
298    }
299}
300
301/// Computes an authentication tag for a message using a secret key
302///
303/// This function computes an authentication tag for a message and a secret key. The tag
304/// is deterministic: the same (message, key) tuple will always produce the same output.
305/// The tag can be later verified using the `verify` function.
306///
307/// The default implementation uses HMAC-SHA512-256.
308///
309/// # Arguments
310/// * `message` - Message to authenticate
311/// * `key` - Secret key for authentication
312///
313/// # Returns
314/// * `Result<Tag>` - Authentication tag or an error
315///
316/// # Example
317/// ```
318/// use libsodium_rs as sodium;
319/// use sodium::crypto_auth;
320/// use sodium::ensure_init;
321///
322/// // Initialize libsodium
323/// ensure_init().expect("Failed to initialize libsodium");
324///
325/// // Generate a random key
326/// let key = crypto_auth::Key::generate().unwrap();
327///
328/// // Message to authenticate
329/// let message = b"Hello, world!";
330///
331/// // Compute authentication tag
332/// let tag = crypto_auth::auth(message, &key).unwrap();
333/// ```
334pub fn auth(message: &[u8], key: &Key) -> Result<Tag> {
335    let mut tag = [0u8; BYTES];
336    let result = unsafe {
337        libsodium_sys::crypto_auth(
338            tag.as_mut_ptr(),
339            message.as_ptr(),
340            message.len() as u64,
341            key.as_bytes().as_ptr(),
342        )
343    };
344
345    if result != 0 {
346        return Err(SodiumError::OperationError("authentication failed".into()));
347    }
348
349    Ok(Tag(tag))
350}
351
352/// Verifies that a tag is valid for a message and key
353///
354/// This function verifies that a given authentication tag is valid for a given message
355/// and key. It returns `true` if the verification passes, or `false` if the
356/// verification fails (indicating that the message may have been tampered with or that
357/// the wrong key was used).
358///
359/// # Arguments
360/// * `tag` - Authentication tag to verify
361/// * `message` - Message that was authenticated
362/// * `key` - Secret key used for authentication
363///
364/// # Returns
365/// * `bool` - `true` if verification passes, `false` otherwise
366///
367/// # Example
368/// ```
369/// use libsodium_rs as sodium;
370/// use sodium::crypto_auth;
371/// use sodium::ensure_init;
372///
373/// // Initialize libsodium
374/// ensure_init().expect("Failed to initialize libsodium");
375///
376/// // Generate a random key
377/// let key = crypto_auth::Key::generate().unwrap();
378///
379/// // Message to authenticate
380/// let message = b"Hello, world!";
381///
382/// // Compute authentication tag
383/// let tag = crypto_auth::auth(message, &key).unwrap();
384///
385/// // Verify the tag (should succeed)
386/// assert!(crypto_auth::verify(&tag, message, &key));
387///
388/// // Verify with wrong message (should fail)
389/// let wrong_message = b"Modified message";
390/// assert!(!crypto_auth::verify(&tag, wrong_message, &key));
391///
392/// // Verify with wrong key (should fail)
393/// let wrong_key = crypto_auth::Key::generate().unwrap();
394/// assert!(!crypto_auth::verify(&tag, message, &wrong_key));
395/// ```
396pub fn verify(tag: &Tag, message: &[u8], key: &Key) -> bool {
397    let result = unsafe {
398        libsodium_sys::crypto_auth_verify(
399            tag.as_bytes().as_ptr(),
400            message.as_ptr(),
401            message.len() as u64,
402            key.as_bytes().as_ptr(),
403        )
404    };
405
406    result == 0
407}
408
409// Export submodules
410/// HMAC-SHA-256 authentication functions
411pub mod hmacsha256;
412/// HMAC-SHA-512 authentication functions
413pub mod hmacsha512;
414/// HMAC-SHA-512-256 authentication functions (truncated SHA-512)
415pub mod hmacsha512256;
416
417#[cfg(test)]
418mod tests {
419    use super::*;
420
421    #[test]
422    fn test_key_generation() {
423        let key = Key::generate().unwrap();
424        assert_eq!(key.as_bytes().len(), KEYBYTES);
425    }
426
427    #[test]
428    fn test_key_from_slice() {
429        let bytes = vec![0u8; KEYBYTES];
430        let key = Key::from_slice(&bytes).unwrap();
431        assert_eq!(key.as_bytes(), bytes.as_slice());
432
433        // Test invalid key size
434        assert!(Key::from_slice(&[0u8; KEYBYTES + 1]).is_err());
435    }
436
437    #[test]
438    fn test_tag_from_slice() {
439        let bytes = vec![0u8; BYTES];
440        let tag = Tag::from_slice(&bytes).unwrap();
441        assert_eq!(tag.as_bytes(), bytes.as_slice());
442
443        // Test invalid tag size
444        assert!(Tag::from_slice(&[0u8; BYTES + 1]).is_err());
445    }
446
447    #[test]
448    fn test_auth_and_verify() {
449        let key = Key::generate().unwrap();
450        let message = b"test message";
451
452        // Create authentication tag
453        let tag = auth(message, &key).unwrap();
454
455        // Verify the tag
456        assert!(verify(&tag, message, &key));
457
458        // Verify with wrong message
459        assert!(!verify(&tag, b"wrong message", &key));
460
461        // Verify with wrong key
462        let wrong_key = Key::generate().unwrap();
463        assert!(!verify(&tag, message, &wrong_key));
464    }
465
466    #[test]
467    fn test_key_as_ref() {
468        let key = Key::generate().unwrap();
469        let key_ref: &[u8] = key.as_ref();
470        assert_eq!(key_ref.len(), KEYBYTES);
471        assert_eq!(key_ref, key.as_bytes());
472    }
473
474    #[test]
475    fn test_key_try_from_slice() {
476        // Valid slice
477        let bytes = vec![0x42; KEYBYTES];
478        let key = Key::try_from(bytes.as_slice()).unwrap();
479        assert_eq!(key.as_bytes(), bytes.as_slice());
480
481        // Invalid slice - too short
482        let short_bytes = vec![0x42; KEYBYTES - 1];
483        assert!(Key::try_from(short_bytes.as_slice()).is_err());
484
485        // Invalid slice - too long
486        let long_bytes = vec![0x42; KEYBYTES + 1];
487        assert!(Key::try_from(long_bytes.as_slice()).is_err());
488    }
489
490    #[test]
491    fn test_key_from_bytes() {
492        let bytes = [0x42; KEYBYTES];
493        let key = Key::from(bytes);
494        assert_eq!(key.as_bytes(), &bytes);
495    }
496
497    #[test]
498    fn test_key_into_bytes() {
499        let original_bytes = [0x42; KEYBYTES];
500        let key = Key::from(original_bytes);
501        let bytes: [u8; KEYBYTES] = key.into();
502        assert_eq!(bytes, original_bytes);
503    }
504
505    #[test]
506    fn test_tag_as_ref() {
507        let key = Key::generate().unwrap();
508        let message = b"test message";
509        let tag = auth(message, &key).unwrap();
510        let tag_ref: &[u8] = tag.as_ref();
511        assert_eq!(tag_ref.len(), BYTES);
512        assert_eq!(tag_ref, tag.as_bytes());
513    }
514
515    #[test]
516    fn test_tag_try_from_slice() {
517        // Valid slice
518        let bytes = vec![0x42; BYTES];
519        let tag = Tag::try_from(bytes.as_slice()).unwrap();
520        assert_eq!(tag.as_bytes(), bytes.as_slice());
521
522        // Invalid slice - too short
523        let short_bytes = vec![0x42; BYTES - 1];
524        assert!(Tag::try_from(short_bytes.as_slice()).is_err());
525
526        // Invalid slice - too long
527        let long_bytes = vec![0x42; BYTES + 1];
528        assert!(Tag::try_from(long_bytes.as_slice()).is_err());
529    }
530
531    #[test]
532    fn test_tag_from_bytes() {
533        let bytes = [0x42; BYTES];
534        let tag = Tag::from(bytes);
535        assert_eq!(tag.as_bytes(), &bytes);
536    }
537
538    #[test]
539    fn test_tag_into_bytes() {
540        let original_bytes = [0x42; BYTES];
541        let tag = Tag::from(original_bytes);
542        let bytes: [u8; BYTES] = tag.into();
543        assert_eq!(bytes, original_bytes);
544    }
545
546    #[test]
547    fn test_key_tag_roundtrip() {
548        // Test Key roundtrip
549        let key_bytes = [0x42; KEYBYTES];
550        let key = Key::from(key_bytes);
551        let key_bytes_out: [u8; KEYBYTES] = key.into();
552        assert_eq!(key_bytes, key_bytes_out);
553        let key_from_bytes = Key::from(key_bytes_out);
554        assert_eq!(key_from_bytes.as_bytes(), &key_bytes);
555
556        // Test Tag roundtrip with real tag
557        let key = Key::generate().unwrap();
558        let message = b"test message";
559        let tag = auth(message, &key).unwrap();
560        let tag_bytes: [u8; BYTES] = tag.clone().into();
561        let tag_from_bytes = Tag::from(tag_bytes);
562        assert_eq!(tag.as_bytes(), tag_from_bytes.as_bytes());
563
564        // Verify the reconstructed tag still works
565        assert!(verify(&tag_from_bytes, message, &key));
566    }
567}