1 // Copyright (c) 2017-2022 The Limenka developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 5 #ifndef LIMENKA_CRYPTO_CHACHA20_H
6 #define LIMENKA_CRYPTO_CHACHA20_H
7 8 #include <span.h>
9 10 #include <array>
11 #include <cstddef>
12 #include <cstdlib>
13 #include <stdint.h>
14 #include <utility>
15 16 // classes for ChaCha20 256-bit stream cipher developed by Daniel J. Bernstein
17 // https://cr.yp.to/chacha/chacha-20080128.pdf.
18 //
19 // The 128-bit input is here implemented as a 96-bit nonce and a 32-bit block
20 // counter, as in RFC8439 Section 2.3. When the 32-bit block counter overflows
21 // the first 32-bit part of the nonce is automatically incremented, making it
22 // conceptually compatible with variants that use a 64/64 split instead.
23 24 /** ChaCha20 cipher that only operates on multiples of 64 bytes. */
25 class ChaCha20Aligned
26 {
27 private:
28 uint32_t input[12];
29 30 public:
31 /** Expected key length in constructor and SetKey. */
32 static constexpr unsigned KEYLEN{32};
33 34 /** Block size (inputs/outputs to Keystream / Crypt should be multiples of this). */
35 static constexpr unsigned BLOCKLEN{64};
36 37 /** For safety, disallow initialization without key. */
38 ChaCha20Aligned() noexcept = delete;
39 40 /** Initialize a cipher with specified 32-byte key. */
41 ChaCha20Aligned(Span<const std::byte> key) noexcept;
42 43 /** Destructor to clean up private memory. */
44 ~ChaCha20Aligned();
45 46 /** Set 32-byte key, and seek to nonce 0 and block position 0. */
47 void SetKey(Span<const std::byte> key) noexcept;
48 49 /** Type for 96-bit nonces used by the Set function below.
50 *
51 * The first field corresponds to the LE32-encoded first 4 bytes of the nonce, also referred
52 * to as the '32-bit fixed-common part' in Example 2.8.2 of RFC8439.
53 *
54 * The second field corresponds to the LE64-encoded last 8 bytes of the nonce.
55 *
56 */
57 using Nonce96 = std::pair<uint32_t, uint64_t>;
58 59 /** Set the 96-bit nonce and 32-bit block counter.
60 *
61 * Block_counter selects a position to seek to (to byte BLOCKLEN*block_counter). After 256 GiB,
62 * the block counter overflows, and nonce.first is incremented.
63 */
64 void Seek(Nonce96 nonce, uint32_t block_counter) noexcept;
65 66 /** outputs the keystream into out, whose length must be a multiple of BLOCKLEN. */
67 void Keystream(Span<std::byte> out) noexcept;
68 69 /** en/deciphers the message <input> and write the result into <output>
70 *
71 * The size of input and output must be equal, and be a multiple of BLOCKLEN.
72 */
73 void Crypt(Span<const std::byte> input, Span<std::byte> output) noexcept;
74 };
75 76 /** Unrestricted ChaCha20 cipher. */
77 class ChaCha20
78 {
79 private:
80 ChaCha20Aligned m_aligned;
81 std::array<std::byte, ChaCha20Aligned::BLOCKLEN> m_buffer;
82 unsigned m_bufleft{0};
83 84 public:
85 /** Expected key length in constructor and SetKey. */
86 static constexpr unsigned KEYLEN = ChaCha20Aligned::KEYLEN;
87 88 /** For safety, disallow initialization without key. */
89 ChaCha20() noexcept = delete;
90 91 /** Initialize a cipher with specified 32-byte key. */
92 ChaCha20(Span<const std::byte> key) noexcept : m_aligned(key) {}
93 94 /** Destructor to clean up private memory. */
95 ~ChaCha20();
96 97 /** Set 32-byte key, and seek to nonce 0 and block position 0. */
98 void SetKey(Span<const std::byte> key) noexcept;
99 100 /** 96-bit nonce type. */
101 using Nonce96 = ChaCha20Aligned::Nonce96;
102 103 /** Set the 96-bit nonce and 32-bit block counter. See ChaCha20Aligned::Seek. */
104 void Seek(Nonce96 nonce, uint32_t block_counter) noexcept
105 {
106 m_aligned.Seek(nonce, block_counter);
107 m_bufleft = 0;
108 }
109 110 /** en/deciphers the message <in_bytes> and write the result into <out_bytes>
111 *
112 * The size of in_bytes and out_bytes must be equal.
113 */
114 void Crypt(Span<const std::byte> in_bytes, Span<std::byte> out_bytes) noexcept;
115 116 /** outputs the keystream to out. */
117 void Keystream(Span<std::byte> out) noexcept;
118 };
119 120 /** Forward-secure ChaCha20
121 *
122 * This implements a stream cipher that automatically transitions to a new stream with a new key
123 * and new nonce after a predefined number of encryptions or decryptions.
124 *
125 * See BIP324 for details.
126 */
127 class FSChaCha20
128 {
129 private:
130 /** Internal stream cipher. */
131 ChaCha20 m_chacha20;
132 133 /** The number of encryptions/decryptions before a rekey happens. */
134 const uint32_t m_rekey_interval;
135 136 /** The number of encryptions/decryptions since the last rekey. */
137 uint32_t m_chunk_counter{0};
138 139 /** The number of rekey operations that have happened. */
140 uint64_t m_rekey_counter{0};
141 142 public:
143 /** Length of keys expected by the constructor. */
144 static constexpr unsigned KEYLEN = 32;
145 146 // No copy or move to protect the secret.
147 FSChaCha20(const FSChaCha20&) = delete;
148 FSChaCha20(FSChaCha20&&) = delete;
149 FSChaCha20& operator=(const FSChaCha20&) = delete;
150 FSChaCha20& operator=(FSChaCha20&&) = delete;
151 152 /** Construct an FSChaCha20 cipher that rekeys every rekey_interval Crypt() calls. */
153 FSChaCha20(Span<const std::byte> key, uint32_t rekey_interval) noexcept;
154 155 /** Encrypt or decrypt a chunk. */
156 void Crypt(Span<const std::byte> input, Span<std::byte> output) noexcept;
157 };
158 159 #endif // LIMENKA_CRYPTO_CHACHA20_H
160