dryoc/classic/crypto_auth.rs
1//! # Secret-key authentication
2//!
3//! Implements secret-key authentication using HMAC-SHA512-256, compatible
4//! with libsodium's `crypto_auth_*` functions.
5//!
6//! # Classic API single-part example
7//!
8//! ```
9//! use dryoc::classic::crypto_auth::{Mac, crypto_auth, crypto_auth_keygen, crypto_auth_verify};
10//!
11//! let key = crypto_auth_keygen();
12//! let mut mac = Mac::default();
13//!
14//! crypto_auth(&mut mac, b"Data to authenticate", &key);
15//!
16//! // This should be valid
17//! crypto_auth_verify(&mac, b"Data to authenticate", &key).expect("failed to authenticate");
18//!
19//! // This should not be valid
20//! crypto_auth_verify(&mac, b"Invalid data", &key).expect_err("should not authenticate");
21//! ```
22//!
23//! # Classic API multi-part example
24//!
25//! ```
26//! use dryoc::classic::crypto_auth::{
27//! Mac, crypto_auth_final, crypto_auth_init, crypto_auth_keygen, crypto_auth_update,
28//! crypto_auth_verify,
29//! };
30//!
31//! let key = crypto_auth_keygen();
32//! let mut mac = Mac::default();
33//!
34//! let mut state = crypto_auth_init(&key);
35//! crypto_auth_update(&mut state, b"Multi-part");
36//! crypto_auth_update(&mut state, b"data");
37//! crypto_auth_final(state, &mut mac);
38//!
39//! // This should be valid
40//! crypto_auth_verify(&mac, b"Multi-partdata", &key).expect("failed to authenticate");
41//!
42//! // This should not be valid
43//! crypto_auth_verify(&mac, b"Invalid data", &key).expect_err("should not authenticate");
44//! ```
45use super::crypto_auth_hmacsha512256::{
46 HmacSha512256State, crypto_auth_hmacsha512256, crypto_auth_hmacsha512256_final,
47 crypto_auth_hmacsha512256_init, crypto_auth_hmacsha512256_keygen,
48 crypto_auth_hmacsha512256_update, crypto_auth_hmacsha512256_verify,
49};
50use crate::constants::{CRYPTO_AUTH_BYTES, CRYPTO_AUTH_KEYBYTES};
51use crate::error::Error;
52
53/// Key for secret-key message authentication.
54pub type Key = [u8; CRYPTO_AUTH_KEYBYTES];
55/// Message authentication code type for use with secret-key authentication.
56pub type Mac = [u8; CRYPTO_AUTH_BYTES];
57
58/// Authenticates `message` using `key`, and places the result into
59/// `mac`.
60///
61/// Equivalent to libsodium's `crypto_auth`.
62pub fn crypto_auth(mac: &mut Mac, message: &[u8], key: &Key) {
63 crypto_auth_hmacsha512256(mac, message, key)
64}
65
66/// Verifies that `mac` is the correct authenticator for `message` using `key`.
67/// Returns `Ok(())` if the message authentication code is valid.
68///
69/// Equivalent to libsodium's `crypto_auth_verify`.
70///
71/// # Errors
72///
73/// Returns an error if `mac` is not valid for `input` under `key`.
74pub fn crypto_auth_verify(mac: &Mac, input: &[u8], key: &Key) -> Result<(), Error> {
75 crypto_auth_hmacsha512256_verify(mac, input, key)
76}
77
78/// Internal state for [`crypto_auth`].
79pub struct AuthState {
80 state: HmacSha512256State,
81}
82
83/// Generates a random key using
84/// [`copy_randombytes`](crate::rng::copy_randombytes), suitable for use with
85/// [`crypto_auth_init`] and [`crypto_auth`].
86///
87/// Equivalent to libsodium's `crypto_auth_keygen`.
88#[must_use]
89pub fn crypto_auth_keygen() -> Key {
90 crypto_auth_hmacsha512256_keygen()
91}
92
93/// Initialize the incremental interface for HMAC-SHA512-256 secret-key.
94///
95/// Initializes the incremental interface for HMAC-SHA512-256 secret-key
96/// authentication, using `key`. Returns a state struct which is required for
97/// subsequent calls to [`crypto_auth_update`] and
98/// [`crypto_auth_final`].
99#[must_use]
100pub fn crypto_auth_init(key: &Key) -> AuthState {
101 AuthState {
102 state: crypto_auth_hmacsha512256_init(key),
103 }
104}
105
106/// Updates `state` for the secret-key authentication function, based on
107/// `input`.
108pub fn crypto_auth_update(state: &mut AuthState, input: &[u8]) {
109 crypto_auth_hmacsha512256_update(&mut state.state, input)
110}
111
112/// Finalizes the message authentication code for `state`, and places the result
113/// into `output`.
114pub fn crypto_auth_final(state: AuthState, output: &mut [u8; CRYPTO_AUTH_BYTES]) {
115 crypto_auth_hmacsha512256_final(state.state, output)
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 #[cfg(dryoc_native_tests)]
122 use crate::test_prelude::*;
123
124 const KEY: Key = {
125 let mut key = [0u8; CRYPTO_AUTH_KEYBYTES];
126 let mut i = 0;
127 while i < key.len() {
128 key[i] = i as u8;
129 i += 1;
130 }
131 key
132 };
133 const MESSAGE: &[u8] = b"classic crypto_auth boundary";
134 /// HMAC-SHA-512 of `MESSAGE` under `KEY`, truncated to 32 bytes
135 /// (computed independently with Python's `hmac`/`hashlib`).
136 const TAG: &str = "2f850f393b25f3568a2e8686d2a19401aa2343ab2ea1474f450962cef5de2b58";
137
138 /// `crypto_auth` is HMAC-SHA-512-256, one-shot and streamed, and the tag
139 /// verifies only for the exact message.
140 #[test]
141 fn test_crypto_auth_known_answer() {
142 let expected = hex::decode(TAG).expect("hex failed");
143 let mut mac = Mac::default();
144 crypto_auth(&mut mac, MESSAGE, &KEY);
145 assert_eq!(mac.as_slice(), expected.as_slice());
146
147 let mut state = crypto_auth_init(&KEY);
148 crypto_auth_update(&mut state, b"");
149 for chunk in MESSAGE.chunks(7) {
150 crypto_auth_update(&mut state, chunk);
151 }
152 let mut streamed = Mac::default();
153 crypto_auth_final(state, &mut streamed);
154 assert_eq!(streamed, mac);
155
156 crypto_auth_verify(&mac, MESSAGE, &KEY).expect("verify failed");
157 crypto_auth_verify(&mac, &MESSAGE[..MESSAGE.len() - 1], &KEY)
158 .expect_err("truncated message");
159 let mut flipped = mac;
160 flipped[0] ^= 1;
161 crypto_auth_verify(&flipped, MESSAGE, &KEY).expect_err("flipped tag");
162 }
163
164 #[cfg(dryoc_native_tests)]
165 #[test]
166 fn test_crypto_auth_matches_libsodium() {
167 use crate::native_test_util::auth_hmacsha512256;
168
169 for len in [0usize, 127, 128, 129] {
170 let message: Vec<u8> = (0..len as u32).map(|i| (i * 31 % 251) as u8).collect();
171 let so_tag = auth_hmacsha512256(&message, &KEY);
172 let mut mac = Mac::default();
173 crypto_auth(&mut mac, &message, &KEY);
174 assert_eq!(mac, so_tag, "len {len}");
175 }
176 }
177}