# XChacha20poly1305 vulnerability to known plaintext attacks

During a security audit, the guy said our cryptosystem is vulnerable to known-plaintext attacks. He said that's because «the implementation uses a crypto_stream_xor() approach» (from libsodium). That's way above my skill, and I did not find anything relevant so far so I'm trying to understand if that's really a problem.

Here is what we do: we derive a key using a global (password, salt) pair. This key −always the same− is used in order to cipher all the documents using xchacha20poly1305. We initialize the stream using xchacha20poly1305::Stream::init_push: it provides us with a header (the nonce ?) that we store with the file for decryption later on. We use rust's implementation of libsodium, sodiumoxide.

• Is its assessment correct? (= is xchacha20poly1305 or our usage vulnerable to KPA, and is it due to the fact it uses a xor ?) Didn't the guy mistakingly mixed things with the fact that we reuse password?
• How bad is it? Does it mean that someone who can cipher a known document in our system can decipher anything else it puts its hands on?

Edit: As requested, here is what we do, with more details, using pseudo rust code. I edited out all the irrelevant details but hopefully left enough relevant information and comments.

password, salt, chunk_size are stored in environment variables and never change.


function encrypt(file, password, salt, chunk_size) {
// first, we derive a key
sodiumoxide::crypto::pwhash::derive_key(
&mut key,
&salt,
pwhash::OPSLIMIT_INTERACTIVE,
pwhash::MEMLIMIT_INTERACTIVE,
)

// Then we initialize the encryption stream
xchacha20poly1305::Stream::init_push(&key);

// we store the IV
store encryption_header_bytes in the output file

// then we encrypt the file
while (there are data in the file) {
// cipher the buffer using xchacha20poly1305's stream
let encoded = stream.push(&buffer[0..chunk_size], None, Tag::Message).unwrap();
store encoded in the output file
}
}

function decrypt(encrypted_file, password, salt, chunk_size) {
// first, we derive a key
sodiumoxide::crypto::pwhash::derive_key(
&mut key,
&salt,
pwhash::OPSLIMIT_INTERACTIVE,
pwhash::MEMLIMIT_INTERACTIVE,
)
// then we read the file in a buffer

// then we read the header, and create a stream out of the header and the key
.unwrap();

// then we decrypt the rest of the file using said stream
while(there are data to decrypt in the buffer) {
let (decrypted, _) = stream
.pull(
&buffer[0..(xchacha20poly1305::ABYTES + chunk_size)],
None,
)
.unwrap();
buffer

store decrypted in the output file
}
}
$$$$

• It might be helpful if you spelled out in a little more detail exactly what your protocol is, and what API you're using. – Squeamish Ossifrage Oct 30 '19 at 15:10
• That's right. I edited my question with a more accurate explanation of our encryption process. – Chacha123 Oct 30 '19 at 16:21
• Two suggestions: 1. Link to the documentation for xchacha20poly1305::init_push. 2. Write down the protocol in enough detail that you can write a compatible implementation in another language—and use that compatible implementation to generate deterministic known-answer test vectors, and then write automatic tests against those test vectors in your main implementation. – Squeamish Ossifrage Oct 30 '19 at 16:23
• It's a wrapper around crypto_secretstream_xchacha20poly1305_init_push. The call to hchacha20 initializes chacha20 with the random IV it generates. – Chacha123 Oct 30 '19 at 16:47
• Just one thing: the last block should have the Tag::Final tag, so that you can later verify that the content wasn't truncated. – Frank Denis Oct 30 '19 at 22:07

Using crypto_secretstream that should be fine. It generates a random nonce for each message. Reusing a key in that context is fine.

• Isn't that taken care of by xchacha20poly1305::Stream::init_push`? I thought it was in charge of generating the necessary random nonce (that we store in the header of the file for decryption). – Chacha123 Oct 30 '19 at 15:03