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 subtle::ConstantTimeEq;
46
47use crate::constants::{CRYPTO_AUTH_BYTES, CRYPTO_AUTH_HMACSHA512256_BYTES, CRYPTO_AUTH_KEYBYTES};
48use crate::error::Error;
49use crate::sha512::Sha512;
50use crate::types::*;
51
52struct HmacSha512State {
53    octx: Sha512,
54    ictx: Sha512,
55}
56
57/// Key for secret-key message authentication.
58pub type Key = [u8; CRYPTO_AUTH_KEYBYTES];
59/// Message authentication code type for use with secret-key authentication.
60pub type Mac = [u8; CRYPTO_AUTH_BYTES];
61
62fn crypto_auth_hmacsha512256(output: &mut Mac, message: &[u8], key: &Key) {
63    let mut state = crypto_auth_hmacsha512256_init(key);
64    crypto_auth_hmacsha512256_update(&mut state, message);
65    crypto_auth_hmacsha512256_final(state, output);
66}
67
68fn crypto_auth_hmacsha512256_verify(mac: &Mac, input: &[u8], key: &Key) -> Result<(), Error> {
69    let mut computed_mac = Mac::default();
70    crypto_auth_hmacsha512256(&mut computed_mac, input, key);
71    if mac.ct_eq(&computed_mac).unwrap_u8() == 1 {
72        Ok(())
73    } else {
74        Err(dryoc_error!("authentication codes do not match"))
75    }
76}
77
78fn crypto_auth_hmacsha512256_init(key: &[u8]) -> HmacSha512State {
79    let mut pad = [0x36u8; 128];
80    let mut khash = [0u8; 64];
81    let keylen = key.len();
82
83    let key = if keylen > 128 {
84        Sha512::compute_into_bytes(&mut khash, key);
85        &khash
86    } else {
87        key
88    };
89
90    let mut ictx = Sha512::new();
91    for i in 0..keylen {
92        pad[i] ^= key[i]
93    }
94    ictx.update(&pad);
95
96    let mut octx = Sha512::new();
97    pad.fill(0x5c);
98    for i in 0..keylen {
99        pad[i] ^= key[i]
100    }
101    octx.update(&pad);
102
103    HmacSha512State { octx, ictx }
104}
105
106fn crypto_auth_hmacsha512256_update(state: &mut HmacSha512State, input: &[u8]) {
107    state.ictx.update(input)
108}
109fn crypto_auth_hmacsha512256_final(
110    mut state: HmacSha512State,
111    output: &mut [u8; CRYPTO_AUTH_HMACSHA512256_BYTES],
112) {
113    let mut ihash = [0u8; 64];
114    state.ictx.finalize_into_bytes(&mut ihash);
115    state.octx.update(&ihash);
116    state.octx.finalize_into_bytes(&mut ihash);
117    output.copy_from_slice(&ihash[..CRYPTO_AUTH_HMACSHA512256_BYTES])
118}
119
120/// Authenticates `message` using `key`, and places the result into
121/// `mac`.
122///
123/// Equivalent to libsodium's `crypto_auth`.
124pub fn crypto_auth(mac: &mut Mac, message: &[u8], key: &Key) {
125    crypto_auth_hmacsha512256(mac, message, key)
126}
127
128/// Verifies that `mac` is the correct authenticator for `message` using `key`.
129/// Returns `Ok(())` if the message authentication code is valid.
130///
131/// Equivalent to libsodium's `crypto_auth_verify`.
132pub fn crypto_auth_verify(mac: &Mac, input: &[u8], key: &Key) -> Result<(), Error> {
133    crypto_auth_hmacsha512256_verify(mac, input, key)
134}
135
136/// Internal state for [`crypto_auth`].
137pub struct AuthState {
138    state: HmacSha512State,
139}
140
141/// Generates a random key using
142/// [`copy_randombytes`](crate::rng::copy_randombytes), suitable for use with
143/// [`crypto_auth_init`] and [`crypto_auth`].
144///
145/// Equivalent to libsodium's `crypto_auth_keygen`.
146pub fn crypto_auth_keygen() -> Key {
147    Key::gen()
148}
149
150/// Initialize the incremental interface for HMAC-SHA512-256 secret-key.
151///
152/// Initializes the incremental interface for HMAC-SHA512-256 secret-key
153/// authentication, using `key`. Returns a state struct which is required for
154/// subsequent calls to [`crypto_auth_update`] and
155/// [`crypto_auth_final`].
156pub fn crypto_auth_init(key: &Key) -> AuthState {
157    AuthState {
158        state: crypto_auth_hmacsha512256_init(key),
159    }
160}
161
162/// Updates `state` for the secret-key authentication function, based on
163/// `input`.
164pub fn crypto_auth_update(state: &mut AuthState, input: &[u8]) {
165    crypto_auth_hmacsha512256_update(&mut state.state, input)
166}
167
168/// Finalizes the message authentication code for `state`, and places the result
169/// into `output`.
170pub fn crypto_auth_final(state: AuthState, output: &mut [u8; CRYPTO_AUTH_BYTES]) {
171    crypto_auth_hmacsha512256_final(state.state, output)
172}
173
174#[cfg(test)]
175mod tests {
176    use rand::TryRngCore;
177
178    use super::*;
179
180    #[test]
181    fn test_crypto_auth() {
182        use rand_core::OsRng;
183        use sodiumoxide::crypto::auth;
184        use sodiumoxide::crypto::auth::Key as SOKey;
185
186        use crate::rng::copy_randombytes;
187
188        for _ in 0..20 {
189            let mlen = (OsRng.try_next_u32().unwrap() % 5000) as usize;
190            let mut message = vec![0u8; mlen];
191            copy_randombytes(&mut message);
192            let key = crypto_auth_keygen();
193
194            let so_tag =
195                auth::authenticate(&message, &SOKey::from_slice(&key).expect("key failed"));
196
197            let mut mac = Mac::new_byte_array();
198            crypto_auth(&mut mac, &message, &key);
199
200            assert_eq!(mac, so_tag.0);
201
202            crypto_auth_verify(&mac, &message, &key).expect("verify failed");
203            crypto_auth_verify(&mac, b"invalid message", &key)
204                .expect_err("verify should have failed");
205        }
206    }
207}