1 /// Implementation of ChaCha20 digester. 2 module tern.digest.chacha20; 3 4 import tern.digest; 5 import tern.digest.circe; 6 7 /** 8 * Implementation of ChaCha20 digester. 9 * 10 * ChaCha20 is a symmetric encryption algorithm designed to provide both high performance 11 * and high security. It operates on 512-bit (64-byte) blocks and accepts a 256-bit (32-byte) 12 * key and a 96-bit (12-byte) nonce. 13 * 14 * Example: 15 * ```d 16 * import tern.digest.chacha20; 17 * 18 * ubyte[] data = [1, 2, 3, 4, 5]; 19 * string key = "my_secret_key"; // Must be exactly 256 bits (32 bytes) in length. 20 * ubyte[12] nonce = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // Must be exactly 96 bits (12 bytes) in length. 21 * uint counter = 0; 22 * ChaCha20.encrypt(data, key, nonce, counter); 23 * ``` 24 */ 25 public static @digester class ChaCha20 26 { 27 private: 28 static: 29 void quarterRound(ref uint[16] block, uint a, uint b, uint c, uint d) 30 { 31 block[a] += block[b]; block[d] = (block[d] ^ block[a]) >>> 16; 32 block[c] += block[d]; block[b] = (block[b] ^ block[c]) >>> 12; 33 block[a] += block[b]; block[d] = (block[d] ^ block[a]) >>> 8; 34 block[c] += block[d]; block[b] = (block[b] ^ block[c]) >>> 7; 35 } 36 37 uint[16] innerRound(ref uint[16] block) 38 { 39 foreach (i; 0 .. 10) 40 { 41 quarterRound(block, 0, 4, 8, 12); 42 quarterRound(block, 1, 5, 9, 13); 43 quarterRound(block, 2, 6, 10, 14); 44 quarterRound(block, 3, 7, 11, 15); 45 quarterRound(block, 0, 5, 10, 15); 46 quarterRound(block, 1, 6, 11, 12); 47 quarterRound(block, 2, 7, 8, 13); 48 quarterRound(block, 3, 4, 9, 14); 49 } 50 return block; 51 } 52 53 public: 54 /** 55 * Encrypts the given byte array `data`. 56 * 57 * Params: 58 * data: Reference to the input byte array to be encrypted. 59 * key: The encryption key. Must be exactly 256 bits (32 bytes) in length. 60 * nonce: The nonce value. Must be exactly 96 bits (12 bytes) in length. 61 * counter: The initial counter value. Defaults to 0. 62 */ 63 void encrypt(ref ubyte[] data, string key, ubyte[12] nonce = (ubyte[12]).init, uint counter = 0) 64 { 65 assert(key.length == 32, "Key must be 256 bits!"); 66 key = cast(string)Circe.hash(cast(ubyte[])key); 67 68 uint[16] state; 69 state[0..4] = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574]; 70 state[4..12] = *cast(uint[8]*)key.ptr; 71 state[12..13] = counter; 72 state[13..16] = cast(uint[3])nonce; 73 74 uint[16] keyStream; 75 ubyte offset = 64; 76 77 foreach (ref octet; data) 78 { 79 if (offset >= 64) 80 { 81 keyStream = state.dup; 82 innerRound(keyStream); 83 // Counter 84 state[12]++; 85 offset = 0; 86 } 87 88 octet ^= (cast(ubyte[64])keyStream)[offset]; 89 offset++; 90 } 91 } 92 93 /** 94 * Decrypts the given byte array `data`. 95 * 96 * Params: 97 * data: Reference to the input byte array to be encrypted. 98 * key: The encryption key. Must be exactly 256 bits (32 bytes) in length. 99 * nonce: The nonce value. Must be exactly 96 bits (12 bytes) in length. 100 * counter: The initial counter value. Defaults to 0. 101 */ 102 void decrypt(ref ubyte[] data, string key, ubyte[12] nonce = (ubyte[12]).init, uint counter = 0) => encrypt(data, key, nonce, counter); 103 }