1 /** 2 * ChaCha20 3 * 4 * Copyright: 5 * (C) 2014 Jack Lloyd 6 * (C) 2014-2015 Etienne Cimon 7 * 8 * License: 9 * Botan is released under the Simplified BSD License (see LICENSE.md) 10 */ 11 module botan.stream.chacha; 12 13 import botan.constants; 14 static if (BOTAN_HAS_CHACHA): 15 16 import botan.stream.stream_cipher; 17 import botan.utils.loadstor; 18 import botan.utils.rotate; 19 import botan.utils.xor_buf; 20 import botan.utils.types; 21 import botan.utils.mem_ops; 22 import std.format : format; 23 24 /** 25 * DJB's ChaCha (http://cr.yp.to/chacha.html) 26 */ 27 final class ChaCha : StreamCipher, SymmetricAlgorithm 28 { 29 public: 30 this(size_t rounds) { 31 m_rounds = rounds; 32 if (m_rounds != 8 && m_rounds != 12 && m_rounds != 20) 33 throw new InvalidArgument("ChaCha only supports 8, 12 or 20 rounds"); 34 } 35 36 /* 37 * Combine cipher stream with message 38 */ 39 override void cipher(const(ubyte)* input, ubyte* output, size_t length) 40 { 41 while (length >= m_buffer.length - m_position) 42 { 43 xorBuf(output, input, m_buffer.ptr + m_position, m_buffer.length - m_position); 44 length -= (m_buffer.length - m_position); 45 input += (m_buffer.length - m_position); 46 output += (m_buffer.length - m_position); 47 chacha(*cast(ubyte[64]*) m_buffer.ptr, *cast(uint[16]*) m_state.ptr, m_rounds); 48 49 ++m_state[12]; 50 m_state[13] += (m_state[12] == 0); 51 52 m_position = 0; 53 } 54 55 xorBuf(output, input, &m_buffer[m_position], length); 56 57 m_position += length; 58 } 59 60 /* 61 * Return the name of this type 62 */ 63 override void setIv(const(ubyte)* iv, size_t length) 64 { 65 if (!validIvLength(length)) 66 throw new InvalidIVLength(name, length); 67 68 m_state[12] = 0; 69 70 m_state[13] = 0; 71 72 if (length == 8) { 73 m_state[14] = loadLittleEndian!uint(iv, 0); 74 m_state[15] = loadLittleEndian!uint(iv, 1); 75 } else if (length == 12) { 76 m_state[13] = loadLittleEndian!uint(iv, 0); 77 m_state[14] = loadLittleEndian!uint(iv, 1); 78 m_state[15] = loadLittleEndian!uint(iv, 2); 79 } 80 81 chacha(*cast(ubyte[64]*) m_buffer.ptr, *cast(uint[16]*) m_state.ptr, m_rounds); 82 ++m_state[12]; 83 m_state[13] += (m_state[12] == 0); 84 85 m_position = 0; 86 } 87 88 override bool validIvLength(size_t iv_len) const 89 { return (iv_len == 8 || iv_len == 12); } 90 91 KeyLengthSpecification keySpec() const 92 { 93 return KeyLengthSpecification(16, 32, 16); 94 } 95 96 /* 97 * Clear memory of sensitive data 98 */ 99 void clear() 100 { 101 zap(m_state); 102 zap(m_buffer); 103 m_position = 0; 104 } 105 106 /* 107 * Return the name of this type 108 */ 109 @property string name() const 110 { 111 return "ChaCha(" ~ m_rounds.to!string ~ ")"; 112 } 113 114 override StreamCipher clone() const { return new ChaCha(m_rounds); } 115 116 117 protected: 118 /* 119 * ChaCha Key Schedule 120 */ 121 override void keySchedule(const(ubyte)* key, size_t length) 122 { 123 __gshared immutable uint[] TAU = [ 0x61707865, 0x3120646e, 0x79622d36, 0x6b206574 ]; 124 125 __gshared immutable uint[] SIGMA = [ 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574 ]; 126 127 const uint[] CONSTANTS = (length == 16) ? TAU : SIGMA; 128 129 m_state.resize(16); 130 m_buffer.resize(64); 131 132 m_state[0] = CONSTANTS[0]; 133 m_state[1] = CONSTANTS[1]; 134 m_state[2] = CONSTANTS[2]; 135 m_state[3] = CONSTANTS[3]; 136 137 m_state[4] = loadLittleEndian!uint(key, 0); 138 m_state[5] = loadLittleEndian!uint(key, 1); 139 m_state[6] = loadLittleEndian!uint(key, 2); 140 m_state[7] = loadLittleEndian!uint(key, 3); 141 142 if (length == 32) 143 key += 16; 144 145 m_state[8] = loadLittleEndian!uint(key, 0); 146 m_state[9] = loadLittleEndian!uint(key, 1); 147 m_state[10] = loadLittleEndian!uint(key, 2); 148 m_state[11] = loadLittleEndian!uint(key, 3); 149 150 m_position = 0; 151 152 const ubyte[8] ZERO; 153 setIv(ZERO.ptr, ZERO.length); 154 } 155 156 SecureVector!uint m_state; 157 SecureVector!ubyte m_buffer; 158 size_t m_position = 0; 159 size_t m_rounds; 160 } 161 162 enum string CHACHA_QUARTER_ROUND(alias _a, alias _b, alias _c, alias _d) = q{ 163 %1$s += %2$s; %4$s ^= %1$s; %4$s = rotateLeft(%4$s, 16); 164 %3$s += %4$s; %2$s ^= %3$s; %2$s = rotateLeft(%2$s, 12); 165 %1$s += %2$s; %4$s ^= %1$s; %4$s = rotateLeft(%4$s, 8); 166 %3$s += %4$s; %2$s ^= %3$s; %2$s = rotateLeft(%2$s, 7); 167 }.format(__traits(identifier, _a), __traits(identifier, _b), __traits(identifier, _c), __traits(identifier, _d)); 168 169 private void chacha(ref ubyte[64] output, in uint[16] input, size_t rounds) 170 { 171 assert(rounds % 2 == 0, "Valid rounds"); 172 uint x00 = input[ 0], x01 = input[ 1], x02 = input[ 2], x03 = input[ 3], 173 x04 = input[ 4], x05 = input[ 5], x06 = input[ 6], x07 = input[ 7], 174 x08 = input[ 8], x09 = input[ 9], x10 = input[10], x11 = input[11], 175 x12 = input[12], x13 = input[13], x14 = input[14], x15 = input[15]; 176 177 178 foreach (size_t i; 0 .. rounds/2) 179 { 180 mixin(CHACHA_QUARTER_ROUND!(x00, x04, x08, x12) ~ 181 CHACHA_QUARTER_ROUND!(x01, x05, x09, x13) ~ 182 CHACHA_QUARTER_ROUND!(x02, x06, x10, x14) ~ 183 CHACHA_QUARTER_ROUND!(x03, x07, x11, x15) ~ 184 185 CHACHA_QUARTER_ROUND!(x00, x05, x10, x15) ~ 186 CHACHA_QUARTER_ROUND!(x01, x06, x11, x12) ~ 187 CHACHA_QUARTER_ROUND!(x02, x07, x08, x13) ~ 188 CHACHA_QUARTER_ROUND!(x03, x04, x09, x14) 189 ); 190 } 191 192 storeLittleEndian(x00 + input[ 0], output.ptr + 4 * 0); 193 storeLittleEndian(x01 + input[ 1], output.ptr + 4 * 1); 194 storeLittleEndian(x02 + input[ 2], output.ptr + 4 * 2); 195 storeLittleEndian(x03 + input[ 3], output.ptr + 4 * 3); 196 storeLittleEndian(x04 + input[ 4], output.ptr + 4 * 4); 197 storeLittleEndian(x05 + input[ 5], output.ptr + 4 * 5); 198 storeLittleEndian(x06 + input[ 6], output.ptr + 4 * 6); 199 storeLittleEndian(x07 + input[ 7], output.ptr + 4 * 7); 200 storeLittleEndian(x08 + input[ 8], output.ptr + 4 * 8); 201 storeLittleEndian(x09 + input[ 9], output.ptr + 4 * 9); 202 storeLittleEndian(x10 + input[10], output.ptr + 4 * 10); 203 storeLittleEndian(x11 + input[11], output.ptr + 4 * 11); 204 storeLittleEndian(x12 + input[12], output.ptr + 4 * 12); 205 storeLittleEndian(x13 + input[13], output.ptr + 4 * 13); 206 storeLittleEndian(x14 + input[14], output.ptr + 4 * 14); 207 storeLittleEndian(x15 + input[15], output.ptr + 4 * 15); 208 }