1 module dcrypt.crypto.engines.poly1305_chacha; 2 3 /// Implementation of the Poly1305-ChaCha20 AEAD cipher. 4 /// 5 /// Standard: RFC 7539 6 7 import dcrypt.crypto.modes.aead; 8 import dcrypt.crypto.engines.chacha; 9 import dcrypt.crypto.engines.salsa; 10 import dcrypt.crypto.macs.poly1305; 11 import dcrypt.bitmanip; 12 13 static assert(isAEADCipher!Poly1305ChaCha20, Poly1305ChaCha20.name~" is not a valid AEAD cipher."); 14 15 alias AEADCipherWrapper!Poly1305ChaCha20 Poly1305ChaChaEngine; 16 17 alias Poly1305Cipher!ChaCha20 Poly1305ChaCha20; 18 alias Poly1305Cipher!Salsa20 Poly1305Salsa20; 19 //alias Poly1305Cipher!XSalsa20 Poly1305XSalsa20; 20 21 @safe 22 private template isSupportedCipher(T) 23 { 24 enum bool isSupportedCipher = 25 is(T == struct) && 26 is(typeof( 27 { 28 uint[16] block; 29 ubyte[] key, nonce; 30 T.initState(block, key, cast(uint)0, nonce); 31 T.block(block, block); 32 })); 33 } 34 35 @safe 36 private struct Poly1305Cipher(Cipher) 37 if (isSupportedCipher!Cipher) 38 { 39 40 @safe nothrow @nogc: 41 42 public enum name = Cipher.name~"-Poly1305"; 43 public enum macSize = 16; 44 45 private { 46 Poly1305Raw poly; 47 Cipher cipher; 48 49 ulong aadLength, cipherTextLength; 50 51 bool forEncryption; 52 bool aadMode; /// true: AAD can be processed. false: encrypting or decrypting, can't process AAD anymore. 53 bool initialized; 54 55 version (unittest) { 56 ubyte[32] polyKey; 57 } 58 } 59 60 /// 61 /// Params: 62 /// forEncryption = Not relevant. 63 /// key = Secret key. 32 bytes. 64 /// constant = Something like an IV (sender ID, ...) 65 /// nonce = Unique per secret key. 8 bytes. 66 public void start(bool forEncryption, in ubyte[] key, in uint constant, in ubyte[] nonce) 67 in { 68 assert(key.length == 32, name~" requires a 256 bit key."); 69 assert(nonce.length == 8, name~" requires a 64 bit nonce."); 70 } body { 71 72 // start(forEncryption, key, nonce); // Salsa20 73 74 ubyte[12] _nonce; 75 toLittleEndian(constant, _nonce[0..4]); 76 _nonce[4..12] = nonce; 77 78 start(forEncryption, key, _nonce); 79 } 80 81 /// 82 /// Params: 83 /// forEncryption = Not relevant. 84 /// key = Secret key. 32 bytes. 85 /// nonce = Unique per secret key. 12 bytes. 86 public void start(bool forEncryption, in ubyte[] key, in ubyte[] nonce) 87 in { 88 assert(key.length == 32, "ChaCha20 requires a 256 bit key."); 89 assert(nonce.length == 12, "ChaCha20 requires a 96 bit nonce."); 90 } body { 91 this.forEncryption = forEncryption; 92 93 immutable ubyte[32] _key = key; 94 immutable ubyte[12] _nonce = nonce; 95 96 version(unittest) { polyKey = poly1305KeyGen(_key, _nonce); } 97 98 poly.start(poly1305KeyGen(_key, _nonce)); 99 cipher.start(forEncryption, key, nonce); 100 101 aadMode = true; 102 aadLength = cipherTextLength = 0; 103 104 initialized = true; 105 } 106 107 public void processAADBytes(in ubyte[] aad) 108 in { 109 assert(initialized, name~" not initialized."); 110 assert(aadMode, "Must process AAD before cipher data!"); 111 } body { 112 poly.put(aad); 113 aadLength += aad.length; 114 } 115 116 public size_t processBytes(in ubyte[] input, ubyte[] output) 117 in { 118 assert(initialized, name~" not initialized."); 119 assert(output.length >= input.length, "Output buffer too small."); 120 } body { 121 122 if(aadMode) { 123 aadMode = false; // Can't process AAD after this. 124 125 // pad AAD 126 pad16(aadLength); 127 } 128 129 cipher.processBytes(input, output); 130 poly.put(output); 131 132 cipherTextLength += input.length; 133 134 return input.length; 135 } 136 137 /// Returns: The MAC value of the processed AAD and cipher data. 138 /// 139 /// Note: Must be reinitialized with `start()` after calling finish 140 public size_t finish(ubyte[] mac, ubyte[] output = null) 141 in { 142 assert(mac.length == 16, "MAC buffer must be 16 bytes."); 143 } body { 144 145 if(aadMode) { 146 pad16(aadLength); 147 } else { 148 pad16(cipherTextLength); 149 } 150 151 ubyte[8] buf; 152 153 // Mac the lengths. 154 // Note: Inconsistency in RFC7539: section 2.8.1 says, that lengths get encoded as 4 byte little endian, 155 // but in the test vectors they get encoded as 8 byte little endian. 156 // 157 toLittleEndian!ulong(aadLength, buf); 158 poly.put(buf[]); 159 toLittleEndian!ulong(cipherTextLength, buf); 160 poly.put(buf[]); 161 162 163 mac[0..16] = poly.finish(); 164 165 initialized = false; 166 return 0; 167 } 168 169 /// Get the minimal size of the output buffer for an input of length `len`. 170 /// Since this is a stream cipher, input and output are equal in length. 171 public size_t getUpdateOutputSize(in size_t len) pure { 172 return len; 173 } 174 175 /// Get the minimal buffer size needed for a call to `finish()`. 176 /// Since this is a stream cipher all data gets processed instantaneously. 177 /// Returns: 0 178 public size_t getOutputSize(in size_t len) pure { 179 return 0; 180 } 181 182 private: 183 184 /// Pad `poly` by adding as much zero bytes to make `len` a integral multiple of 16. 185 void pad16(size_t len) { 186 if(len % 16 != 0) { 187 ubyte[16] zeros = 0; 188 poly.put(zeros[0..16-len%16]); 189 } 190 } 191 192 static ubyte[32] poly1305KeyGen(in ubyte[] key, in ubyte[] nonce) pure { 193 uint[16] block; 194 195 Cipher.initState(block, key, 0, nonce); 196 Cipher.block(block, block); 197 198 ubyte[32] poly1305Key; 199 toLittleEndian(block[0..8], poly1305Key[]); 200 return poly1305Key; 201 } 202 203 // Test poly1305KeyGen 204 // Test vectors from RFC7539, section 2.6.2 205 unittest { 206 ubyte[32] key = cast(const ubyte[]) x"808182838485868788898a8b8c8d8e8f 909192939495969798999a9b9c9d9e9f"; 207 ubyte[12] nonce = cast(const ubyte[]) x"000000000001020304050607"; 208 209 ubyte[32] expectedPoly1305Key = cast(const ubyte[]) x"8ad5a08b905f81cc815040274ab29471 a833b637e3fd0da508dbb8e2fdd1a646"; 210 211 ubyte[32] poly1305Key = Poly1305ChaCha20.poly1305KeyGen(key, nonce); 212 213 assert(poly1305Key == expectedPoly1305Key, "poly1305KeyGen() failed."); 214 } 215 } 216 217 // Test vectors from RFC7539, section 2.8.2 218 unittest { 219 220 Poly1305ChaCha20 pcc; 221 222 enum string plaintext = x" 223 4c 61 64 69 65 73 20 61 6e 64 20 47 65 6e 74 6c 224 65 6d 65 6e 20 6f 66 20 74 68 65 20 63 6c 61 73 225 73 20 6f 66 20 27 39 39 3a 20 49 66 20 49 20 63 226 6f 75 6c 64 20 6f 66 66 65 72 20 79 6f 75 20 6f 227 6e 6c 79 20 6f 6e 65 20 74 69 70 20 66 6f 72 20 228 74 68 65 20 66 75 74 75 72 65 2c 20 73 75 6e 73 229 63 72 65 65 6e 20 77 6f 75 6c 64 20 62 65 20 69 230 74 2e"; 231 232 enum string expectedCipherText = x" 233 d3 1a 8d 34 64 8e 60 db 7b 86 af bc 53 ef 7e c2 234 a4 ad ed 51 29 6e 08 fe a9 e2 b5 a7 36 ee 62 d6 235 3d be a4 5e 8c a9 67 12 82 fa fb 69 da 92 72 8b 236 1a 71 de 0a 9e 06 0b 29 05 d6 a5 b6 7e cd 3b 36 237 92 dd bd 7f 2d 77 8b 8c 98 03 ae e3 28 09 1b 58 238 fa b3 24 e4 fa d6 75 94 55 85 80 8b 48 31 d7 bc 239 3f f4 de f0 8e 4b 7a 9d e5 76 d2 65 86 ce c6 4b 240 61 16"; 241 242 enum string aad = x"50 51 52 53 c0 c1 c2 c3 c4 c5 c6 c7"; 243 244 ubyte[32] key = cast(const ubyte[]) x"80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f"; 245 ubyte[8] iv = cast(const ubyte[]) x"40 41 42 43 44 45 46 47"; 246 uint senderID = 7; 247 248 pcc.start(true, key, senderID, iv); 249 250 ubyte[plaintext.length] ciphertext; 251 252 pcc.processAADBytes(cast(const ubyte[]) aad[]); 253 pcc.processBytes(cast(const ubyte[]) plaintext[], ciphertext[]); 254 255 ubyte[16] tag; 256 pcc.finish(tag); 257 258 assert(ciphertext == expectedCipherText, Poly1305ChaCha20.name~" produced wrong ciphertext."); 259 assert(tag == x"1ae10b594f09e26a7e902ecbd0600691", Poly1305ChaCha20.name~" produced wrong tag."); 260 261 }