Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

aead: Add detached in-place encrypt/decrypt API; make alloc optional #58

Merged
merged 1 commit into from
Nov 17, 2019
Merged

aead: Add detached in-place encrypt/decrypt API; make alloc optional #58

merged 1 commit into from
Nov 17, 2019

Conversation

tarcieri
Copy link
Member

@tarcieri tarcieri commented Oct 4, 2019

This commit adds encrypt_in_place_detached() and decrypt_in_place_detached() methods to Aead and AeadMut, along with default implementations of encrypt() and decrypt() which assume a postfix authentication tag.

Presently all of the AEAD implementations in RustCrypto/AEADs use this pattern. This PR exposes the detached interface as a public API:

RustCrypto/AEADs#21

Note that this need not be the only in-place API (hence the long name with _detached on the end. I plan on doing a follow-up PR for adding an in-place API which does not depend on alloc but also handles ciphertext message assembly/parsing, which would be more useful for end
users.

That said, this API isn't just useful for AEAD implementers: there are genuine use cases for a detached API from an end user perspective, e.g encrypted filesystems.

With the addition of this API, we can also make alloc an optional (but
enabled-by-default) feature, allowing use of AEADs on truly #![no_std]
targets.

@tarcieri
Copy link
Member Author

tarcieri commented Oct 4, 2019

cc @newpavlov

This commit adds `encrypt_in_place_detached()` and
`decrypt_in_place_detached()` methods to `Aead` and `AeadMut`, along
with default implementations of `encrypt()` and `decrypt()` which assume
a postfix authentication tag.

Presently all of the AEAD implementations in `RustCrypto/AEADs` use this
pattern. This PR exposes the detached interface as a public API:

RustCrypto/AEADs#21

Note that this need not be the only in-place API (hence the long name
with `_detached` on the end. I plan on doing a follow-up PR for adding
an in-place API which does not depend on `alloc` but also handles
ciphertext message assembly/parsing, which would be more useful for end
users.

That said, this API isn't just useful for AEAD implementers: there are
genuine use cases for a detached API from an end user perspective, e.g
encrypted filesystems.

With the addition of this API, we can also make `alloc` an optional (but
enabled-by-default) feature, allowing use of AEADs on truly `#![no_std]`
targets.
@tarcieri tarcieri changed the title Add detached in-place encrypt/decrypt API; make alloc optional aead: Add detached in-place encrypt/decrypt API; make alloc optional Oct 4, 2019
nonce: &GenericArray<u8, Self::NonceSize>,
associated_data: &[u8],
buffer: &mut [u8],
) -> Result<GenericArray<u8, Self::TagSize>, Error>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you help me understand why you'd want to return the tag here, rather than doing that in-place as well? Other than requiring split_at_mut() in the macro it seems like it would otherwise be superior...

Copy link
Member Author

@tarcieri tarcieri Oct 10, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's actually what I originally suggested here: #40 (comment)

@newpavlov suggested returning the tag instead here: #40 (comment)

I ended up liking that suggestion. I think it's cleaner because it's one less side effect, one less mutable argument, and actually leverages the return value.

Every time you rely on a side effect there's a risk it won't happen. That risk is unavoidable with an in-place API: mutation-by-side-effect is the name of the game. But we don't need to rely on it happening it twice, and can instead have the underlying implementation construct the tag by value, instead of it being one more thing for the caller to initialize with the hope the backing implementation actually does its job.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dunno, if the cipher implementation is doing all this in-place, and it has trouble mutating slices, my intuition is that you've probably got bigger problems than worrying about the tag getting written.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @jcape on doing the tag in-place. Typically, the tag will be prefixed/suffixed to the ciphertext anyways. From a performance standpoint, it would be great if this didn't require a memcpy.

IMO, the risk of the backing implementation not mutating the tag is roughly the same as it returning a bad tag.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be a bit extreme, but I would be in favor of having 0 visibility into the tag for all APIs. It should be treated as implicit ciphertext expansion.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be a bit extreme, but I would be in favor of having 0 visibility into the tag for all APIs. It should be treated as implicit ciphertext expansion.

There's an extensive discussion on the ciphertext layout in #40 :-) I'm generally in agreement with you (RFC and all), but if it prevents divergent implementations for weird non-RFC-compliant schemes like the encrypted filesystem thing where the tag lives in some separate metadata structure, I'm OK with exposing this API.

@akhilles
Copy link
Contributor

I was exploring an uglier version of the API that you're proposing:

fn encrypt_in_place<'a>(
  &self,
  nonce: &GenericArray<u8, Self::NonceSize>,
  buffer: &'a mut [u8],
  offset: usize,
  len: usize,
  associated_data: &[u8],
) -> Result<&'a [u8], Error>;

It attempts to maximize flexibility at the cost of certain assumptions that need to be made:

  • plaintext fits in buffer: buffer.len() >= offset + len
  • ciphertext fits in buffer: buffer.len() >= len + tag_len

Is this something that can coexist with encrypt_in_place_detached() and decrypt_in_place_detached(). Maybe it can be part of an UnsafeAEAD trait?

@akhilles
Copy link
Contributor

Alternative using range bounds:

fn encrypt_in_place<'a>(
  &self,
  nonce: &GenericArray<u8, Self::NonceSize>,
  buffer: &'a mut [u8],
  plaintext: RangeFrom<usize>,
  associated_data: &[u8],
) -> Result<&'a [u8], Error>;

fn decrypt_in_place<'a>(
  &self,
  nonce: &GenericArray<u8, Self::NonceSize>,
  buffer: &'a mut [u8],
  ciphertext_tag: RangeFrom<usize>,
  associated_data: &[u8],
) -> Result<&'a [u8], Error>;

@tarcieri
Copy link
Member Author

@akhilles see #59 for my take on an in-place API based on a Buffer trait, based loosely on some of the things @newpavlov was proposing back here (albeit using a trait instead of a callback function): #40 (comment)

Is this something that can coexist with encrypt_in_place_detached() and decrypt_in_place_detached()

Yup, that was the goal of having *_in_place_detached vs *_in_place and composing them

@akhilles
Copy link
Contributor

Since the _in_place_detached API is the implementation layer, can the offset param be added to it? It's very useful for processing messages/packets with headers, and it cannot be efficiently incorporated into a higher level API.

@tarcieri
Copy link
Member Author

tarcieri commented Oct 19, 2019

@akhilles I don't understand what you're trying to accomplish which can't be accomplished by slicing the buffer alone...

@akhilles
Copy link
Contributor

@tarcieri, borrowing from ring's documentation:

                Packet 1                  Packet 2                 Packet 3
Input:  [Header][Ciphertext][Tag][Header][Ciphertext][Tag][Header][Ciphertext][Tag]
                     |         +--------------+                        |
              +------+   +-----+    +----------------------------------+
              v          v          v
Output: [Plaintext][Plaintext][Plaintext]
       “Split stream reassembled in place”

this can't be done with slicing AFAIK

@akhilles
Copy link
Contributor

I guess I should have been more specific: offset is only used for the input buffer. The output starts at index 0 and overwrites the offset bytes.

I encountered this use case a lot in applications using mbedtls. It's typically solved by offsetting the raw pointers for input and output.

@tarcieri
Copy link
Member Author

If I understand correctly, you have multiple AEAD messages in a single buffer, and want the resulting plaintexts to be written to an earlier position in the same buffer?

@akhilles
Copy link
Contributor

@tarcieri yup!

@tarcieri
Copy link
Member Author

Ok, interesting. That seems worth supporting, but also note that to really be helpful we'd need to implement offsets throughout the whole "stack", e.g. in SyncStreamCipher.

Without that the only thing the current AEAD implementations can do is copy the plaintext after decryption.

@akhilles
Copy link
Contributor

Understood, it's probably a bit out of scope for this PR. I'll start with some of the lower level components and try to introduce offset where it makes sense.

@tarcieri
Copy link
Member Author

tarcieri commented Oct 19, 2019

@akhilles we can go ahead and add it to the API now, it's just it will have to be implemented entirely in terms of something like copy_within until an apply_keystream_with_offset method or thereabouts is added to SyncStreamCipher

@tarcieri
Copy link
Member Author

tarcieri commented Nov 3, 2019

@newpavlov any chance you have some time to look at this?

Copy link
Member

@newpavlov newpavlov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the delay!

I am not 100% happy with this API (ideally I would like to do unification with block-modes), but it should be a fine starting point. The request raised by @akhilles is an interesting one and we should not forget about it during discussions of the next version.

@@ -12,3 +12,7 @@ categories = ["cryptography", "no-std"]

[dependencies]
generic-array = { version = "0.12", default-features = false }

[features]
default = ["alloc"]
Copy link
Member

@newpavlov newpavlov Nov 17, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the implementation crates don't forget to write:

aead = { version = "0.1.2", default-features=false }
[features]
default = ["alloc"]
alloc = ["aead/alloc"]

And in the next minor version we should not forget to clean it up.

@newpavlov newpavlov merged commit 8e522f8 into RustCrypto:master Nov 17, 2019
@tarcieri tarcieri deleted the aead/detached-api branch November 17, 2019 23:21
@tarcieri
Copy link
Member Author

@newpavlov awesome, thank you! Does this mean you're going to cut a v0.1.2 with this in it?

dns2utf8 pushed a commit to dns2utf8/traits that referenced this pull request Jan 24, 2023
dns2utf8 pushed a commit to dns2utf8/traits that referenced this pull request Jan 24, 2023
Cuts the following new releases which include an upgrade to
`crypto-mac` v0.10 (RustCrypto#58):

- `bcrypt-pbkdf` v0.4.0
- `pbkdf2` v0.6.0
- `scrypt` v0.5.0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants