authenc.ha (4918B)
1 // SPDX-License-Identifier: MPL-2.0 2 // (c) Hare authors <https://harelang.org> 3 4 use bytes; 5 use crypto::chachapoly; 6 use crypto::math; 7 use errors; 8 use io; 9 use memio; 10 11 // A secret session key. 12 export type sessionkey = [32]u8; 13 14 // A value which is only used once. 15 export type nonce = [24]u8; 16 17 // A message authentication code. 18 export type mac = [16]u8; 19 20 // An encrypted, authenticated message. 21 export type box = (mac, nonce, []u8); 22 23 // Performs authenticated encryption on a message. The result may be 24 // authenticated and decrypted with [[decrypt]]. 25 // 26 // To use this function, you must first establish a session key which is shared 27 // with both parties. This key must be random and secret. You may derive this 28 // key with a key exchange (such as [[exchange]] or [[dh]]), or with a key 29 // derivation function (such as [[derivekey]]), or by sharing it in person, or 30 // some other, similar means which preserves the traits of randomness and 31 // secrecy. 32 // 33 // You must also establish a unique nonce for each message, which you must not 34 // reuse for any future messages using the same session key. It is recommended 35 // to generate this randomly with [[crypto::random::]]. 36 // 37 // The plaintext parameter provides the message to encrypt. It will be 38 // overwritten with the ciphertext. The buffer provided in the return value is 39 // borrowed from this parameter. 40 // 41 // The optional 'additional' parameters provide additional data to be 42 // authenticated with the same MAC. This data is not encrypted, but [[decrypt]] 43 // will fail if it has been tampered with. The order of these arguments must be 44 // consistent between [[encrypt]] and [[decrypt]]. 45 // 46 // The return value contains all of the information which should be transmitted 47 // to the other party, including the computed MAC, a copy of the nonce, and the 48 // ciphertext. It is safe to transmit these values over an unsecured connection, 49 // or to encode them with something like [[encoding::base64::]]. 50 // 51 // Any 'additional' data, if provided, is not included in the [[box]] type. The 52 // user must provide for this data to be transmitted to the other party 53 // themselves. 54 // 55 // let key: crypto::sessionkey = [0...]; 56 // let nonce: crypto::nonce = [0...]; 57 // random::buffer(key); // Or some other means of establishing the key 58 // random::buffer(nonce); 59 // 60 // let buf: [64]u8 = [0...]; // Populate with your message 61 // let box = crypto::encrypt(&key, &nonce, buf[..], buf[..]); 62 // 63 // // To decrypt this message: 64 // let plaintext = match (crypto::decrypt(&key, &box, buf[..])) { 65 // case let buf: []u8 => 66 // yield buf; 67 // case errors::invalid => 68 // abort("Message authentication or decryption failed"); 69 // }; 70 // 71 // The current implementation of this algorithm is based on RFC 8439, but uses 72 // XChaCha20 instead of ChaCha20. 73 export fn encrypt( 74 key: *sessionkey, 75 nonce: *nonce, 76 plaintext: []u8, 77 additional: []u8... 78 ) box = { 79 let s = chachapoly::chachapoly(); 80 defer io::close(&s)!; 81 82 let h = memio::fixed(plaintext); 83 chachapoly::xinit(&s, &h, key, nonce, additional...); 84 io::writeall(&s, plaintext)!; 85 let m: mac = [0...]; 86 chachapoly::seal(&s, m); 87 return (m, *nonce, memio::buffer(&h)); 88 }; 89 90 // Authenticates and decrypts a message encrypted with [[encrypt]]. If the 91 // decryption is successful, the plaintext slice is returned, and if not, 92 // [[errors::invalid]] is returned. 93 // 94 // The 'sessionkey' parameter is the shared key. The 'box' parameter is the 95 // output of [[encrypt]]. If additional data should be authenticated, it may be 96 // provided in the variadic 'additional' parameters. 97 // 98 // Note that the data is decrypted in-place, such that the box's ciphertext 99 // becomes overwritten with the plaintext. The return value is borrowed from 100 // this buffer. If decryption fails, this buffer will be zeroed, causing the 101 // ciphertext to be destroyed as well. It is advised to zero the plaintext 102 // buffer yourself after you are done using it, see [[bytes::zero]]. 103 // 104 // See [[decrypt]] for the full details and a usage example. 105 export fn decrypt( 106 key: *sessionkey, 107 box: *box, 108 additional: []u8... 109 ) ([]u8 | errors::invalid) = { 110 let s = chachapoly::chachapoly(); 111 defer io::close(&s)!; 112 113 let ciphertext = box.2; 114 let h = memio::fixed(ciphertext); 115 chachapoly::xinit(&s, &h, key, box.1, additional...); 116 117 let plaintext = ciphertext; 118 io::readall(&s, plaintext)!; 119 120 if (chachapoly::verify(&s, box.0) is errors::invalid) { 121 bytes::zero(plaintext); 122 return errors::invalid; 123 }; 124 return plaintext; 125 }; 126 127 // Compares two slices and returns true if they are equal. Comparison is done 128 // in constant time, meaning that, if the slices have equal length, the time it 129 // takes depends only on the length of the slices and not the content. 130 export fn compare(a: []u8, b: []u8) bool = { 131 if (len(a) != len(b)) { 132 return false; 133 }; 134 135 return math::eqslice(a, b) == 1; 136 }; 137 138 // TODO: Add additional entry-points which provide a finer degree of control 139 // over buffer usage.