scryptenc/
encrypt.rs

1// SPDX-FileCopyrightText: 2022 Shun Sakai
2//
3// SPDX-License-Identifier: Apache-2.0 OR MIT
4
5//! Encrypts to the scrypt encrypted data format.
6
7use aes::cipher::{KeyIvInit, StreamCipher, generic_array::GenericArray};
8use hmac::Mac;
9use scrypt::Params;
10
11use crate::{
12    Aes256Ctr128BE, HEADER_SIZE, HmacSha256, HmacSha256Key, HmacSha256Output, TAG_SIZE,
13    format::{DerivedKey, Header},
14};
15
16/// Encryptor for the scrypt encrypted data format.
17#[derive(Clone, Debug)]
18pub struct Encryptor<'m> {
19    header: Header,
20    dk: DerivedKey,
21    plaintext: &'m [u8],
22}
23
24impl<'m> Encryptor<'m> {
25    /// Creates a new `Encryptor`.
26    ///
27    /// This uses the recommended scrypt parameters according to the [OWASP
28    /// Password Storage Cheat Sheet] created by [`Params::default`].
29    ///
30    /// # Examples
31    ///
32    /// ```
33    /// # use scryptenc::Encryptor;
34    /// #
35    /// let data = b"Hello, world!\n";
36    /// let passphrase = "passphrase";
37    ///
38    /// let cipher = Encryptor::new(data, passphrase);
39    /// ```
40    ///
41    /// [OWASP Password Storage Cheat Sheet]: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#scrypt
42    #[inline]
43    pub fn new(plaintext: &'m impl AsRef<[u8]>, passphrase: impl AsRef<[u8]>) -> Self {
44        Self::with_params(plaintext, passphrase, Params::default())
45    }
46
47    #[allow(clippy::missing_panics_doc)]
48    /// Creates a new `Encryptor` with the specified [`Params`].
49    ///
50    /// # Examples
51    ///
52    /// ```
53    /// # use scryptenc::{Encryptor, scrypt::Params};
54    /// #
55    /// let data = b"Hello, world!\n";
56    /// let passphrase = "passphrase";
57    ///
58    /// let params = Params::new(10, 8, 1, Params::RECOMMENDED_LEN).unwrap();
59    /// let cipher = Encryptor::with_params(data, passphrase, params);
60    /// ```
61    pub fn with_params(
62        plaintext: &'m impl AsRef<[u8]>,
63        passphrase: impl AsRef<[u8]>,
64        params: Params,
65    ) -> Self {
66        let inner = |plaintext: &'m [u8], passphrase: &[u8], params: Params| -> Self {
67            let mut header = Header::new(params);
68
69            // The derived key size is 64 bytes. The first 256 bits are for AES-256-CTR key,
70            // and the last 256 bits are for HMAC-SHA-256 key.
71            let mut dk = [u8::default(); DerivedKey::SIZE];
72            scrypt::scrypt(passphrase, &header.salt(), &header.params().into(), &mut dk)
73                .expect("derived key size should be 64 bytes");
74            let dk = DerivedKey::new(dk);
75
76            header.compute_checksum();
77            header.compute_mac(&dk.mac());
78            Self {
79                header,
80                dk,
81                plaintext,
82            }
83        };
84        inner(plaintext.as_ref(), passphrase.as_ref(), params)
85    }
86
87    /// Encrypts the plaintext into `buf`.
88    ///
89    /// # Panics
90    ///
91    /// Panics if any of the following are true:
92    ///
93    /// - `buf` and the encrypted data have different lengths.
94    /// - The end of the keystream will be reached with the given data length.
95    ///
96    /// # Examples
97    ///
98    /// ```
99    /// # use scryptenc::{Encryptor, scrypt::Params};
100    /// #
101    /// let data = b"Hello, world!\n";
102    /// let passphrase = "passphrase";
103    ///
104    /// let params = Params::new(10, 8, 1, Params::RECOMMENDED_LEN).unwrap();
105    /// let cipher = Encryptor::with_params(data, passphrase, params);
106    /// let mut buf = [u8::default(); 142];
107    /// cipher.encrypt(&mut buf);
108    /// # assert_ne!(buf.as_slice(), data);
109    /// ```
110    pub fn encrypt(&self, buf: &mut (impl AsMut<[u8]> + ?Sized)) {
111        let inner = |encryptor: &Self, buf: &mut [u8]| {
112            fn compute_mac(data: &[u8], key: &HmacSha256Key) -> HmacSha256Output {
113                let mut mac = HmacSha256::new_from_slice(key)
114                    .expect("HMAC-SHA-256 key size should be 256 bits");
115                mac.update(data);
116                mac.finalize().into_bytes()
117            }
118
119            let bound = (HEADER_SIZE, encryptor.out_len() - TAG_SIZE);
120            buf[..bound.0].copy_from_slice(&encryptor.header.as_bytes());
121            let body = &mut buf[bound.0..bound.1];
122            body.copy_from_slice(encryptor.plaintext);
123
124            let mut cipher = Aes256Ctr128BE::new(&encryptor.dk.encrypt(), &GenericArray::default());
125            cipher.apply_keystream(body);
126            let mac = compute_mac(&buf[..bound.1], &encryptor.dk.mac());
127            buf[bound.1..].copy_from_slice(&mac);
128        };
129        inner(self, buf.as_mut());
130    }
131
132    /// Encrypts the plaintext and into a newly allocated
133    /// [`Vec`](alloc::vec::Vec).
134    ///
135    /// # Examples
136    ///
137    /// ```
138    /// # use scryptenc::{Encryptor, scrypt::Params};
139    /// #
140    /// let data = b"Hello, world!\n";
141    /// let passphrase = "passphrase";
142    ///
143    /// let params = Params::new(10, 8, 1, Params::RECOMMENDED_LEN).unwrap();
144    /// let cipher = Encryptor::with_params(data, passphrase, params);
145    /// let ciphertext = cipher.encrypt_to_vec();
146    /// # assert_ne!(ciphertext, data);
147    /// ```
148    #[cfg(feature = "alloc")]
149    #[must_use]
150    #[inline]
151    pub fn encrypt_to_vec(&self) -> alloc::vec::Vec<u8> {
152        let mut buf = vec![u8::default(); self.out_len()];
153        self.encrypt(&mut buf);
154        buf
155    }
156
157    /// Returns the number of output bytes of the encrypted data.
158    ///
159    /// # Examples
160    ///
161    /// ```
162    /// # use scryptenc::{Encryptor, scrypt::Params};
163    /// #
164    /// let data = b"Hello, world!\n";
165    /// let passphrase = "passphrase";
166    ///
167    /// let params = Params::new(10, 8, 1, Params::RECOMMENDED_LEN).unwrap();
168    /// let cipher = Encryptor::with_params(data, passphrase, params);
169    /// assert_eq!(cipher.out_len(), 142);
170    /// ```
171    #[must_use]
172    #[inline]
173    pub const fn out_len(&self) -> usize {
174        assert!(self.plaintext.len() <= (usize::MAX - HEADER_SIZE - TAG_SIZE));
175        HEADER_SIZE + self.plaintext.len() + TAG_SIZE
176    }
177}
178
179/// Encrypts `plaintext` and into a newly allocated [`Vec`](alloc::vec::Vec).
180///
181/// This uses the recommended scrypt parameters according to the [OWASP Password
182/// Storage Cheat Sheet] created by [`Params::default`].
183///
184/// This is a convenience function for using [`Encryptor::new`] and
185/// [`Encryptor::encrypt_to_vec`].
186///
187/// # Examples
188///
189/// ```
190/// let data = b"Hello, world!\n";
191/// let passphrase = "passphrase";
192///
193/// let ciphertext = scryptenc::encrypt(data, passphrase);
194/// # assert_ne!(ciphertext, data);
195/// ```
196///
197/// [OWASP Password Storage Cheat Sheet]: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#scrypt
198#[cfg(feature = "alloc")]
199#[inline]
200pub fn encrypt(plaintext: impl AsRef<[u8]>, passphrase: impl AsRef<[u8]>) -> alloc::vec::Vec<u8> {
201    Encryptor::new(&plaintext, passphrase).encrypt_to_vec()
202}
203
204#[allow(clippy::module_name_repetitions)]
205/// Encrypts `plaintext` with the specified [`Params`] and into a newly
206/// allocated [`Vec`](alloc::vec::Vec).
207///
208/// This is a convenience function for using [`Encryptor::with_params`] and
209/// [`Encryptor::encrypt_to_vec`].
210///
211/// # Examples
212///
213/// ```
214/// # use scryptenc::scrypt::Params;
215/// #
216/// let data = b"Hello, world!\n";
217/// let passphrase = "passphrase";
218///
219/// let params = Params::new(10, 8, 1, Params::RECOMMENDED_LEN).unwrap();
220/// let ciphertext = scryptenc::encrypt_with_params(data, passphrase, params);
221/// # assert_ne!(ciphertext, data);
222/// ```
223#[cfg(feature = "alloc")]
224#[inline]
225pub fn encrypt_with_params(
226    plaintext: impl AsRef<[u8]>,
227    passphrase: impl AsRef<[u8]>,
228    params: Params,
229) -> alloc::vec::Vec<u8> {
230    Encryptor::with_params(&plaintext, passphrase, params).encrypt_to_vec()
231}