ChaCha20.cpp 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. /*
  2. * Copyright (c) 2022, stelar7 <dudedbz@gmail.com>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/ByteReader.h>
  7. #include <AK/Endian.h>
  8. #include <LibCrypto/Cipher/ChaCha20.h>
  9. namespace Crypto::Cipher {
  10. ChaCha20::ChaCha20(ReadonlyBytes key, ReadonlyBytes nonce, u32 initial_counter)
  11. {
  12. VERIFY(key.size() == 16 || key.size() == 32);
  13. VERIFY(nonce.size() == 8 || nonce.size() == 12);
  14. // The first four words (0-3) are constants
  15. if (key.size() == 32) {
  16. m_state[0] = CONSTANT_32_BYTES[0];
  17. m_state[1] = CONSTANT_32_BYTES[1];
  18. m_state[2] = CONSTANT_32_BYTES[2];
  19. m_state[3] = CONSTANT_32_BYTES[3];
  20. } else {
  21. m_state[0] = CONSTANT_16_BYTES[0];
  22. m_state[1] = CONSTANT_16_BYTES[1];
  23. m_state[2] = CONSTANT_16_BYTES[2];
  24. m_state[3] = CONSTANT_16_BYTES[3];
  25. }
  26. // The next eight words (4-11) are taken from the key by reading the bytes in little-endian order, in 4-byte chunks.
  27. for (u32 i = 0; i < 16; i += 4) {
  28. m_state[(i / 4) + 4] = AK::convert_between_host_and_little_endian(ByteReader::load32(key.offset(i)));
  29. }
  30. // NOTE: For the 128-bit keys we read the same bytes twice to fill the state
  31. u32 key_offset = key.size() == 32 ? 16 : 0;
  32. for (u32 i = 0; i < 16; i += 4) {
  33. m_state[(i / 4) + 8] = AK::convert_between_host_and_little_endian(ByteReader::load32(key.offset(key_offset + i)));
  34. }
  35. // Word 12 is a block counter. Since each block is 64-bytes, a 32-bit word is enough for 256 gigabytes of data.
  36. m_state[12] = initial_counter;
  37. // Words 13-15 are a nonce, which should not be repeated for the same key.
  38. // The 13th word is the first 32 bits of the input nonce taken as a little-endian integer,
  39. // while the 15th word is the last 32 bits.
  40. // NOTE: In the case of an 8-byte nonce, we skip the 13th word
  41. u32 nonce_offset = nonce.size() == 8 ? 1 : 0;
  42. for (u32 i = 0; i < 12; i += 4) {
  43. m_state[(i / 4) + 13 + nonce_offset] = AK::convert_between_host_and_little_endian(ByteReader::load32(nonce.offset(i)));
  44. }
  45. }
  46. // https://datatracker.ietf.org/doc/html/rfc7539#section-2.3
  47. void ChaCha20::generate_block()
  48. {
  49. // Copy the current state into the block
  50. memcpy(m_block, m_state, 16 * sizeof(u32));
  51. // ChaCha20 runs 20 rounds, alternating between "column rounds" and "diagonal rounds".
  52. // Each round consists of four quarter-rounds
  53. for (u32 i = 0; i < 20; i += 2) {
  54. // Column rounds
  55. do_quarter_round(m_block[0], m_block[4], m_block[8], m_block[12]);
  56. do_quarter_round(m_block[1], m_block[5], m_block[9], m_block[13]);
  57. do_quarter_round(m_block[2], m_block[6], m_block[10], m_block[14]);
  58. do_quarter_round(m_block[3], m_block[7], m_block[11], m_block[15]);
  59. // Diagonal rounds
  60. do_quarter_round(m_block[0], m_block[5], m_block[10], m_block[15]);
  61. do_quarter_round(m_block[1], m_block[6], m_block[11], m_block[12]);
  62. do_quarter_round(m_block[2], m_block[7], m_block[8], m_block[13]);
  63. do_quarter_round(m_block[3], m_block[4], m_block[9], m_block[14]);
  64. }
  65. // At the end of 20 rounds, we add the original input words to the output words,
  66. for (u32 i = 0; i < 16; i++) {
  67. m_block[i] += m_state[i];
  68. }
  69. // and serialize the result by sequencing the words one-by-one in little-endian order.
  70. for (u32 i = 0; i < 16; i++) {
  71. m_block[i] = AK::convert_between_host_and_little_endian(m_block[i]);
  72. }
  73. }
  74. ALWAYS_INLINE static void rotl(u32& x, u32 n)
  75. {
  76. x = (x << n) | (x >> (32 - n));
  77. }
  78. // https://datatracker.ietf.org/doc/html/rfc8439#section-2.1
  79. void ChaCha20::do_quarter_round(u32& a, u32& b, u32& c, u32& d)
  80. {
  81. a += b;
  82. d ^= a;
  83. rotl(d, 16);
  84. c += d;
  85. b ^= c;
  86. rotl(b, 12);
  87. a += b;
  88. d ^= a;
  89. rotl(d, 8);
  90. c += d;
  91. b ^= c;
  92. rotl(b, 7);
  93. }
  94. void ChaCha20::run_cipher(ReadonlyBytes input, Bytes& output)
  95. {
  96. size_t offset = 0;
  97. size_t block_offset = 0;
  98. while (offset < input.size()) {
  99. if (block_offset == 0 || block_offset >= 64) {
  100. // Generate a new XOR block
  101. generate_block();
  102. // Increment the block counter, and carry over to block 13
  103. m_state[12]++;
  104. if (m_state[12] == 0) {
  105. m_state[13]++;
  106. }
  107. block_offset = 0;
  108. }
  109. // XOR the input and the current block
  110. u32 n = min(input.size() - offset, 64 - block_offset);
  111. u8* key_block = (u8*)m_block + block_offset;
  112. for (u32 i = 0; i < n; i++) {
  113. u8 input_byte = input.offset_pointer(offset)[i];
  114. u8 key_byte = key_block[i];
  115. u8 output_byte = input_byte ^ key_byte;
  116. ByteReader::store(output.offset_pointer(offset + i), output_byte);
  117. }
  118. offset += n;
  119. block_offset += n;
  120. }
  121. }
  122. void ChaCha20::encrypt(ReadonlyBytes input, Bytes& output)
  123. {
  124. VERIFY(input.size() <= output.size());
  125. this->run_cipher(input, output);
  126. }
  127. void ChaCha20::decrypt(ReadonlyBytes input, Bytes& output)
  128. {
  129. VERIFY(input.size() <= output.size());
  130. this->run_cipher(input, output);
  131. }
  132. }