Skip to content

Commit 29b421c

Browse files
committed
Add various combinations of buffer<->file encryption/decryption
1 parent 0f8a7f5 commit 29b421c

File tree

5 files changed

+1033
-90
lines changed

5 files changed

+1033
-90
lines changed

include/session/attachments.hpp

Lines changed: 201 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,12 @@ constexpr size_t ENCRYPT_KEY_SIZE = 32;
3636
// retrieved via oxen-storage-server onion requests, and its padded size is the maximum attachment
3737
// size allowed by the storage server. (Technically this value was chosen as it is the largest
3838
// unencrypted data size that has the same padded+encrypted size as a 10'000'000B file).
39-
constexpr size_t ENCRYPT_MAX_SIZE =
39+
constexpr size_t MAX_REGULAR_SIZE =
4040
10218286; // == 10223616 after stream mac+tag and (1-byte) padding
4141

4242
// Returns the amount of padding to add to an attachment to obfuscate the true size, given
43-
// crypto_secretstream encryption with a 32kiB chunk size. We determine the padded size as follows,
44-
// given an input size N:
43+
// crypto_secretstream encryption with a 32kiB chunk size. We determine the padded size as
44+
// follows, given an input size N:
4545
//
4646
// - compute the total raw size M as N plus:
4747
// - 1 for the 'S' prefix (outside the encryption)
@@ -65,11 +65,24 @@ constexpr size_t ENCRYPT_MAX_SIZE =
6565
// + 31 × 17 -- embedded mac+tags after every 32kiB of file stream data
6666
// = 1015808 final output.
6767
//
68-
// (Note that we always including at least one padding byte, and there are some complications in the
69-
// calculation as padding values get large enough to start inducing additional mac+tags; see the
70-
// implementation for details).
68+
// (Note that we always including at least one padding byte, and there are some complications in
69+
// the calculation as padding values get large enough to start inducing additional mac+tags; see
70+
// the implementation for details).
7171
size_t encrypted_padding(size_t data_size);
7272

73+
/// API: crypto/attachment::encrypted_size
74+
///
75+
/// Returns the exact final encrypted (including any overhead and padding) of an input of
76+
/// `plaintext_size`.
77+
size_t encrypted_size(size_t plaintext_size);
78+
79+
/// API: crypto/attachment::decrypted_max_size
80+
///
81+
/// Returns the maximum possible decrypted size of encrypted data of length `encrypted_size`. The
82+
/// actual size can be (and usually is) less than this depending on how much padding is in the data.
83+
/// Returns std::nullopt if the input is too small to be a valid encrypted attachment.
84+
std::optional<size_t> decrypted_max_size(size_t encrypted_size);
85+
7386
/// API: crypto/attachment::encrypt
7487
///
7588
/// Encrypt an attachment for storage on the file server and distribution to other users using
@@ -118,9 +131,105 @@ std::pair<std::vector<std::byte>, std::array<std::byte, ENCRYPT_KEY_SIZE>> encry
118131
Domain domain,
119132
bool allow_large = false);
120133

134+
/// API: crypto/attachment::encrypt
135+
///
136+
/// Similar to the above `encrypt` except that instead of allocating and returning a vector it
137+
/// writes the encrypted result directly into a given output span. The output span *must* be
138+
/// exactly `encrypted_size()` bytes long (but this is checked via assertion in debug builds).
139+
///
140+
/// Inputs:
141+
/// - `seed` -- as above
142+
/// - `data` -- as above
143+
/// - `domain` -- as above
144+
/// - `out` -- writeable span into which the encrypted data will be written. This span must be
145+
/// exactly `encrypted_size(data.size())` bytes long.
146+
/// - `allow_large` -- as above.
147+
///
148+
/// Outputs:
149+
/// - 32 byte decryption key
150+
///
151+
/// Throws std::invalid_argument if `seed` is shorter than 32 bytes, or if data is larger than
152+
/// MAX_REGULAR_SIZE (unless `allow_large` is true).
153+
std::array<std::byte, ENCRYPT_KEY_SIZE> encrypt(
154+
std::span<const std::byte> seed,
155+
std::span<const std::byte> data,
156+
Domain domain,
157+
std::span<std::byte> out,
158+
bool allow_large = false);
159+
160+
/// API: crypto/attachment::encrypt
161+
///
162+
/// Encrypts the contents of a file on disk into a buffer. This requires reading the file twice
163+
/// (once in order to generate the deterministic encryption key and nonce, and then a second time
164+
/// for the actual encryption), but does not require holding the file contents in memory.
165+
///
166+
/// Inputs:
167+
/// - `seed`, `domain`, `allow_large` -- see above.
168+
/// - `file` -- path to the file to encrypt.
169+
///
170+
/// Outputs:
171+
/// - Pair of values: the padded+encrypted data, and the decryption key (32 bytes), both in raw
172+
/// bytes.
173+
///
174+
/// Throws std::invalid_argument if `seed` is shorter than 32 bytes, or if the file is larger than
175+
/// MAX_REGULAR_SIZE.
176+
std::pair<std::vector<std::byte>, std::array<std::byte, ENCRYPT_KEY_SIZE>> encrypt(
177+
std::span<const std::byte> seed,
178+
const std::filesystem::path& file,
179+
Domain domain,
180+
bool allow_large = false);
181+
182+
/// API: crypto/attachment::encrypt
183+
///
184+
/// Encrypts the contents of a file on disk into a buffer. This method is a more general version of
185+
/// the above that allows allocation of the encrypted buffer via a callback once the size is
186+
/// determined from the file.
187+
///
188+
/// Inputs:
189+
/// - `seed`, `domain`, `allow_large` -- see above.
190+
/// - `file` -- path to the file to encrypt.
191+
/// - `make_buffer` -- callback that is invoked with the exact required encrypted size for the file
192+
/// that must return a byte span of that exact file where the encrypted data will be written.
193+
///
194+
/// Outputs:
195+
/// - The 32-byte decryption key, in raw bytes.
196+
///
197+
/// Throws std::invalid_argument if `seed` is shorter than 32 bytes, or if the file is larger than
198+
/// MAX_REGULAR_SIZE.
199+
/// Throws std::runtime_error if the file size changes between first and second passes.
200+
std::array<std::byte, ENCRYPT_KEY_SIZE> encrypt(
201+
std::span<const std::byte> seed,
202+
const std::filesystem::path& file,
203+
Domain domain,
204+
std::function<std::span<std::byte>(size_t enc_size)> make_buffer,
205+
bool allow_large = false);
206+
207+
/// API: crypto/attachment::encrypt
208+
///
209+
/// Encrypts the contents of a plaintext buffer, writing the encrypted data to a file. The file
210+
/// will be overwritten.
211+
///
212+
/// Inputs:
213+
/// - `seed`, `domain`, `allow_large` -- see above.
214+
/// - `data` -- the buffer of data to encrypt.
215+
/// - `file` -- path to the file to write to.
216+
///
217+
/// Outputs:
218+
/// - The 32-byte decryption key, in raw bytes.
219+
///
220+
/// Throws std::invalid_argument if `seed` is shorter than 32 bytes, or if data is larger than
221+
/// MAX_REGULAR_SIZE (unless `allow_large` is given). Throws on I/O error. If decryption fails
222+
/// then any partially written output file will be removed.
223+
std::array<std::byte, ENCRYPT_KEY_SIZE> encrypt(
224+
std::span<const std::byte> seed,
225+
std::span<const std::byte> data,
226+
Domain domain,
227+
const std::filesystem::path& file,
228+
bool allow_large = false);
229+
121230
/// API: crypto/attachment::decrypt
122231
///
123-
/// Decrypts an attachment allegedly produced by attachment::encrypt to a single in-memory buffer.
232+
/// Decrypts an attachment allegedly produced by attachment::encrypt to an in-memory byte vector.
124233
///
125234
/// Inputs:
126235
/// - `data` -- in-memory buffer of data to decrypt.
@@ -133,6 +242,28 @@ std::pair<std::vector<std::byte>, std::array<std::byte, ENCRYPT_KEY_SIZE>> encry
133242
std::vector<std::byte> decrypt(
134243
std::span<const std::byte> encrypted, std::span<const std::byte, ENCRYPT_KEY_SIZE> key);
135244

245+
/// API: crypto/attachment::decrypt
246+
///
247+
/// Decrypts an attachment allegedly produced by attachment::encrypt to a single in-memory,
248+
/// caller-provided buffer. This version writes into a given output span rather than allocating a
249+
/// new vector.
250+
///
251+
/// Inputs:
252+
/// - `data` -- in-memory buffer of data to decrypt.
253+
/// - `key` -- the 32-byte decryption key
254+
/// - `out` -- writeable output span in which the decrypted value should be written. The given span
255+
/// must be at least `decrypted_max_size(data.size())` bytes large.
256+
///
257+
/// Outputs:
258+
/// - size_t -- the actual decrypted data size written into `out` which could be (and often is, due
259+
/// to padding) less than `out.size()`.
260+
///
261+
/// Throws std::runtime_error if decryption fails.
262+
size_t decrypt(
263+
std::span<const std::byte> encrypted,
264+
std::span<const std::byte, ENCRYPT_KEY_SIZE> key,
265+
std::span<std::byte> out);
266+
136267
/// API: crypto/attachment::Decryptor
137268
///
138269
/// Object-based interfaced to streaming decryption. The basic usage is to construct the object
@@ -205,4 +336,67 @@ void decrypt(
205336
std::span<const std::byte, ENCRYPT_KEY_SIZE> key,
206337
const std::filesystem::path& filename);
207338

339+
/// API: crypto/attachment::decrypt
340+
///
341+
/// Decrypts an encrypted attachment stored in an input file into a byte vector.
342+
///
343+
/// Inputs:
344+
/// - `filename` -- path to encrypted file.
345+
/// - `key` -- the 32-byte decryption key.
346+
///
347+
/// Outputs:
348+
/// - vector of decrypted content.
349+
///
350+
/// Throws std::runtime_error if decryption fails; can throw I/O exceptions if reading the file
351+
/// fails.
352+
std::vector<std::byte> decrypt(
353+
const std::filesystem::path& encrypted_file,
354+
std::span<const std::byte, ENCRYPT_KEY_SIZE> key);
355+
356+
/// API: crypto/attachment::decrypt
357+
///
358+
/// Decrypts an encrypted attachment stored in an input file into a provided memory buffer.
359+
///
360+
/// Inputs:
361+
/// - `filename` -- path to encrypted file.
362+
/// - `key` -- the 32-byte decryption key.
363+
/// - `make_buffer` -- callback that is invoked to allocate the buffer into which the content should
364+
/// be written. This is passed the required buffer size. Note that this buffer may not be
365+
/// completely filled: the return value of `decrypt()` indicates the actual amount of the buffer
366+
/// that was written.
367+
///
368+
/// Outputs:
369+
/// - size_t the actual decrypted size. Can be less than the value passed to `decrypted.size()`
370+
/// because of padding.
371+
///
372+
/// Throws std::runtime_error if decryption fails; can throw I/O exceptions if reading the file
373+
/// fails.
374+
size_t decrypt(
375+
const std::filesystem::path& encrypted_file,
376+
std::span<const std::byte, ENCRYPT_KEY_SIZE> key,
377+
std::function<std::span<std::byte>(size_t dec_size)> make_buffer);
378+
379+
/// API: crypto/attachment::decrypt
380+
///
381+
/// Decrypts an attachment allegedly produced by attachment::encrypt stored in a file to another
382+
/// output file. Overwrites the destination file if it already exists.
383+
///
384+
/// Unlike the various decrypt functions above, this version does not need to hold more than a few
385+
/// kB of the input/output file in memory at a time, regardless of the size of the input or output
386+
/// files.
387+
///
388+
/// Inputs:
389+
/// - `file_in` -- filename containing the data to decrypt.
390+
/// - `key` -- the 32-byte decryption key.
391+
/// - `file_out` -- where to write the output file.
392+
///
393+
/// Outputs: None.
394+
///
395+
/// Throws std::runtime_error if decryption fails or if writing to the file fails. Upon exception a
396+
/// partially written file will be deleted.
397+
void decrypt(
398+
const std::filesystem::path& file_in,
399+
std::span<const std::byte, ENCRYPT_KEY_SIZE> key,
400+
const std::filesystem::path& file_out);
401+
208402
} // namespace session::attachment

0 commit comments

Comments
 (0)