Branch data Line data Source code
1 : : // Copyright (c) 2017-2022 The Bitcoin Core 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 : : // Based on the public domain implementation 'merged' by D. J. Bernstein
6 : : // See https://cr.yp.to/chacha.html.
7 : :
8 : : #include <crypto/common.h>
9 : : #include <crypto/chacha20.h>
10 : : #include <support/cleanse.h>
11 : : #include <span.h>
12 : :
13 : : #include <algorithm>
14 : : #include <bit>
15 : : #include <string.h>
16 : :
17 : : #define QUARTERROUND(a,b,c,d) \
18 : : a += b; d = std::rotl(d ^ a, 16); \
19 : : c += d; b = std::rotl(b ^ c, 12); \
20 : : a += b; d = std::rotl(d ^ a, 8); \
21 : : c += d; b = std::rotl(b ^ c, 7);
22 : :
23 : : #define REPEAT10(a) do { {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; } while(0)
24 : :
25 : 811212 : void ChaCha20Aligned::SetKey(Span<const std::byte> key) noexcept
26 : : {
27 [ - + ]: 811212 : assert(key.size() == KEYLEN);
28 : 811212 : input[0] = ReadLE32(UCharCast(key.data() + 0));
29 : 811212 : input[1] = ReadLE32(UCharCast(key.data() + 4));
30 : 811212 : input[2] = ReadLE32(UCharCast(key.data() + 8));
31 : 811212 : input[3] = ReadLE32(UCharCast(key.data() + 12));
32 : 811212 : input[4] = ReadLE32(UCharCast(key.data() + 16));
33 : 811212 : input[5] = ReadLE32(UCharCast(key.data() + 20));
34 : 811212 : input[6] = ReadLE32(UCharCast(key.data() + 24));
35 : 811212 : input[7] = ReadLE32(UCharCast(key.data() + 28));
36 : 811212 : input[8] = 0;
37 : 811212 : input[9] = 0;
38 : 811212 : input[10] = 0;
39 : 811212 : input[11] = 0;
40 : 811212 : }
41 : :
42 : 425649 : ChaCha20Aligned::~ChaCha20Aligned()
43 : : {
44 : 425649 : memory_cleanse(input, sizeof(input));
45 : 425649 : }
46 : :
47 : 425647 : ChaCha20Aligned::ChaCha20Aligned(Span<const std::byte> key) noexcept
48 : : {
49 : 425647 : SetKey(key);
50 : 425647 : }
51 : :
52 : 2077734 : void ChaCha20Aligned::Seek(Nonce96 nonce, uint32_t block_counter) noexcept
53 : : {
54 : 2077734 : input[8] = block_counter;
55 : 2077734 : input[9] = nonce.first;
56 : 2077734 : input[10] = nonce.second;
57 : 2077734 : input[11] = nonce.second >> 32;
58 : 2077734 : }
59 : :
60 : 6475459 : inline void ChaCha20Aligned::Keystream(Span<std::byte> output) noexcept
61 : : {
62 [ - + ]: 6475459 : unsigned char* c = UCharCast(output.data());
63 [ - + ]: 6475459 : size_t blocks = output.size() / BLOCKLEN;
64 [ - + ]: 6475459 : assert(blocks * BLOCKLEN == output.size());
65 : :
66 : 6475459 : uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
67 : 6475459 : uint32_t j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
68 : :
69 [ + - ]: 6475459 : if (!blocks) return;
70 : :
71 : 6475459 : j4 = input[0];
72 : 6475459 : j5 = input[1];
73 : 6475459 : j6 = input[2];
74 : 6475459 : j7 = input[3];
75 : 6475459 : j8 = input[4];
76 : 6475459 : j9 = input[5];
77 : 6475459 : j10 = input[6];
78 : 6475459 : j11 = input[7];
79 : 6475459 : j12 = input[8];
80 : 6475459 : j13 = input[9];
81 : 6475459 : j14 = input[10];
82 : 6475459 : j15 = input[11];
83 : :
84 : 11507447 : for (;;) {
85 : 8991453 : x0 = 0x61707865;
86 : 8991453 : x1 = 0x3320646e;
87 : 8991453 : x2 = 0x79622d32;
88 : 8991453 : x3 = 0x6b206574;
89 : 8991453 : x4 = j4;
90 : 8991453 : x5 = j5;
91 : 8991453 : x6 = j6;
92 : 8991453 : x7 = j7;
93 : 8991453 : x8 = j8;
94 : 8991453 : x9 = j9;
95 : 8991453 : x10 = j10;
96 : 8991453 : x11 = j11;
97 : 8991453 : x12 = j12;
98 : 8991453 : x13 = j13;
99 : 8991453 : x14 = j14;
100 : 8991453 : x15 = j15;
101 : :
102 : : // The 20 inner ChaCha20 rounds are unrolled here for performance.
103 [ + + ]: 8991453 : REPEAT10(
104 : : QUARTERROUND( x0, x4, x8,x12);
105 : : QUARTERROUND( x1, x5, x9,x13);
106 : : QUARTERROUND( x2, x6,x10,x14);
107 : : QUARTERROUND( x3, x7,x11,x15);
108 : : QUARTERROUND( x0, x5,x10,x15);
109 : : QUARTERROUND( x1, x6,x11,x12);
110 : : QUARTERROUND( x2, x7, x8,x13);
111 : : QUARTERROUND( x3, x4, x9,x14);
112 : : );
113 : :
114 : 8991453 : x0 += 0x61707865;
115 : 8991453 : x1 += 0x3320646e;
116 : 8991453 : x2 += 0x79622d32;
117 : 8991453 : x3 += 0x6b206574;
118 : 8991453 : x4 += j4;
119 : 8991453 : x5 += j5;
120 : 8991453 : x6 += j6;
121 : 8991453 : x7 += j7;
122 : 8991453 : x8 += j8;
123 : 8991453 : x9 += j9;
124 : 8991453 : x10 += j10;
125 : 8991453 : x11 += j11;
126 : 8991453 : x12 += j12;
127 : 8991453 : x13 += j13;
128 : 8991453 : x14 += j14;
129 : 8991453 : x15 += j15;
130 : :
131 : 8991453 : ++j12;
132 [ + + ]: 8991453 : if (!j12) ++j13;
133 : :
134 : 8991453 : WriteLE32(c + 0, x0);
135 : 8991453 : WriteLE32(c + 4, x1);
136 : 8991453 : WriteLE32(c + 8, x2);
137 : 8991453 : WriteLE32(c + 12, x3);
138 : 8991453 : WriteLE32(c + 16, x4);
139 : 8991453 : WriteLE32(c + 20, x5);
140 : 8991453 : WriteLE32(c + 24, x6);
141 : 8991453 : WriteLE32(c + 28, x7);
142 : 8991453 : WriteLE32(c + 32, x8);
143 : 8991453 : WriteLE32(c + 36, x9);
144 : 8991453 : WriteLE32(c + 40, x10);
145 : 8991453 : WriteLE32(c + 44, x11);
146 : 8991453 : WriteLE32(c + 48, x12);
147 : 8991453 : WriteLE32(c + 52, x13);
148 : 8991453 : WriteLE32(c + 56, x14);
149 : 8991453 : WriteLE32(c + 60, x15);
150 : :
151 [ + + ]: 8991453 : if (blocks == 1) {
152 : 6475459 : input[8] = j12;
153 : 6475459 : input[9] = j13;
154 : 6475459 : return;
155 : : }
156 : 2515994 : blocks -= 1;
157 : 2515994 : c += BLOCKLEN;
158 : : }
159 : : }
160 : :
161 : 91719 : inline void ChaCha20Aligned::Crypt(Span<const std::byte> in_bytes, Span<std::byte> out_bytes) noexcept
162 : : {
163 [ - + ]: 91719 : assert(in_bytes.size() == out_bytes.size());
164 [ - + ]: 91719 : const unsigned char* m = UCharCast(in_bytes.data());
165 : 91719 : unsigned char* c = UCharCast(out_bytes.data());
166 : 91719 : size_t blocks = out_bytes.size() / BLOCKLEN;
167 [ - + ]: 91719 : assert(blocks * BLOCKLEN == out_bytes.size());
168 : :
169 : 91719 : uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
170 : 91719 : uint32_t j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
171 : :
172 [ + - ]: 91719 : if (!blocks) return;
173 : :
174 : 91719 : j4 = input[0];
175 : 91719 : j5 = input[1];
176 : 91719 : j6 = input[2];
177 : 91719 : j7 = input[3];
178 : 91719 : j8 = input[4];
179 : 91719 : j9 = input[5];
180 : 91719 : j10 = input[6];
181 : 91719 : j11 = input[7];
182 : 91719 : j12 = input[8];
183 : 91719 : j13 = input[9];
184 : 91719 : j14 = input[10];
185 : 91719 : j15 = input[11];
186 : :
187 : 10100977 : for (;;) {
188 : 5096348 : x0 = 0x61707865;
189 : 5096348 : x1 = 0x3320646e;
190 : 5096348 : x2 = 0x79622d32;
191 : 5096348 : x3 = 0x6b206574;
192 : 5096348 : x4 = j4;
193 : 5096348 : x5 = j5;
194 : 5096348 : x6 = j6;
195 : 5096348 : x7 = j7;
196 : 5096348 : x8 = j8;
197 : 5096348 : x9 = j9;
198 : 5096348 : x10 = j10;
199 : 5096348 : x11 = j11;
200 : 5096348 : x12 = j12;
201 : 5096348 : x13 = j13;
202 : 5096348 : x14 = j14;
203 : 5096348 : x15 = j15;
204 : :
205 : : // The 20 inner ChaCha20 rounds are unrolled here for performance.
206 [ - + ]: 5096348 : REPEAT10(
207 : : QUARTERROUND( x0, x4, x8,x12);
208 : : QUARTERROUND( x1, x5, x9,x13);
209 : : QUARTERROUND( x2, x6,x10,x14);
210 : : QUARTERROUND( x3, x7,x11,x15);
211 : : QUARTERROUND( x0, x5,x10,x15);
212 : : QUARTERROUND( x1, x6,x11,x12);
213 : : QUARTERROUND( x2, x7, x8,x13);
214 : : QUARTERROUND( x3, x4, x9,x14);
215 : : );
216 : :
217 : 5096348 : x0 += 0x61707865;
218 : 5096348 : x1 += 0x3320646e;
219 : 5096348 : x2 += 0x79622d32;
220 : 5096348 : x3 += 0x6b206574;
221 : 5096348 : x4 += j4;
222 : 5096348 : x5 += j5;
223 : 5096348 : x6 += j6;
224 : 5096348 : x7 += j7;
225 : 5096348 : x8 += j8;
226 : 5096348 : x9 += j9;
227 : 5096348 : x10 += j10;
228 : 5096348 : x11 += j11;
229 : 5096348 : x12 += j12;
230 : 5096348 : x13 += j13;
231 : 5096348 : x14 += j14;
232 : 5096348 : x15 += j15;
233 : :
234 [ - + ]: 5096348 : x0 ^= ReadLE32(m + 0);
235 : 5096348 : x1 ^= ReadLE32(m + 4);
236 : 5096348 : x2 ^= ReadLE32(m + 8);
237 : 5096348 : x3 ^= ReadLE32(m + 12);
238 : 5096348 : x4 ^= ReadLE32(m + 16);
239 : 5096348 : x5 ^= ReadLE32(m + 20);
240 : 5096348 : x6 ^= ReadLE32(m + 24);
241 : 5096348 : x7 ^= ReadLE32(m + 28);
242 : 5096348 : x8 ^= ReadLE32(m + 32);
243 : 5096348 : x9 ^= ReadLE32(m + 36);
244 : 5096348 : x10 ^= ReadLE32(m + 40);
245 : 5096348 : x11 ^= ReadLE32(m + 44);
246 : 5096348 : x12 ^= ReadLE32(m + 48);
247 : 5096348 : x13 ^= ReadLE32(m + 52);
248 : 5096348 : x14 ^= ReadLE32(m + 56);
249 : 5096348 : x15 ^= ReadLE32(m + 60);
250 : :
251 : 5096348 : ++j12;
252 [ - + ]: 5096348 : if (!j12) ++j13;
253 : :
254 : 5096348 : WriteLE32(c + 0, x0);
255 : 5096348 : WriteLE32(c + 4, x1);
256 : 5096348 : WriteLE32(c + 8, x2);
257 : 5096348 : WriteLE32(c + 12, x3);
258 : 5096348 : WriteLE32(c + 16, x4);
259 : 5096348 : WriteLE32(c + 20, x5);
260 : 5096348 : WriteLE32(c + 24, x6);
261 : 5096348 : WriteLE32(c + 28, x7);
262 : 5096348 : WriteLE32(c + 32, x8);
263 : 5096348 : WriteLE32(c + 36, x9);
264 : 5096348 : WriteLE32(c + 40, x10);
265 : 5096348 : WriteLE32(c + 44, x11);
266 : 5096348 : WriteLE32(c + 48, x12);
267 : 5096348 : WriteLE32(c + 52, x13);
268 : 5096348 : WriteLE32(c + 56, x14);
269 : 5096348 : WriteLE32(c + 60, x15);
270 : :
271 [ + + ]: 5096348 : if (blocks == 1) {
272 : 91719 : input[8] = j12;
273 : 91719 : input[9] = j13;
274 : 91719 : return;
275 : : }
276 : 5004629 : blocks -= 1;
277 : 5004629 : c += BLOCKLEN;
278 : 5004629 : m += BLOCKLEN;
279 : : }
280 : : }
281 : :
282 : 32212982 : void ChaCha20::Keystream(Span<std::byte> out) noexcept
283 : : {
284 [ + + ]: 32212982 : if (out.empty()) return;
285 [ + + ]: 32212735 : if (m_bufleft) {
286 [ + + ]: 26506998 : unsigned reuse = std::min<size_t>(m_bufleft, out.size());
287 : 26506998 : std::copy(m_buffer.end() - m_bufleft, m_buffer.end() - m_bufleft + reuse, out.begin());
288 : 26506998 : m_bufleft -= reuse;
289 : 26506998 : out = out.subspan(reuse);
290 : : }
291 [ + + ]: 32212735 : if (out.size() >= m_aligned.BLOCKLEN) {
292 : 1463436 : size_t blocks = out.size() / m_aligned.BLOCKLEN;
293 : 1463436 : m_aligned.Keystream(out.first(blocks * m_aligned.BLOCKLEN));
294 : 1463436 : out = out.subspan(blocks * m_aligned.BLOCKLEN);
295 : : }
296 [ + + ]: 32212735 : if (!out.empty()) {
297 : 4750723 : m_aligned.Keystream(m_buffer);
298 : 4750723 : std::copy(m_buffer.begin(), m_buffer.begin() + out.size(), out.begin());
299 : 4750723 : m_bufleft = m_aligned.BLOCKLEN - out.size();
300 : : }
301 : : }
302 : :
303 : 1717223 : void ChaCha20::Crypt(Span<const std::byte> input, Span<std::byte> output) noexcept
304 : : {
305 [ - + ]: 1717223 : assert(input.size() == output.size());
306 : :
307 [ + + ]: 1717223 : if (!input.size()) return;
308 [ + + ]: 462786 : if (m_bufleft) {
309 [ + + ]: 304930 : unsigned reuse = std::min<size_t>(m_bufleft, input.size());
310 [ + + ]: 7424468 : for (unsigned i = 0; i < reuse; i++) {
311 : 7119538 : output[i] = input[i] ^ m_buffer[m_aligned.BLOCKLEN - m_bufleft + i];
312 : : }
313 : 304930 : m_bufleft -= reuse;
314 : 304930 : output = output.subspan(reuse);
315 : 304930 : input = input.subspan(reuse);
316 : : }
317 [ + + ]: 462786 : if (input.size() >= m_aligned.BLOCKLEN) {
318 : 91719 : size_t blocks = input.size() / m_aligned.BLOCKLEN;
319 : 91719 : m_aligned.Crypt(input.first(blocks * m_aligned.BLOCKLEN), output.first(blocks * m_aligned.BLOCKLEN));
320 : 91719 : output = output.subspan(blocks * m_aligned.BLOCKLEN);
321 : 91719 : input = input.subspan(blocks * m_aligned.BLOCKLEN);
322 : : }
323 [ + + ]: 462786 : if (!input.empty()) {
324 : 260910 : m_aligned.Keystream(m_buffer);
325 [ + + ]: 3623525 : for (unsigned i = 0; i < input.size(); i++) {
326 : 3362615 : output[i] = input[i] ^ m_buffer[i];
327 : : }
328 : 260910 : m_bufleft = m_aligned.BLOCKLEN - input.size();
329 : : }
330 : : }
331 : :
332 : 425259 : ChaCha20::~ChaCha20()
333 : : {
334 : 425259 : memory_cleanse(m_buffer.data(), m_buffer.size());
335 : 425259 : }
336 : :
337 : 385565 : void ChaCha20::SetKey(Span<const std::byte> key) noexcept
338 : : {
339 : 385565 : m_aligned.SetKey(key);
340 : 385565 : m_bufleft = 0;
341 : 385565 : memory_cleanse(m_buffer.data(), m_buffer.size());
342 : 385565 : }
343 : :
344 : 491 : FSChaCha20::FSChaCha20(Span<const std::byte> key, uint32_t rekey_interval) noexcept :
345 : 491 : m_chacha20(key), m_rekey_interval(rekey_interval)
346 : : {
347 [ - + ]: 491 : assert(key.size() == KEYLEN);
348 : 491 : }
349 : :
350 : 203421 : void FSChaCha20::Crypt(Span<const std::byte> input, Span<std::byte> output) noexcept
351 : : {
352 [ - + ]: 203421 : assert(input.size() == output.size());
353 : :
354 : : // Invoke internal stream cipher for actual encryption/decryption.
355 : 203421 : m_chacha20.Crypt(input, output);
356 : :
357 : : // Rekey after m_rekey_interval encryptions/decryptions.
358 [ + + ]: 203421 : if (++m_chunk_counter == m_rekey_interval) {
359 : : // Get new key from the stream cipher.
360 : 826 : std::byte new_key[KEYLEN];
361 : 826 : m_chacha20.Keystream(new_key);
362 : : // Update its key.
363 : 826 : m_chacha20.SetKey(new_key);
364 : : // Wipe the key (a copy remains inside m_chacha20, where it'll be wiped on the next rekey
365 : : // or on destruction).
366 : 826 : memory_cleanse(new_key, sizeof(new_key));
367 : : // Set the nonce for the new section of output.
368 : 826 : m_chacha20.Seek({0, ++m_rekey_counter}, 0);
369 : : // Reset the chunk counter.
370 : 826 : m_chunk_counter = 0;
371 : : }
372 : 203421 : }
|