chacha20.cpp raw

   1  // Copyright (c) 2017-present 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  // 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  void ChaCha20Aligned::SetKey(Span<const std::byte> key) noexcept
  26  {
  27      assert(key.size() == KEYLEN);
  28      input[0] = ReadLE32(key.data() + 0);
  29      input[1] = ReadLE32(key.data() + 4);
  30      input[2] = ReadLE32(key.data() + 8);
  31      input[3] = ReadLE32(key.data() + 12);
  32      input[4] = ReadLE32(key.data() + 16);
  33      input[5] = ReadLE32(key.data() + 20);
  34      input[6] = ReadLE32(key.data() + 24);
  35      input[7] = ReadLE32(key.data() + 28);
  36      input[8] = 0;
  37      input[9] = 0;
  38      input[10] = 0;
  39      input[11] = 0;
  40  }
  41  
  42  ChaCha20Aligned::~ChaCha20Aligned()
  43  {
  44      memory_cleanse(input, sizeof(input));
  45  }
  46  
  47  ChaCha20Aligned::ChaCha20Aligned(Span<const std::byte> key) noexcept
  48  {
  49      SetKey(key);
  50  }
  51  
  52  void ChaCha20Aligned::Seek(Nonce96 nonce, uint32_t block_counter) noexcept
  53  {
  54      input[8] = block_counter;
  55      input[9] = nonce.first;
  56      input[10] = nonce.second;
  57      input[11] = nonce.second >> 32;
  58  }
  59  
  60  inline void ChaCha20Aligned::Keystream(Span<std::byte> output) noexcept
  61  {
  62      std::byte* c = output.data();
  63      size_t blocks = output.size() / BLOCKLEN;
  64      assert(blocks * BLOCKLEN == output.size());
  65  
  66      uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
  67      uint32_t j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
  68  
  69      if (!blocks) return;
  70  
  71      j4 = input[0];
  72      j5 = input[1];
  73      j6 = input[2];
  74      j7 = input[3];
  75      j8 = input[4];
  76      j9 = input[5];
  77      j10 = input[6];
  78      j11 = input[7];
  79      j12 = input[8];
  80      j13 = input[9];
  81      j14 = input[10];
  82      j15 = input[11];
  83  
  84      for (;;) {
  85          x0 = 0x61707865;
  86          x1 = 0x3320646e;
  87          x2 = 0x79622d32;
  88          x3 = 0x6b206574;
  89          x4 = j4;
  90          x5 = j5;
  91          x6 = j6;
  92          x7 = j7;
  93          x8 = j8;
  94          x9 = j9;
  95          x10 = j10;
  96          x11 = j11;
  97          x12 = j12;
  98          x13 = j13;
  99          x14 = j14;
 100          x15 = j15;
 101  
 102          // The 20 inner ChaCha20 rounds are unrolled here for performance.
 103          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          x0 += 0x61707865;
 115          x1 += 0x3320646e;
 116          x2 += 0x79622d32;
 117          x3 += 0x6b206574;
 118          x4 += j4;
 119          x5 += j5;
 120          x6 += j6;
 121          x7 += j7;
 122          x8 += j8;
 123          x9 += j9;
 124          x10 += j10;
 125          x11 += j11;
 126          x12 += j12;
 127          x13 += j13;
 128          x14 += j14;
 129          x15 += j15;
 130  
 131          ++j12;
 132          if (!j12) ++j13;
 133  
 134          WriteLE32(c + 0, x0);
 135          WriteLE32(c + 4, x1);
 136          WriteLE32(c + 8, x2);
 137          WriteLE32(c + 12, x3);
 138          WriteLE32(c + 16, x4);
 139          WriteLE32(c + 20, x5);
 140          WriteLE32(c + 24, x6);
 141          WriteLE32(c + 28, x7);
 142          WriteLE32(c + 32, x8);
 143          WriteLE32(c + 36, x9);
 144          WriteLE32(c + 40, x10);
 145          WriteLE32(c + 44, x11);
 146          WriteLE32(c + 48, x12);
 147          WriteLE32(c + 52, x13);
 148          WriteLE32(c + 56, x14);
 149          WriteLE32(c + 60, x15);
 150  
 151          if (blocks == 1) {
 152              input[8] = j12;
 153              input[9] = j13;
 154              return;
 155          }
 156          blocks -= 1;
 157          c += BLOCKLEN;
 158      }
 159  }
 160  
 161  inline void ChaCha20Aligned::Crypt(Span<const std::byte> in_bytes, Span<std::byte> out_bytes) noexcept
 162  {
 163      assert(in_bytes.size() == out_bytes.size());
 164      const std::byte* m = in_bytes.data();
 165      std::byte* c = out_bytes.data();
 166      size_t blocks = out_bytes.size() / BLOCKLEN;
 167      assert(blocks * BLOCKLEN == out_bytes.size());
 168  
 169      uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
 170      uint32_t j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
 171  
 172      if (!blocks) return;
 173  
 174      j4 = input[0];
 175      j5 = input[1];
 176      j6 = input[2];
 177      j7 = input[3];
 178      j8 = input[4];
 179      j9 = input[5];
 180      j10 = input[6];
 181      j11 = input[7];
 182      j12 = input[8];
 183      j13 = input[9];
 184      j14 = input[10];
 185      j15 = input[11];
 186  
 187      for (;;) {
 188          x0 = 0x61707865;
 189          x1 = 0x3320646e;
 190          x2 = 0x79622d32;
 191          x3 = 0x6b206574;
 192          x4 = j4;
 193          x5 = j5;
 194          x6 = j6;
 195          x7 = j7;
 196          x8 = j8;
 197          x9 = j9;
 198          x10 = j10;
 199          x11 = j11;
 200          x12 = j12;
 201          x13 = j13;
 202          x14 = j14;
 203          x15 = j15;
 204  
 205          // The 20 inner ChaCha20 rounds are unrolled here for performance.
 206          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          x0 += 0x61707865;
 218          x1 += 0x3320646e;
 219          x2 += 0x79622d32;
 220          x3 += 0x6b206574;
 221          x4 += j4;
 222          x5 += j5;
 223          x6 += j6;
 224          x7 += j7;
 225          x8 += j8;
 226          x9 += j9;
 227          x10 += j10;
 228          x11 += j11;
 229          x12 += j12;
 230          x13 += j13;
 231          x14 += j14;
 232          x15 += j15;
 233  
 234          x0 ^= ReadLE32(m + 0);
 235          x1 ^= ReadLE32(m + 4);
 236          x2 ^= ReadLE32(m + 8);
 237          x3 ^= ReadLE32(m + 12);
 238          x4 ^= ReadLE32(m + 16);
 239          x5 ^= ReadLE32(m + 20);
 240          x6 ^= ReadLE32(m + 24);
 241          x7 ^= ReadLE32(m + 28);
 242          x8 ^= ReadLE32(m + 32);
 243          x9 ^= ReadLE32(m + 36);
 244          x10 ^= ReadLE32(m + 40);
 245          x11 ^= ReadLE32(m + 44);
 246          x12 ^= ReadLE32(m + 48);
 247          x13 ^= ReadLE32(m + 52);
 248          x14 ^= ReadLE32(m + 56);
 249          x15 ^= ReadLE32(m + 60);
 250  
 251          ++j12;
 252          if (!j12) ++j13;
 253  
 254          WriteLE32(c + 0, x0);
 255          WriteLE32(c + 4, x1);
 256          WriteLE32(c + 8, x2);
 257          WriteLE32(c + 12, x3);
 258          WriteLE32(c + 16, x4);
 259          WriteLE32(c + 20, x5);
 260          WriteLE32(c + 24, x6);
 261          WriteLE32(c + 28, x7);
 262          WriteLE32(c + 32, x8);
 263          WriteLE32(c + 36, x9);
 264          WriteLE32(c + 40, x10);
 265          WriteLE32(c + 44, x11);
 266          WriteLE32(c + 48, x12);
 267          WriteLE32(c + 52, x13);
 268          WriteLE32(c + 56, x14);
 269          WriteLE32(c + 60, x15);
 270  
 271          if (blocks == 1) {
 272              input[8] = j12;
 273              input[9] = j13;
 274              return;
 275          }
 276          blocks -= 1;
 277          c += BLOCKLEN;
 278          m += BLOCKLEN;
 279      }
 280  }
 281  
 282  void ChaCha20::Keystream(Span<std::byte> out) noexcept
 283  {
 284      if (out.empty()) return;
 285      if (m_bufleft) {
 286          unsigned reuse = std::min<size_t>(m_bufleft, out.size());
 287          std::copy(m_buffer.end() - m_bufleft, m_buffer.end() - m_bufleft + reuse, out.begin());
 288          m_bufleft -= reuse;
 289          out = out.subspan(reuse);
 290      }
 291      if (out.size() >= m_aligned.BLOCKLEN) {
 292          size_t blocks = out.size() / m_aligned.BLOCKLEN;
 293          m_aligned.Keystream(out.first(blocks * m_aligned.BLOCKLEN));
 294          out = out.subspan(blocks * m_aligned.BLOCKLEN);
 295      }
 296      if (!out.empty()) {
 297          m_aligned.Keystream(m_buffer);
 298          std::copy(m_buffer.begin(), m_buffer.begin() + out.size(), out.begin());
 299          m_bufleft = m_aligned.BLOCKLEN - out.size();
 300      }
 301  }
 302  
 303  void ChaCha20::Crypt(Span<const std::byte> input, Span<std::byte> output) noexcept
 304  {
 305      assert(input.size() == output.size());
 306  
 307      if (!input.size()) return;
 308      if (m_bufleft) {
 309          unsigned reuse = std::min<size_t>(m_bufleft, input.size());
 310          for (unsigned i = 0; i < reuse; i++) {
 311              output[i] = input[i] ^ m_buffer[m_aligned.BLOCKLEN - m_bufleft + i];
 312          }
 313          m_bufleft -= reuse;
 314          output = output.subspan(reuse);
 315          input = input.subspan(reuse);
 316      }
 317      if (input.size() >= m_aligned.BLOCKLEN) {
 318          size_t blocks = input.size() / m_aligned.BLOCKLEN;
 319          m_aligned.Crypt(input.first(blocks * m_aligned.BLOCKLEN), output.first(blocks * m_aligned.BLOCKLEN));
 320          output = output.subspan(blocks * m_aligned.BLOCKLEN);
 321          input = input.subspan(blocks * m_aligned.BLOCKLEN);
 322      }
 323      if (!input.empty()) {
 324          m_aligned.Keystream(m_buffer);
 325          for (unsigned i = 0; i < input.size(); i++) {
 326              output[i] = input[i] ^ m_buffer[i];
 327          }
 328          m_bufleft = m_aligned.BLOCKLEN - input.size();
 329      }
 330  }
 331  
 332  ChaCha20::~ChaCha20()
 333  {
 334      memory_cleanse(m_buffer.data(), m_buffer.size());
 335  }
 336  
 337  void ChaCha20::SetKey(Span<const std::byte> key) noexcept
 338  {
 339      m_aligned.SetKey(key);
 340      m_bufleft = 0;
 341      memory_cleanse(m_buffer.data(), m_buffer.size());
 342  }
 343  
 344  FSChaCha20::FSChaCha20(Span<const std::byte> key, uint32_t rekey_interval) noexcept :
 345      m_chacha20(key), m_rekey_interval(rekey_interval)
 346  {
 347      assert(key.size() == KEYLEN);
 348  }
 349  
 350  void FSChaCha20::Crypt(Span<const std::byte> input, Span<std::byte> output) noexcept
 351  {
 352      assert(input.size() == output.size());
 353  
 354      // Invoke internal stream cipher for actual encryption/decryption.
 355      m_chacha20.Crypt(input, output);
 356  
 357      // Rekey after m_rekey_interval encryptions/decryptions.
 358      if (++m_chunk_counter == m_rekey_interval) {
 359          // Get new key from the stream cipher.
 360          std::byte new_key[KEYLEN];
 361          m_chacha20.Keystream(new_key);
 362          // Update its key.
 363          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          memory_cleanse(new_key, sizeof(new_key));
 367          // Set the nonce for the new section of output.
 368          m_chacha20.Seek({0, ++m_rekey_counter}, 0);
 369          // Reset the chunk counter.
 370          m_chunk_counter = 0;
 371      }
 372  }
 373