zeros/poly1305/
xchacha20.rs

1/*
2==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--
3
4Zeros
5
6Copyright (C) 2019-2025  Anonymous
7
8There are several releases over multiple years,
9they are listed as ranges, such as: "2019-2025".
10
11This program is free software: you can redistribute it and/or modify
12it under the terms of the GNU Lesser General Public License as published by
13the Free Software Foundation, either version 3 of the License, or
14(at your option) any later version.
15
16This program is distributed in the hope that it will be useful,
17but WITHOUT ANY WARRANTY; without even the implied warranty of
18MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19GNU Lesser General Public License for more details.
20
21You should have received a copy of the GNU Lesser General Public License
22along with this program.  If not, see <https://www.gnu.org/licenses/>.
23
24::--::--::--::--::--::--::--::--::--::--::--::--::--::--::--::--
25*/
26
27//! # Poly1305-XChacha20
28//!
29//! ## Notes
30//!
31//! -   This module is `unsafe`, because we made it from a [*drafted* specification][specification].  We have tried to search for a formal one,
32//!     but failed.
33//! -   Tests are passed using data from the draft specification.
34//! -   Tests are also passed against [`libsodium`][site:libsodium] project.
35//! -   Key is same as [`chacha::Key`][type:chacha/Key].
36//! -   But nonce must be [`192`-bit][type:Nonce].
37//! -   Additional authenticated data (AAD) is optional.
38//! -   For small amount of data, you can use shortcuts like [`encrypt()`][fn:encrypt], [`encrypt_stream()`][fn:encrypt_stream],
39//!     [`decrypt()`][fn:decrypt]... For large data, use [`make_encrypter()`][fn:make_encrypter], [`make_decrypter()`][fn:make_decrypter].
40//! -   Some functions use an initial counter of zero.  That is **NOT** in specification.  They exist only for some odd features provided by
41//!     other libraries, such as [`libsodium`][site:libsodium].  Use with care!
42//!
43//! [type:Nonce]: type.Nonce.html
44//! [type:chacha/Key]: ../../chacha/type.Key.html
45//!
46//! [specification]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha-03
47//! [site:libsodium]: https://github.com/jedisct1/libsodium
48//! [fn:encrypt]: fn.encrypt.html
49//! [fn:encrypt_stream]: fn.encrypt_stream.html
50//! [fn:decrypt]: fn.decrypt.html
51//! [fn:make_encrypter]: fn.make_encrypter.html
52//! [fn:make_decrypter]: fn.make_decrypter.html
53
54use {
55    core::mem,
56    alloc::{
57        string::String,
58        vec::Vec,
59    },
60    crate::{
61        Bytes,
62        Result,
63        chacha::{
64            self,
65            Chacha,
66            Key,
67            TWENTY,
68            word_array::{self, WORD_SIZE},
69        },
70    },
71    super::{
72        Poly1305,
73        Tag,
74        chacha20::{self, Args, Counter},
75        encrypted_data_buffer::EncryptedDataBuffer,
76    },
77};
78
79#[cfg(not(feature="std"))]
80use crate::io::Write;
81
82#[cfg(feature="std")]
83use {
84    std::io::{Read, Write},
85    crate::IoResult,
86};
87
88#[cfg(feature="simd")]
89use crate::chacha::salsa20_simd::run_quarter_rounds;
90
91#[cfg(not(feature="simd"))]
92use crate::chacha::salsa20::run_quarter_rounds;
93
94/// # Nonce (`192` bits)
95///
96/// ## Notes
97///
98/// This is __`192-bit`__, which is different to [`chacha::Nonce`][type:chacha/Nonce] (`128` bits).
99///
100/// [type:chacha/Nonce]: ../../chacha/type.Nonce.html
101pub type Nonce = [u8; 24];
102
103/// # Makes new Poly1305-XChacha20 for encryption
104pub unsafe fn make_encrypter<K, N, A, W>(key: K, nonce: N, aad: Option<A>, output: W) -> Result<Poly1305<&'static [u8], W>>
105where K: AsRef<[u8]>, N: AsRef<[u8]>, A: AsRef<[u8]>, W: Write {
106    unsafe {
107        make_encrypter_with_counter(key, nonce, aad, output, Counter::DEFAULT)
108    }
109}
110
111/// # Makes new Poly1305-XChacha20 for encryption
112unsafe fn make_encrypter_with_counter<K, N, A, W>(key: K, nonce: N, aad: Option<A>, output: W, counter: Counter) ->
113Result<Poly1305<&'static [u8], W>>
114where K: AsRef<[u8]>, N: AsRef<[u8]>, A: AsRef<[u8]>, W: Write {
115    let args = make_args(true, key, nonce, aad, output, counter)?;
116    Ok(Poly1305 {
117        encrypted_data_buffer: None,
118        chacha20: args.chacha20,
119        aad_len: args.aad_len,
120        s: args.s,
121        output_size: u64::MIN,
122    })
123}
124
125/// # Makes new Poly1305-XChacha20 for decryption
126///
127/// When done decrypting, if the tag made from input data does not match your `authenticated_tag`, an error is returned.
128pub unsafe fn make_decrypter<K, N, A, T, W>(key: K, nonce: N, aad: Option<A>, authenticated_tag: T, output: W) -> Result<Poly1305<T, W>>
129where K: AsRef<[u8]>, N: AsRef<[u8]>, A: AsRef<[u8]>, T: AsRef<[u8]>, W: Write {
130    unsafe {
131        make_decrypter_with_counter(key, nonce, aad, authenticated_tag, output, Counter::DEFAULT)
132    }
133}
134
135unsafe fn make_decrypter_with_counter<K, N, A, T, W>(key: K, nonce: N, aad: Option<A>, authenticated_tag: T, output: W, counter: Counter)
136-> Result<Poly1305<T, W>>
137where K: AsRef<[u8]>, N: AsRef<[u8]>, A: AsRef<[u8]>, T: AsRef<[u8]>, W: Write {
138    let args = make_args(false, key, nonce, aad, output, counter)?;
139    Ok(Poly1305 {
140        encrypted_data_buffer: Some(EncryptedDataBuffer::new(authenticated_tag, args.tag, args.r)),
141        chacha20: args.chacha20,
142        aad_len: args.aad_len,
143        s: args.s,
144        output_size: u64::MIN,
145    })
146}
147
148/// # Makes arguments
149fn make_args<K, N, A, W>(for_encrypter: bool, key: K, nonce: N, aad: Option<A>, output: W, counter: Counter) -> Result<Args<W>>
150where K: AsRef<[u8]>, N: AsRef<[u8]>, A: AsRef<[u8]>, W: Write {
151    let (key, nonce) = make_key_and_chacha20_nonce(key, nonce)?;
152    chacha20::make_args(for_encrypter, key, nonce, aad, output, counter)
153}
154
155/// # Makes key and Chacha20 nonce
156fn make_key_and_chacha20_nonce<K, N>(key: K, nonce: N) -> Result<(Key, chacha20::Nonce)> where K: AsRef<[u8]>, N: AsRef<[u8]> {
157    let key = key.as_ref();
158    let key = Key::try_from(key)
159        .map_err(|_| err!("Invalid key size: {key_len}. Required: {required}", key_len=key.len(), required=mem::size_of::<Key>()))?;
160
161    let nonce = nonce.as_ref();
162    let nonce = Nonce::try_from(nonce)
163        .map_err(|_| err!("Invalid nonce size: {nonce_len}. Required: {required}", nonce_len=nonce.len(), required=mem::size_of::<Nonce>()))?;
164
165    let key = {
166        let mut key = word_array::new(key, chacha::Nonce::try_from(&nonce[..size_of::<chacha::Nonce>()]).map_err(|_| e!())?);
167        run_quarter_rounds(&TWENTY, &mut key);
168
169        let mut tmp = Key::default();
170        macro_rules! copy { ($(($i: literal, $k: literal)),+) => {
171            $(
172                tmp[$i .. $i + WORD_SIZE].copy_from_slice(&key[$k].to_le_bytes());
173            )+
174        }}
175        copy!((0, 0), (4, 1), (8, 2), (12, 3), (16, 12), (20, 13), (24, 14), (28, 15));
176        tmp
177    };
178
179    let nonce = {
180        let mut tmp = chacha20::Nonce::default();
181        tmp[4..].copy_from_slice(&nonce[size_of::<chacha::Nonce>()..]);
182        tmp
183    };
184
185    Ok((key, nonce))
186}
187
188/// # Encrypts some bytes
189pub unsafe fn encrypt<'a, const X: usize, K, N, A, B, B0>(key: K, nonce: N, aad: Option<A>, bytes: B) -> Result<(Vec<u8>, Tag)>
190where K: AsRef<[u8]>, N: AsRef<[u8]>, A: AsRef<[u8]>, B: Into<Bytes<'a, X, B0>>, B0: AsRef<[u8]> + 'a {
191    let mut encrypter = unsafe {
192        make_encrypter(key, nonce, aad, Vec::new())?
193    };
194    encrypter.encrypt_bytes(bytes)?;
195    encrypter.finish()
196}
197
198/// # Encrypts a stream
199///
200/// _See [`io`](../../io/index.html#reading-streams) for more details._
201#[cfg(feature="std")]
202#[doc(cfg(feature="std"))]
203pub unsafe fn encrypt_stream<K, N, A, R>(key: K, nonce: N, aad: Option<A>, stream: &mut R, capacity: Option<usize>) -> IoResult<(Vec<u8>, Tag)>
204where K: AsRef<[u8]>, N: AsRef<[u8]>, A: AsRef<[u8]>, R: Read {
205    let mut encrypter = unsafe {
206        make_encrypter(key, nonce, aad, Vec::with_capacity(capacity.unwrap_or(0)))?
207    };
208    crate::io::read_stream(stream, |data| {
209        encrypter.encrypt(data)?;
210        Ok(())
211    })?;
212    Ok(encrypter.finish()?)
213}
214
215/// # Decrypts some bytes
216pub unsafe fn decrypt<'a, const X: usize, K, N, A, T, B, B0>(key: K, nonce: N, aad: Option<A>, authenticated_tag: T, bytes: B) -> Result<Vec<u8>>
217where K: AsRef<[u8]>, N: AsRef<[u8]>, A: AsRef<[u8]>, T: AsRef<[u8]>, B: Into<Bytes<'a, X, B0>>, B0: AsRef<[u8]> + 'a {
218    let mut decrypter = unsafe {
219        make_decrypter(key, nonce, aad, authenticated_tag, Vec::new())?
220    };
221    decrypter.encrypt_bytes(bytes)?;
222    decrypter.finish().map(|(bytes, _)| bytes)
223}
224
225/// # Decrypts a stream
226///
227/// _See [`io`](../../io/index.html#reading-streams) for more details._
228#[cfg(feature="std")]
229#[doc(cfg(feature="std"))]
230pub unsafe fn decrypt_stream<K, N, A, T, R>(key: K, nonce: N, aad: Option<A>, authenticated_tag: T, stream: &mut R, capacity: Option<usize>)
231-> IoResult<Vec<u8>>
232where K: AsRef<[u8]>, N: AsRef<[u8]>, A: AsRef<[u8]>, T: AsRef<[u8]>, R: Read {
233    let mut decrypter = unsafe {
234        make_decrypter(key, nonce, aad, authenticated_tag, Vec::with_capacity(capacity.unwrap_or(0)))?
235    };
236    crate::io::read_stream(stream, |data| {
237        decrypter.encrypt(data)?;
238        Ok(())
239    })?;
240    Ok(decrypter.finish()?.0)
241}
242
243/// # Decrypts some bytes to string
244pub unsafe fn decrypt_to_string<'a, const X: usize, K, N, A, T, B, B0>(key: K, nonce: N, aad: Option<A>, authenticated_tag: T, bytes: B)
245-> Result<String>
246where K: AsRef<[u8]>, N: AsRef<[u8]>, A: AsRef<[u8]>, T: AsRef<[u8]>, B: Into<Bytes<'a, X, B0>>, B0: AsRef<[u8]> + 'a {
247    unsafe {
248        crate::str::string_from_utf8(decrypt(key, nonce, aad, authenticated_tag, bytes)?)
249    }
250}
251
252/// # Decrypts a stream to string
253///
254/// _See [`io`](../../io/index.html#reading-streams) for more details._
255#[cfg(feature="std")]
256#[doc(cfg(feature="std"))]
257pub unsafe fn decrypt_stream_to_string<K, N, A, T, R>(
258    key: K, nonce: N, aad: Option<A>, authenticated_tag: T, stream: &mut R, capacity: Option<usize>,
259) -> IoResult<String>
260where K: AsRef<[u8]>, N: AsRef<[u8]>, A: AsRef<[u8]>, T: AsRef<[u8]>, R: Read {
261    unsafe {
262        Ok(crate::str::string_from_utf8(decrypt_stream(key, nonce, aad, authenticated_tag, stream, capacity)?)?)
263    }
264}
265
266/// # Makes new Chacha20
267pub unsafe fn make_chacha20<K, N, W>(key: K, nonce: N, output: W) -> Result<Chacha<W>> where K: AsRef<[u8]>, N: AsRef<[u8]>, W: Write {
268    let (key, nonce) = make_key_and_chacha20_nonce(key, nonce)?;
269    Ok(chacha20::make_chacha20_from_key_and_nonce(key, nonce, output, Counter::DEFAULT))
270}
271
272/// # Encrypts some bytes with just Chacha20
273pub unsafe fn encrypt_with_chacha20<'a, const X: usize, K, N, B, B0>(key: K, nonce: N, bytes: B) -> Result<Vec<u8>>
274where K: AsRef<[u8]>, N: AsRef<[u8]>, B: Into<Bytes<'a, X, B0>>, B0: AsRef<[u8]> + 'a {
275    let mut chacha20 = unsafe {
276        make_chacha20(key, nonce, Vec::new())?
277    };
278    chacha20.encrypt_bytes(bytes)?;
279    chacha20.finish()
280}
281
282/// # Makes new Chacha20 with zero counter
283pub unsafe fn make_chacha20_with_zero_counter<K, N, W>(key: K, nonce: N, output: W) -> Result<Chacha<W>>
284where K: AsRef<[u8]>, N: AsRef<[u8]>, W: Write {
285    let (key, nonce) = make_key_and_chacha20_nonce(key, nonce)?;
286    Ok(chacha20::make_chacha20_from_key_and_nonce(key, nonce, output, unsafe { Counter::zero() }))
287}
288
289/// # Encrypts some bytes with just Chacha20 setup with zero counter
290pub unsafe fn encrypt_with_chacha20_with_zero_counter<'a, const X: usize, K, N, B, B0>(key: K, nonce: N, bytes: B) -> Result<Vec<u8>>
291where K: AsRef<[u8]>, N: AsRef<[u8]>, B: Into<Bytes<'a, X, B0>>, B0: AsRef<[u8]> + 'a {
292    let mut chacha20 = unsafe {
293        make_chacha20_with_zero_counter(key, nonce, Vec::new())?
294    };
295    chacha20.encrypt_bytes(bytes)?;
296    chacha20.finish()
297}