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