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}