scryptenc/
format.rs

1// SPDX-FileCopyrightText: 2022 Shun Sakai
2//
3// SPDX-License-Identifier: Apache-2.0 OR MIT
4
5//! Specifications of the scrypt encrypted data format.
6
7use core::mem;
8
9use ctr::cipher::{self, KeySizeUser};
10use hmac::{
11    Mac,
12    digest::{
13        OutputSizeUser,
14        typenum::{U32, Unsigned},
15    },
16};
17use rand::{Rng, SeedableRng, rngs::StdRng};
18use sha2::{Digest, Sha256};
19
20use crate::{Aes256Ctr128BE, Error, HmacSha256, HmacSha256Key, HmacSha256Output, Params, Result};
21
22/// A type alias for magic number of the scrypt encrypted data format.
23type MagicNumber = [u8; 6];
24
25/// A type alias for salt of scrypt.
26type Salt = [u8; 32];
27
28/// A type alias for checksum of the scrypt encrypted data format.
29type Checksum = [u8; 16];
30
31/// A type alias for the header MAC.
32type HeaderMac = HmacSha256;
33
34/// A type alias for output of the header MAC.
35type HeaderMacOutput = HmacSha256Output;
36
37/// A type alias for key of the header MAC.
38type HeaderMacKey = HmacSha256Key;
39
40/// A type alias for key of AES-256-CTR.
41type Aes256Ctr128BEKey = cipher::Key<Aes256Ctr128BE>;
42
43/// The number of bytes of the header.
44///
45/// # Examples
46///
47/// ```
48/// assert_eq!(scryptenc::HEADER_SIZE, 96);
49///
50/// let ciphertext = include_bytes!("../tests/data/data.txt.scrypt");
51/// let plaintext = include_bytes!("../tests/data/data.txt");
52/// assert_eq!(
53///     scryptenc::HEADER_SIZE,
54///     ciphertext.len() - (plaintext.len() + scryptenc::TAG_SIZE)
55/// );
56/// ```
57pub const HEADER_SIZE: usize = Header::SIZE;
58
59/// The number of bytes of the MAC (authentication tag) of the scrypt encrypted
60/// data format.
61///
62/// # Examples
63///
64/// ```
65/// assert_eq!(scryptenc::TAG_SIZE, 32);
66///
67/// let ciphertext = include_bytes!("../tests/data/data.txt.scrypt");
68/// let plaintext = include_bytes!("../tests/data/data.txt");
69/// assert_eq!(
70///     scryptenc::TAG_SIZE,
71///     ciphertext.len() - (scryptenc::HEADER_SIZE + plaintext.len())
72/// );
73/// ```
74pub const TAG_SIZE: usize = <HmacSha256 as OutputSizeUser>::OutputSize::USIZE;
75
76/// Version of the scrypt encrypted data format.
77#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
78enum Version {
79    /// Version 0.
80    #[default]
81    V0,
82
83    #[allow(dead_code)]
84    /// Version 1.
85    #[doc(hidden)]
86    V1,
87}
88
89impl From<Version> for u8 {
90    #[inline]
91    fn from(version: Version) -> Self {
92        version as Self
93    }
94}
95
96/// Header of the scrypt encrypted data format.
97#[derive(Clone, Debug)]
98pub struct Header {
99    magic_number: MagicNumber,
100    version: Version,
101    params: Params,
102    salt: Salt,
103    checksum: Checksum,
104    mac: HeaderMacOutput,
105}
106
107impl Header {
108    /// Magic number of the scrypt encrypted data format.
109    ///
110    /// This is the ASCII code for "scrypt".
111    const MAGIC_NUMBER: MagicNumber = *b"scrypt";
112
113    /// The number of bytes of the header.
114    const SIZE: usize = mem::size_of::<MagicNumber>()
115        + mem::size_of::<Version>()
116        + (mem::size_of::<Params>() - (mem::align_of::<Params>() - mem::size_of::<u8>()))
117        + mem::size_of::<Salt>()
118        + mem::size_of::<Checksum>()
119        + <HeaderMac as OutputSizeUser>::OutputSize::USIZE;
120
121    /// Creates a new `Header`.
122    pub fn new(params: scrypt::Params) -> Self {
123        let magic_number = Self::MAGIC_NUMBER;
124        let version = Version::default();
125        let params = params.into();
126        let salt = StdRng::from_entropy().r#gen();
127        let checksum = Checksum::default();
128        let mac = HeaderMacOutput::default();
129        Self {
130            magic_number,
131            version,
132            params,
133            salt,
134            checksum,
135            mac,
136        }
137    }
138
139    /// Parses `data` into the header.
140    pub fn parse(data: &[u8]) -> Result<Self> {
141        if data.len() < Self::SIZE + TAG_SIZE {
142            return Err(Error::InvalidLength);
143        }
144
145        let Some(magic_number) = Some(Self::MAGIC_NUMBER).filter(|mn| &data[..6] == mn) else {
146            return Err(Error::InvalidMagicNumber);
147        };
148        let version = match data[6] {
149            0 => Version::V0,
150            v => return Err(Error::UnknownVersion(v)),
151        };
152        let log_n = data[7];
153        let r = u32::from_be_bytes(
154            data[8..12]
155                .try_into()
156                .expect("size of `r` parameter should be 4 bytes"),
157        );
158        let p = u32::from_be_bytes(
159            data[12..16]
160                .try_into()
161                .expect("size of `p` parameter should be 4 bytes"),
162        );
163        let params =
164            scrypt::Params::new(log_n, r, p, scrypt::Params::RECOMMENDED_LEN).map(Params::from)?;
165        let salt = data[16..48]
166            .try_into()
167            .expect("size of salt should be 32 bytes");
168        let checksum = Checksum::default();
169        let mac = HeaderMacOutput::default();
170        Ok(Self {
171            magic_number,
172            version,
173            params,
174            salt,
175            checksum,
176            mac,
177        })
178    }
179
180    /// Gets a SHA-256 checksum of this header.
181    #[inline]
182    pub fn compute_checksum(&mut self) {
183        let result = Sha256::digest(&self.as_bytes()[..48]);
184        self.checksum.copy_from_slice(&result[..16]);
185    }
186
187    /// Verifies a SHA-256 checksum stored in this header.
188    #[inline]
189    pub fn verify_checksum(&mut self, checksum: &[u8]) -> Result<()> {
190        self.compute_checksum();
191        if self.checksum == checksum {
192            Ok(())
193        } else {
194            Err(Error::InvalidChecksum)
195        }
196    }
197
198    /// Gets a HMAC-SHA-256 of this header.
199    #[inline]
200    pub fn compute_mac(&mut self, key: &HeaderMacKey) {
201        let mut mac =
202            HmacSha256::new_from_slice(key).expect("HMAC-SHA-256 key size should be 256 bits");
203        mac.update(&self.as_bytes()[..64]);
204        self.mac.copy_from_slice(&mac.finalize().into_bytes());
205    }
206
207    /// Verifies a HMAC-SHA-256 stored in this header.
208    pub fn verify_mac(&mut self, key: &HeaderMacKey, tag: &HeaderMacOutput) -> Result<()> {
209        let mut mac =
210            HmacSha256::new_from_slice(key).expect("HMAC-SHA-256 key size should be 256 bits");
211        mac.update(&self.as_bytes()[..64]);
212        mac.verify(tag).map_err(Error::InvalidHeaderMac)?;
213        self.mac.copy_from_slice(tag);
214        Ok(())
215    }
216
217    /// Converts this header to a byte array.
218    pub fn as_bytes(&self) -> [u8; Self::SIZE] {
219        let mut header = [u8::default(); Self::SIZE];
220        header[..6].copy_from_slice(&self.magic_number);
221        header[6] = self.version.into();
222        header[7] = self.params.log_n();
223        header[8..12].copy_from_slice(&self.params.r().to_be_bytes());
224        header[12..16].copy_from_slice(&self.params.p().to_be_bytes());
225        header[16..48].copy_from_slice(&self.salt);
226        header[48..64].copy_from_slice(&self.checksum);
227        header[64..].copy_from_slice(&self.mac);
228        header
229    }
230
231    /// Returns the scrypt parameters stored in this header.
232    #[inline]
233    pub const fn params(&self) -> Params {
234        self.params
235    }
236
237    /// Returns a salt stored in this header.
238    #[inline]
239    pub const fn salt(&self) -> Salt {
240        self.salt
241    }
242}
243
244/// Derived key.
245#[derive(Clone, Debug)]
246pub struct DerivedKey {
247    encrypt: Aes256Ctr128BEKey,
248    mac: HmacSha256Key,
249}
250
251impl DerivedKey {
252    /// The number of bytes of the derived key.
253    pub const SIZE: usize = <Aes256Ctr128BE as KeySizeUser>::KeySize::USIZE + U32::USIZE;
254
255    /// Creates a new `DerivedKey`.
256    #[inline]
257    pub fn new(dk: [u8; Self::SIZE]) -> Self {
258        let encrypt = *Aes256Ctr128BEKey::from_slice(&dk[..32]);
259        let mac = *HmacSha256Key::from_slice(&dk[32..]);
260        Self { encrypt, mac }
261    }
262
263    /// Returns the key for encrypted.
264    #[inline]
265    pub const fn encrypt(&self) -> Aes256Ctr128BEKey {
266        self.encrypt
267    }
268
269    /// Returns the key for a MAC.
270    #[inline]
271    pub const fn mac(&self) -> HmacSha256Key {
272        self.mac
273    }
274}
275
276#[cfg(test)]
277mod tests {
278    use core::str;
279
280    use super::*;
281
282    #[test]
283    fn header_size() {
284        assert_eq!(HEADER_SIZE, 96);
285        assert_eq!(HEADER_SIZE, Header::SIZE);
286    }
287
288    #[test]
289    fn tag_size() {
290        assert_eq!(TAG_SIZE, 32);
291        assert_eq!(TAG_SIZE, <HmacSha256 as OutputSizeUser>::OutputSize::USIZE);
292    }
293
294    #[test]
295    fn version() {
296        assert_eq!(Version::V0 as u8, 0);
297        assert_eq!(Version::V1 as u8, 1);
298    }
299
300    #[test]
301    fn size_of_version() {
302        assert_eq!(mem::size_of::<Version>(), mem::size_of::<u8>());
303    }
304
305    #[test]
306    fn clone_version() {
307        assert_eq!(Version::V0.clone(), Version::V0);
308        assert_eq!(Version::V1.clone(), Version::V1);
309    }
310
311    #[test]
312    fn copy_version() {
313        {
314            let a = Version::V0;
315            let b = a;
316            assert_eq!(a, b);
317        }
318
319        {
320            let a = Version::V1;
321            let b = a;
322            assert_eq!(a, b);
323        }
324    }
325
326    #[cfg(feature = "alloc")]
327    #[test]
328    fn debug_version() {
329        assert_eq!(format!("{:?}", Version::V0), "V0");
330        assert_eq!(format!("{:?}", Version::V1), "V1");
331    }
332
333    #[test]
334    fn default_version() {
335        assert_eq!(Version::default(), Version::V0);
336    }
337
338    #[test]
339    fn version_equality() {
340        assert_eq!(Version::V0, Version::V0);
341        assert_ne!(Version::V0, Version::V1);
342        assert_ne!(Version::V1, Version::V0);
343        assert_eq!(Version::V1, Version::V1);
344    }
345
346    #[test]
347    fn from_version_to_u8() {
348        assert_eq!(u8::from(Version::V0), 0);
349        assert_eq!(u8::from(Version::V1), 1);
350    }
351
352    #[test]
353    fn magic_number() {
354        assert_eq!(str::from_utf8(&Header::MAGIC_NUMBER).unwrap(), "scrypt");
355    }
356
357    #[test]
358    fn derived_key_size() {
359        assert_eq!(DerivedKey::SIZE, 64);
360    }
361}