diff --git a/benchmarks/stdlib/crypto/aes_bench.c3 b/benchmarks/stdlib/crypto/aes_bench.c3 new file mode 100644 index 000000000..8e148e997 --- /dev/null +++ b/benchmarks/stdlib/crypto/aes_bench.c3 @@ -0,0 +1,31 @@ +module std::crypto::aes_bench; + +import std::crypto::aes; + +fn void init() @init +{ + set_benchmark_warmup_iterations(5); + set_benchmark_max_iterations(10_000); +} + + +AesType aes = AES256; +char[] key = x"603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"; +char[] text = x"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710"; +char[] cipher = x"601ec313775789a5b7a7f504bbf3d228f443e3ca4d62b59aca84e990cacaf5c52b0930daa23de94ce87017ba2d84988ddfc9c58db67aada613c2dd08457941a6"; +char[16] iv = x"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"; + +fn void bench_ctr_xcrypt() @benchmark +{ + char[64] out; + Aes ctx; + + // encrypt + ctx.init_with_iv(aes, CTR, key, iv); + ctx.encrypt_buffer(text, &out); + + // decrypt + ctx.init_with_iv(aes, CTR, key, iv); + ctx.decrypt_buffer(cipher, &out); +} + diff --git a/lib/std/crypto/aes.c3 b/lib/std/crypto/aes.c3 new file mode 100644 index 000000000..eb65d60ca --- /dev/null +++ b/lib/std/crypto/aes.c3 @@ -0,0 +1,650 @@ +<* +This is an implementation of the AES algorithm with the ECB, CTR and CBC +modes. The key size can be chosen among AES128, AES192, AES256. + +Ported from github.com/kokke/tiny-aes-c by Koni Marti. + +The implementation is verified against the test vectors from the National +Institute of Standards and Technology Special Publication 800-38A 2001 ED. + +Data length must be evenly divisible by 16 bytes (len % 16 == 0) unless CTR is +used. You should pad the end of the string with zeros or use PKCS7 if this is not the case. +For AES192/256 the key size is proportionally larger. + +The following example demonstrates the AES encryption of a plaintext string +with an AES 128-bit key: + +``` +module app; +import std::crypto::aes, std::io; +fn void main() +{ + char[] key = x"2b7e151628aed2a6abf7158809cf4f3c"; + char[] text = x"6bc1bee22e409f96e93d7e117393172a"; + char[16] iv = x"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"; + Aes aes; + aes.init(AES128, key, iv); + defer aes.destroy(); + char[] cipher = aes.encrypt(mem, text); + defer free(cipher); + assert(cipher == x"874d6191b620e3261bef6864990db6ce"); +} +``` + +*> +module std::crypto::aes; + +<* Block length in bytes. AES is 128-bit blocks only. *> +const BLOCKLEN = 16; + +<* Number of columns of a AES state. *> +const COLNUM = 4; + +<* + Block modes: + ECB - Electronic Code Book (Not recommended, indata be 16 byte multiple) + CBC - Cipher Block Chaining (Indata be 16 byte multiple) + CTR - Counter Mode (Recommended, data may be any size) +*> +enum BlockMode +{ + ECB, + CBC, + CTR, +} + +<* AES type: 128, 192 or 256 bits *> +enum AesType : (AesKey key) +{ + AES128 = { 128, 16, 176, 4, 10 }, + AES192 = { 192, 24, 208, 6, 12 }, + AES256 = { 256, 32, 240, 8, 14 } +} + +struct AesKey +{ + <* Size of key in bits *> + usz key_size; + <* Size of key in bytes *> + int key_len; + <* Size of the expanded round_key *> + int key_exp_size; // expected size of round_key + <* Number of 32 bit words in key *> + usz nk; + <* Number of rounds in the cipher *> + usz nr; +} + +struct Aes +{ + <* The type, AES128, AES192 or AES256 *> + AesKey type; + <* Block mode: ECB, CBC or CTR *> + BlockMode mode; + <* Initialization Vector *> + char[BLOCKLEN] iv; + <* Internal key state *> + char[256] round_key; + <* Internal state *> + AesState state; +} +alias AesState = char[COLNUM][COLNUM]; + +<* + Initializes the AES crypto. The initialization vector should be securely random for each encryption + to mitigate things like replay attacks. + + @param type : "The type or AES: 128, 192 or 256 bits" + @param [in] key : "The key to use, should be the same bit size as the type, so 16, 24 or 32 bytes" + @param iv : "The initialization vector" + @param mode : "The block mode: EBC, CBC, CTR. Defaults to CTR" + + @require key.len == type.key.key_len : "Key does not match expected length." +*> +fn Aes* Aes.init(&self, AesType type, char[] key, char[BLOCKLEN] iv, BlockMode mode = CTR) +{ + *self = { .type = type.key, .mode = mode, .iv = iv }; + key_expansion(type, key, &self.round_key); + return self; +} + +<* + Completely erases data stored in the context. +*> +fn void Aes.destroy(&self) +{ + *self = {}; +} + +<* + Check if the length is valid using the given block mode. It has to be a multiple of 16 bytes unless CTR is used. +*> +macro bool is_valid_encryption_len(BlockMode mode, usz len) +{ + switch (mode) + { + case CTR: + return true; + case ECB: + case CBC: + return len % BLOCKLEN == 0; + } +} + +<* + @param [in] in : "Plaintext input." + @param [out] out : "Cipher output." + @require is_valid_encryption_len(self.mode, in.len) : "The input must be a multiple of 16 unless CTR is used" + @require out.len >= in.len : "Out buffer must be sufficiently large to hold the data" +*> +fn void Aes.encrypt_buffer(&self, char[] in, char[] out) +{ + switch (self.mode) + { + case CTR: ctr_xcrypt_buffer(self, in, out); + case ECB: ecb_encrypt_buffer(self, in, out); + case CBC: cbc_encrypt_buffer(self, in, out); + } +} + +<* + @param [in] in : "Cipher input." + @param [out] out : "Plaintext output." + @require is_valid_encryption_len(self.mode, in.len) : "The encrypted data must be a multiple of 16 unless CTR is used" + @require out.len >= in.len : "Out buffer must be sufficiently large to hold the data" +*> +fn void Aes.decrypt_buffer(&self, char[] in, char[] out) +{ + switch (self.mode) + { + case ECB: ecb_decrypt_buffer(self, in, out); + case CBC: cbc_decrypt_buffer(self, in, out); + case CTR: ctr_xcrypt_buffer(self, in, out); + } +} + +<* + Encrypt the data, allocating memory for the encrypted data. + + @param [in] in : "Plaintext input." + @param [&inout] allocator : "The allocator to use for the output" + @require is_valid_encryption_len(self.mode, in.len) : "The in-data needs to be a multiple of 16 unless CTR is used" +*> +fn char[] Aes.encrypt(&self, Allocator allocator, char[] in) +{ + char[] out = allocator::alloc_array(allocator, char, in.len); + self.encrypt_buffer(in, out) @inline; + return out; +} + +<* + Encrypt the data, allocating temp memory for the encrypted data. + + @param [in] in : "Plaintext input." + @require is_valid_encryption_len(self.mode, in.len) : "The in-data needs to be a multiple of 16 unless CTR is used" +*> +fn char[] Aes.tencrypt(&self, char[] in) +{ + return self.encrypt(tmem, in); +} + +<* + Decrypt the data, allocating memory for the decrypted data. + + @param [in] in : "Encrypted input." + @param [&inout] allocator : "The allocator to use for the output" + @require is_valid_encryption_len(self.mode, in.len) : "The in-data needs to be a multiple of 16 unless CTR is used" +*> +fn char[] Aes.decrypt(&self, Allocator allocator, char[] in) +{ + char[] out = allocator::alloc_array(allocator, char, in.len); + self.decrypt_buffer(in, out) @inline; + return out; +} + +<* + Decrypt the data, allocating temp memory for the decrypted data. + + @param [in] in : "Encrypted input." + @require is_valid_encryption_len(self.mode, in.len) : "The in-data needs to be a multiple of 16 unless CTR is used" + +*> +fn char[] Aes.tdecrypt(&self, char[] in) +{ + return self.decrypt(tmem, in); +} + +module std::crypto::aes @private; + +<* + @param [&inout] aes : "AES context." + @param [in] in : "Plaintext input." + @param [out] out : "Cipher output." +*> +fn void ecb_encrypt_block(Aes *aes, char[BLOCKLEN]* in, char[BLOCKLEN]* out) +{ + for (usz i = 0; i < 4; i++) + { + for (usz j = 0; j < 4; j++) + { + aes.state[i][j] = (*in)[i * 4 + j]; + } + } + aes_cipher(aes, &aes.round_key); + for (usz i = 0; i < 4; i++) + { + for (usz j = 0; j < 4; j++) + { + (*out)[i * 4 + j] = aes.state[i][j]; + } + } +} + +<* + @param [&inout] aes : "AES context." + @param [in] in : "Cipher input." + @param [out] out : "Plaintext output." +*> +fn void ecb_decrypt_block(Aes *aes, char[BLOCKLEN]* in, char[BLOCKLEN]* out) +{ + for (usz i = 0; i < 4; i++) + { + for (usz j = 0; j < 4; j++) + { + aes.state[i][j] = (*in)[i * 4 + j]; + } + } + inv_cipher(aes, &aes.round_key); + for (usz i = 0; i < 4; i++) + { + for (usz j = 0; j < 4; j++) + { + (*out)[i * 4 + j] = aes.state[i][j]; + } + } +} + +<* + @param [&inout] aes : "AES context." + @param [in] in : "Cipher input." + @param [out] out : "Plaintext output." + @require out.len >= in.len : "out must be at least as large as buf" +*> +fn void ecb_decrypt_buffer(Aes *aes, char[] in, char[] out) +{ + usz len = in.len; + for (usz i = 0; i < len; i += 4) + { + ecb_decrypt_block(aes, in[:BLOCKLEN], out[:BLOCKLEN]) @inline; + } +} + +<* + @param [&inout] aes : "AES context." + @param [in] in : "Plaintext input." + @param [out] out : "Cipher output." +*> +fn void ecb_encrypt_buffer(Aes *aes, char[] in, char[] out) +{ + usz len = in.len; + for (usz i = 0; i < len; i += BLOCKLEN) + { + ecb_encrypt_block(aes, in[i:BLOCKLEN], out[i:BLOCKLEN]) @inline; + } +} + +fn void xor_with_iv(char[] buf, char[BLOCKLEN]* iv) @local +{ + foreach (i, b : *iv) + { + buf[i] ^= b; + } +} + +<* + @param [&inout] aes : "AES context." + @param [in] in : "Plaintext input." + @param [out] out : "Cipher output." +*> +fn void cbc_encrypt_buffer(Aes *aes, char[] in, char[] out) +{ + char[] iv = aes.iv[..]; + usz len = in.len; + char[BLOCKLEN] tmp; + char[BLOCKLEN] tmp2; + for (usz i = 0; i < len; i += BLOCKLEN) + { + tmp[:BLOCKLEN] = in[i:BLOCKLEN]; + xor_with_iv(&tmp, iv); + ecb_encrypt_block(aes, &tmp, &tmp2); + out[i:BLOCKLEN] = tmp2[..]; + iv = tmp2[..]; + } +} + +<* + @param [&inout] aes : "AES context." + @param [in] in : "Cipher input." + @param [out] out : "Plaintext output." +*> +fn void cbc_decrypt_buffer(Aes *aes, char[] in, char[] out) +{ + char[BLOCKLEN] tmp; + usz len = in.len; + for (usz i = 0; i < len; i += BLOCKLEN) + { + ecb_decrypt_block(aes, in[i:BLOCKLEN], &tmp); + xor_with_iv(&tmp, aes.iv[..]); + aes.iv[:BLOCKLEN] = in[i:BLOCKLEN]; + out[i:BLOCKLEN] = tmp[..]; + } +} + +<* + @param [&inout] aes : "AES context." + @param [in] in : "Plaintext/cipher input." + @param [out] out : "Cipher/plaintext output." +*> +fn void ctr_xcrypt_buffer(Aes *aes, char[] in, char[] out) +{ + char[BLOCKLEN] buffer @noinit; + usz len = in.len; + for (int bi = BLOCKLEN, usz i = 0; i < len; i++) + { + if (bi == BLOCKLEN) + { + buffer = aes.iv; + ecb_encrypt_block(aes, &buffer, &buffer); + + for LOOP: (bi = (BLOCKLEN - 1); bi >= 0; bi--) + { + if (aes.iv[bi] == 255) + { + aes.iv[bi] = 0; + continue; + } + aes.iv[bi]++; + break LOOP; + } + bi = 0; + } + out[i] = in[i] ^ buffer[bi]; + bi++; + } +} + +macro char get_sbox_value(num) => SBOX[num]; +macro char get_sbox_invert(num) => RSBOX[num]; + +const char[256] SBOX = + x`637c777bf26b6fc53001672bfed7ab76 + ca82c97dfa5947f0add4a2af9ca472c0 + b7fd9326363ff7cc34a5e5f171d83115 + 04c723c31896059a071280e2eb27b275 + 09832c1a1b6e5aa0523bd6b329e32f84 + 53d100ed20fcb15b6acbbe394a4c58cf + d0efaafb434d338545f9027f503c9fa8 + 51a3408f929d38f5bcb6da2110fff3d2 + cd0c13ec5f974417c4a77e3d645d1973 + 60814fdc222a908846eeb814de5e0bdb + e0323a0a4906245cc2d3ac629195e479 + e7c8376d8dd54ea96c56f4ea657aae08 + ba78252e1ca6b4c6e8dd741f4bbd8b8a + 703eb5664803f60e613557b986c11d9e + e1f8981169d98e949b1e87e9ce5528df + 8ca1890dbfe6426841992d0fb054bb16`; + +const char[256] RSBOX = + x`52096ad53036a538bf40a39e81f3d7fb + 7ce339829b2fff87348e4344c4dee9cb + 547b9432a6c2233dee4c950b42fac34e + 082ea16628d924b2765ba2496d8bd125 + 72f8f66486689816d4a45ccc5d65b692 + 6c704850fdedb9da5e154657a78d9d84 + 90d8ab008cbcd30af7e45805b8b34506 + d02c1e8fca3f0f02c1afbd0301138a6b + 3a9111414f67dcea97f2cfcef0b4e673 + 96ac7422e7ad3585e2f937e81c75df6e + 47f11a711d29c5896fb7620eaa18be1b + fc563e4bc6d279209adbc0fe78cd5af4 + 1fdda8338807c731b11210592780ec5f + 60517fa919b54a0d2de57a9f93c99cef + a0e03b4dae2af5b0c8ebbb3c83539961 + 172b047eba77d626e169146355210c7d`; + +const char[11] RCON = x`8d01020408102040801b36`; + +fn void add_round_key(Aes* aes, usz round, char[] round_key) +{ + usz i, j; + for (i = 0; i < 4; i++) + { + for (j = 0; j < 4; j++) + { + aes.state[i][j] ^= round_key[(round * COLNUM * 4) + (i * COLNUM) + j]; + } + } +} + +fn void sub_bytes(Aes* aes) +{ + for (usz i = 0; i < 4; i++) + { + for (usz j = 0; j < 4; j++) + { + aes.state[j][i] = get_sbox_value(aes.state[j][i]); + } + } +} + +fn void shift_rows(Aes* aes) +{ + char temp; + + temp = aes.state[0][1]; + aes.state[0][1] = aes.state[1][1]; + aes.state[1][1] = aes.state[2][1]; + aes.state[2][1] = aes.state[3][1]; + aes.state[3][1] = temp; + + temp = aes.state[0][2]; + aes.state[0][2] = aes.state[2][2]; + aes.state[2][2] = temp; + + temp = aes.state[1][2]; + aes.state[1][2] = aes.state[3][2]; + aes.state[3][2] = temp; + + temp = aes.state[0][3]; + aes.state[0][3] = aes.state[3][3]; + aes.state[3][3] = aes.state[2][3]; + aes.state[2][3] = aes.state[1][3]; + aes.state[1][3] = temp; +} + +fn char xtime(char x) @local +{ + return ((x << 1) ^ (((x >> 7) & 1) * 0x1b)); +} + +fn void mix_columns(Aes* aes) +{ + for (usz i = 0; i < 4; i++) + { + char t = aes.state[i][0]; + char tmp = aes.state[i][0] ^ aes.state[i][1] ^ aes.state[i][2] ^ aes.state[i][3]; + + char tm = aes.state[i][0] ^ aes.state[i][1]; + tm = xtime(tm); + aes.state[i][0] ^= tm ^ tmp; + + tm = aes.state[i][1] ^ aes.state[i][2]; + tm = xtime(tm); + aes.state[i][1] ^= tm ^ tmp; + + tm = aes.state[i][2] ^ aes.state[i][3]; + tm = xtime(tm); + aes.state[i][2] ^= tm ^ tmp; + + tm = aes.state[i][3] ^ t; + tm = xtime(tm); + aes.state[i][3] ^= tm ^ tmp; + } +} + +fn char multiply(char x, char y) @local +{ + return (((y & 1) * x) ^ + (((y>>1) & 1) * xtime(x)) ^ + (((y>>2) & 1) * xtime(xtime(x))) ^ + (((y>>3) & 1) * xtime(xtime(xtime(x)))) ^ + (((y>>4) & 1) * xtime(xtime(xtime(xtime(x)))))); +} + +fn void inv_mix_columns(Aes* aes) +{ + for (int i = 0; i < 4; i++) + { + char a = aes.state[i][0]; + char b = aes.state[i][1]; + char c = aes.state[i][2]; + char d = aes.state[i][3]; + + aes.state[i][0] = multiply(a, 0x0e) ^ multiply(b, 0x0b) ^ multiply(c, 0x0d) ^ multiply(d, 0x09); + aes.state[i][1] = multiply(a, 0x09) ^ multiply(b, 0x0e) ^ multiply(c, 0x0b) ^ multiply(d, 0x0d); + aes.state[i][2] = multiply(a, 0x0d) ^ multiply(b, 0x09) ^ multiply(c, 0x0e) ^ multiply(d, 0x0b); + aes.state[i][3] = multiply(a, 0x0b) ^ multiply(b, 0x0d) ^ multiply(c, 0x09) ^ multiply(d, 0x0e); + } +} + +fn void inv_sub_bytes(Aes* aes) +{ + for (usz i = 0; i < 4; i++) + { + for (usz j = 0; j < 4; j++) + { + aes.state[j][i] = get_sbox_invert(aes.state[j][i]); + } + } +} + +fn void inv_shift_rows(Aes* aes) +{ + char temp; + + temp = aes.state[3][1]; + aes.state[3][1] = aes.state[2][1]; + aes.state[2][1] = aes.state[1][1]; + aes.state[1][1] = aes.state[0][1]; + aes.state[0][1] = temp; + + temp = aes.state[0][2]; + aes.state[0][2] = aes.state[2][2]; + aes.state[2][2] = temp; + + temp = aes.state[1][2]; + aes.state[1][2] = aes.state[3][2]; + aes.state[3][2] = temp; + + temp = aes.state[0][3]; + aes.state[0][3] = aes.state[1][3]; + aes.state[1][3] = aes.state[2][3]; + aes.state[2][3] = aes.state[3][3]; + aes.state[3][3] = temp; +} + +fn void aes_cipher(Aes* aes, char[] round_key) +{ + usz round = 0; + add_round_key(aes, 0, round_key); + + for LOOP: (round = 1;; round++) + { + sub_bytes(aes); + shift_rows(aes); + if (round == aes.type.nr) break LOOP; + mix_columns(aes); + add_round_key(aes, round, round_key); + } + add_round_key(aes, aes.type.nr, round_key); +} + +fn void inv_cipher(Aes* aes, char[] round_key) +{ + add_round_key(aes, aes.type.nr, round_key); + for (usz round = aes.type.nr - 1; ; round--) + { + inv_shift_rows(aes); + inv_sub_bytes(aes); + add_round_key(aes, round, round_key); + if (!round) return; + inv_mix_columns(aes); + } +} + +<*ยจ + @param type : "The AES variant to expant the key for" + @param [out] round_key : "Key to expand into" + @param [in] key : "The key to expand" + @require key.len == type.key.key_len : "Key does not match expected length." +*> +fn void key_expansion(AesType type, char[] key, char[] round_key) @private +{ + usz nk = type.key.nk; + for (usz i = 0; i < nk; i++) + { + round_key[(i * 4) + 0] = key[(i * 4) + 0]; + round_key[(i * 4) + 1] = key[(i * 4) + 1]; + round_key[(i * 4) + 2] = key[(i * 4) + 2]; + round_key[(i * 4) + 3] = key[(i * 4) + 3]; + } + + for (usz i = nk; i < COLNUM * (type.key.nr + 1); i++) + { + usz k = (i - 1) * 4; + + char[4] tempa @noinit; + + tempa[0] = round_key[k + 0]; + tempa[1] = round_key[k + 1]; + tempa[2] = round_key[k + 2]; + tempa[3] = round_key[k + 3]; + + if (i % nk == 0) + { + // rotword + char tmp = tempa[0]; + tempa[0] = tempa[1]; + tempa[1] = tempa[2]; + tempa[2] = tempa[3]; + tempa[3] = tmp; + + // subword + tempa[0] = get_sbox_value(tempa[0]); + tempa[1] = get_sbox_value(tempa[1]); + tempa[2] = get_sbox_value(tempa[2]); + tempa[3] = get_sbox_value(tempa[3]); + + tempa[0] = tempa[0] ^ RCON[i / nk]; + } + + if (type.key.key_size == 256) + { + if (i % nk == 4) + { + // subword + tempa[0] = get_sbox_value(tempa[0]); + tempa[1] = get_sbox_value(tempa[1]); + tempa[2] = get_sbox_value(tempa[2]); + tempa[3] = get_sbox_value(tempa[3]); + } + } + usz j = i * 4; + k = (i - nk) * 4; + round_key[j + 0] = round_key[k + 0] ^ tempa[0]; + round_key[j + 1] = round_key[k + 1] ^ tempa[1]; + round_key[j + 2] = round_key[k + 2] ^ tempa[2]; + round_key[j + 3] = round_key[k + 3] ^ tempa[3]; + } +} + diff --git a/lib/std/crypto/aes_128_192_256.c3 b/lib/std/crypto/aes_128_192_256.c3 new file mode 100644 index 000000000..3f7dea9fb --- /dev/null +++ b/lib/std/crypto/aes_128_192_256.c3 @@ -0,0 +1,87 @@ +// Experimental implementation +module std::crypto::aes128; +import std::crypto::aes; + +fn char[] encrypt(Allocator allocator, char[16]* key, char[aes::BLOCKLEN] iv, char[] data) +{ + Aes aes @noinit; + aes.init(AES128, key, iv, CTR); + defer aes.destroy(); + return aes.encrypt(allocator, data); +} + +fn char[] tencrypt(char[16]* key, char[aes::BLOCKLEN] iv, char[] data) +{ + return encrypt(tmem, key, iv, data); +} + +fn char[] decrypt(Allocator allocator, char[16]* key, char[aes::BLOCKLEN] iv, char[] data) +{ + Aes aes @noinit; + aes.init(AES128, key, iv, CTR); + defer aes.destroy(); + return aes.decrypt(allocator, data); +} + +fn char[] tdecrypt(char[16]* key, char[aes::BLOCKLEN] iv, char[] data) +{ + return decrypt(tmem, key, iv, data); +} + +module std::crypto::aes192; +import std::crypto::aes; + +fn char[] encrypt(Allocator allocator, char[24]* key, char[aes::BLOCKLEN] iv, char[] data) +{ + Aes aes @noinit; + aes.init(AES192, key, iv, CTR); + defer aes.destroy(); + return aes.encrypt(allocator, data); +} + +fn char[] tencrypt(char[24]* key, char[aes::BLOCKLEN] iv, char[] data) +{ + return encrypt(tmem, key, iv, data); +} + +fn char[] decrypt(Allocator allocator, char[24]* key, char[aes::BLOCKLEN] iv, char[] data) +{ + Aes aes @noinit; + aes.init(AES192, key, iv, CTR); + defer aes.destroy(); + return aes.decrypt(allocator, data); +} + +fn char[] tdecrypt(char[24]* key, char[aes::BLOCKLEN] iv, char[] data) +{ + return decrypt(tmem, key, iv, data); +} + +module std::crypto::aes256; +import std::crypto::aes; + +fn char[] encrypt(Allocator allocator, char[32]* key, char[aes::BLOCKLEN] iv, char[] data) +{ + Aes aes @noinit; + aes.init(AES256, key, iv, CTR); + defer aes.destroy(); + return aes.encrypt(allocator, data); +} + +fn char[] tencrypt(char[32]* key, char[aes::BLOCKLEN] iv, char[] data) +{ + return encrypt(tmem, key, iv, data); +} + +fn char[] decrypt(Allocator allocator, char[32]* key, char[aes::BLOCKLEN] iv, char[] data) +{ + Aes aes @noinit; + aes.init(AES256, key, iv, CTR); + defer aes.destroy(); + return aes.decrypt(allocator, data); +} + +fn char[] tdecrypt(char[32]* key, char[aes::BLOCKLEN] iv, char[] data) +{ + return decrypt(tmem, key, iv, data); +} diff --git a/lib/std/crypto/crypto.c3 b/lib/std/crypto/crypto.c3 index b6080dc54..f9758de35 100644 --- a/lib/std/crypto/crypto.c3 +++ b/lib/std/crypto/crypto.c3 @@ -9,4 +9,3 @@ fn bool safe_compare(void* data1, void* data2, usz len) } return match == 0; } - diff --git a/releasenotes.md b/releasenotes.md index 7bcf63fad..7a503a053 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -164,6 +164,7 @@ - Added array `@reduce`, `@filter`, `@any`, `@all`, `@sum`, `@product`, and `@indices_of` macros. - `String.bformat` has reduced overhead. - Supplemental `roundeven` has a normal implementation. +- Added Advanced Encryption Standard (AES) algorithm (ECB, CTR, CBC modes) in `std::crypto::aes`. ## 0.7.4 Change list diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index 6c7418176..bcd57d960 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -802,7 +802,8 @@ typedef struct { Expr* expr; UnaryOp operator : 8; - bool no_wrap : 1; + bool no_wrap; + bool no_read; } ExprUnary; diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index 400ec09cf..1acea7107 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -2357,6 +2357,7 @@ static inline bool sema_call_check_contract_param_match(SemaContext *context, De return false; } } + if (expr->expr_kind != EXPR_IDENTIFIER) return true; Decl *ident = expr->ident_expr; if (ident->decl_kind != DECL_VAR) return true; @@ -3927,6 +3928,9 @@ static inline bool sema_expr_analyse_subscript_lvalue(SemaContext *context, Expr case EXPR_CT_IDENT: if (!sema_analyse_expr_lvalue(context, subscripted, NULL)) return false; break; + case EXPR_UNARY: + subscripted->unary_expr.no_read = true; + goto DEFAULT; case EXPR_SUBSCRIPT: { Expr *inner = expr_copy(subscripted); @@ -3938,9 +3942,11 @@ static inline bool sema_expr_analyse_subscript_lvalue(SemaContext *context, Expr subscripted->expr_kind = EXPR_UNARY; subscripted->unary_expr.operator = UNARYOP_DEREF; subscripted->unary_expr.expr = inner; + subscripted->unary_expr.no_read = true; FALLTHROUGH; } default: +DEFAULT: if (!sema_analyse_expr(context, subscripted)) return false; break; } @@ -11657,6 +11663,7 @@ static inline bool sema_cast_rvalue(SemaContext *context, Expr *expr, bool mutat break; case EXPR_SUBSCRIPT: case EXPR_SLICE: + /* { Expr *inner = exprptr(expr->expr_kind == EXPR_SUBSCRIPT ? expr->subscript_expr.expr : expr->slice_expr.expr); if (inner->expr_kind != EXPR_IDENTIFIER) break; @@ -11665,7 +11672,8 @@ static inline bool sema_cast_rvalue(SemaContext *context, Expr *expr, bool mutat if (!decl->var.out_param || decl->var.in_param) break; if (context->active_scope.flags & (SCOPE_ENSURE | SCOPE_ENSURE_MACRO)) break; RETURN_SEMA_ERROR(expr, "'out' parameters may not be read."); - } + }*/ + break; case EXPR_UNARY: { if (expr->unary_expr.operator != UNARYOP_DEREF) break; @@ -11673,6 +11681,7 @@ static inline bool sema_cast_rvalue(SemaContext *context, Expr *expr, bool mutat if (inner->expr_kind != EXPR_IDENTIFIER) break; Decl *decl = inner->ident_expr; if (decl->decl_kind != DECL_VAR) break; + if (expr->unary_expr.no_read) break; if (!decl->var.out_param || decl->var.in_param) break; if (context->active_scope.flags & (SCOPE_ENSURE | SCOPE_ENSURE_MACRO)) return true; RETURN_SEMA_ERROR(expr, "'out' parameters may not be read."); diff --git a/test/test_suite/contracts/out_subscript.c3 b/test/test_suite/contracts/out_subscript.c3 index 9d9e97cbc..70a163894 100644 --- a/test/test_suite/contracts/out_subscript.c3 +++ b/test/test_suite/contracts/out_subscript.c3 @@ -1,12 +1,13 @@ + <* @param [out] z @param [out] out_data *> fn void tes2t(char* z, char[] out_data, char[] in_data) { z[0] = 2; - z[0] += 1; // #error: 'out' parameters may not be read + z[0] += 1; // error: 'out' parameters may not be read out_data[0] = 3; - out_data[0] *= 1; // #error: 'out' parameters may not be read - out_data[0..3]; // #error: 'out' parameters may not be read + out_data[0] *= 1; // error: 'out' parameters may not be read + out_data[0..3]; // error: 'out' parameters may not be read out_data[0..3] = 23; } \ No newline at end of file diff --git a/test/unit/stdlib/crypto/aes_test.c3 b/test/unit/stdlib/crypto/aes_test.c3 new file mode 100644 index 000000000..7148eefda --- /dev/null +++ b/test/unit/stdlib/crypto/aes_test.c3 @@ -0,0 +1,326 @@ +module std::crypto::aes_test; + +import std::crypto, std::io; + +struct TestCase +{ + AesType aes; + char[] key; + char[] plaintext; + char[] cipher; +} + +fn void test_ecb_encrypt() @test +{ + char[16] out; + TestCase[] tests = { + { + .aes = AES128, + .key = x"2b7e151628aed2a6abf7158809cf4f3c", + .plaintext = x"6bc1bee22e409f96e93d7e117393172a", + .cipher = x"3ad77bb40d7a3660a89ecaf32466ef97", + }, + { + .aes = AES128, + .key = x"2b7e151628aed2a6abf7158809cf4f3c", + .plaintext = x"ae2d8a571e03ac9c9eb76fac45af8e51", + .cipher = x"f5d3d58503b9699de785895a96fdbaaf", + }, + { + .aes = AES128, + .key = x"2b7e151628aed2a6abf7158809cf4f3c", + .plaintext = x"30c81c46a35ce411e5fbc1191a0a52ef", + .cipher = x"43b1cd7f598ece23881b00e3ed030688", + }, + { + .aes = AES128, + .key = x"2b7e151628aed2a6abf7158809cf4f3c", + .plaintext = x"f69f2445df4f9b17ad2b417be66c3710", + .cipher = x"7b0c785e27e8ad3f8223207104725dd4", + }, + { + .aes = AES192, + .key = x"8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + .plaintext = x"6bc1bee22e409f96e93d7e117393172a", + .cipher = x"bd334f1d6e45f25ff712a214571fa5cc", + }, + { + .aes = AES256, + .key = x"603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + .plaintext = x"6bc1bee22e409f96e93d7e117393172a", + .cipher = x"f3eed1bdb5d2a03c064b5a7e3db181f8", + }, + }; + + Aes ctx; + foreach (i, t : tests) + { + ctx.init(t.aes, t.key, {}, ECB); + ctx.encrypt_buffer(t.plaintext, &out); + assert(out[:16] == t.cipher[:16], + "Test %d failed; invalid cipher; got: %s, want: %s", i+1, out, t.cipher); + + } + + foreach (i, t : tests) + { + @pool() + { + ctx.init(t.aes, t.key, {}, ECB); + char[] tmp_out = ctx.tencrypt(t.plaintext); + assert(tmp_out[:16] == t.cipher[:16], + "Test %d failed; invalid cipher; got: %s, want: %s", i+1, tmp_out, t.cipher); + }; + } +} + +fn void test_ecb_decrypt() @test +{ + char[16] out; + TestCase[] tests = { + { + .aes = AES128, + .key = x"2b7e151628aed2a6abf7158809cf4f3c", + .plaintext = x"6bc1bee22e409f96e93d7e117393172a", + .cipher = x"3ad77bb40d7a3660a89ecaf32466ef97", + }, + { + .aes = AES192, + .key = x"8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + .plaintext = x"6bc1bee22e409f96e93d7e117393172a", + .cipher = x"bd334f1d6e45f25ff712a214571fa5cc", + }, + { + .aes = AES256, + .key = x"603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + .plaintext = x"6bc1bee22e409f96e93d7e117393172a", + .cipher = x"f3eed1bdb5d2a03c064b5a7e3db181f8", + }, + }; + + Aes ctx; + foreach (i, t : tests) + { + ctx.init(t.aes, t.key, {}, ECB); + ctx.decrypt_buffer(t.cipher, &out); + assert(out[:16] == t.plaintext[:16], + "Test %d failed; invalid plaintext; got: %s, want: %s", i+1, out, t.plaintext); + } + + foreach (i, t : tests) + { + @pool() + { + ctx.init(t.aes, t.key, {}, ECB); + char[] tmp_out = ctx.tdecrypt(t.cipher); + assert(tmp_out[:16] == t.plaintext[:16], + "Test %d failed; invalid plaintext; got: %s, want: %s", i+1, tmp_out, t.plaintext); + }; + } +} + +fn void test_cbc_encrypt() @test +{ + char[64] out; + TestCase[] tests = { + { + .aes = AES128, + .key = x"2b7e151628aed2a6abf7158809cf4f3c", + .plaintext = x"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + .cipher = x"7649abac8119b246cee98e9b12e9197d5086cb9b507219ee95db113a917678b273bed6b8e3c1743b7116e69e222295163ff1caa1681fac09120eca307586e1a7", + }, + { + .aes = AES192, + .key = x"8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + .plaintext = x"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + .cipher = x"4f021db243bc633d7178183a9fa071e8b4d9ada9ad7dedf4e5e738763f69145a571b242012fb7ae07fa9baac3df102e008b0e27988598881d920a9e64f5615cd", + }, + { + .aes = AES256, + .key = x"603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + .plaintext = x"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + .cipher = x"f58c4c04d6e5f1ba779eabfb5f7bfbd69cfc4e967edb808d679f777bc6702c7d39f23369a9d9bacfa530e26304231461b2eb05e2c39be9fcda6c19078c6a9d1b", + }, + }; + char[16] iv = x"000102030405060708090a0b0c0d0e0f"; + + Aes ctx; + foreach (i, t : tests) + { + ctx.init(t.aes, t.key, iv, CBC); + ctx.encrypt_buffer(t.plaintext, &out); + assert(out[:64] == t.cipher[:64], + "Test %d failed; invalid cipher; got: %s, want: %s", i+1, out, t.cipher); + } + + foreach (i, t : tests) + { + @pool() + { + ctx.init(t.aes, t.key, iv, CBC); + char[] tmp_out = ctx.tencrypt(t.plaintext); + assert(tmp_out[:64] == t.cipher[:64], + "Test %d failed; invalid cipher; got: %s, want: %s", i+1, tmp_out, t.cipher); + }; + } +} + +fn void test_cbc_decrypt() @test +{ + char[64] out; + TestCase[] tests = { + { + .aes = AES128, + .key = x"2b7e151628aed2a6abf7158809cf4f3c", + .plaintext = x"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + .cipher = x"7649abac8119b246cee98e9b12e9197d5086cb9b507219ee95db113a917678b273bed6b8e3c1743b7116e69e222295163ff1caa1681fac09120eca307586e1a7", + }, + { + .aes = AES192, + .key = x"8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + .plaintext = x"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + .cipher = x"4f021db243bc633d7178183a9fa071e8b4d9ada9ad7dedf4e5e738763f69145a571b242012fb7ae07fa9baac3df102e008b0e27988598881d920a9e64f5615cd", + }, + { + .aes = AES256, + .key = x"603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + .plaintext = x"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + .cipher = x"f58c4c04d6e5f1ba779eabfb5f7bfbd69cfc4e967edb808d679f777bc6702c7d39f23369a9d9bacfa530e26304231461b2eb05e2c39be9fcda6c19078c6a9d1b", + }, + }; + char[16] iv = x"000102030405060708090a0b0c0d0e0f"; + + Aes ctx; + foreach (i, t : tests) + { + ctx.init(t.aes, t.key, iv, CBC); + ctx.decrypt_buffer(t.cipher, &out); + assert(out[:64] == t.plaintext[:64], + "Test %d failed; invalid plaintext; got: %s, want: %s", i+1, out, t.plaintext); + } + + foreach (i, t : tests) + { + @pool() + { + ctx.init(t.aes, t.key, iv, CBC); + char[] tmp_out = ctx.tdecrypt(t.cipher); + assert(tmp_out[:64] == t.plaintext[:64], + "Test %d failed; invalid plaintext; got: %s, want: %s", i+1, tmp_out, t.plaintext); + }; + } +} + +fn void test_ctr_xcrypt() @test +{ + char[64] out; + TestCase[] tests = { + { + .aes = AES128, + .key = x"2b7e151628aed2a6abf7158809cf4f3c", + .plaintext = x"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + .cipher = x"874d6191b620e3261bef6864990db6ce9806f66b7970fdff8617187bb9fffdff5ae4df3edbd5d35e5b4f09020db03eab1e031dda2fbe03d1792170a0f3009cee", + }, + { + .aes = AES192, + .key = x"8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + .plaintext = x"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + .cipher = x"1abc932417521ca24f2b0459fe7e6e0b090339ec0aa6faefd5ccc2c6f4ce8e941e36b26bd1ebc670d1bd1d665620abf74f78a7f6d29809585a97daec58c6b050", + }, + { + .aes = AES256, + .key = x"603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + .plaintext = x"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + .cipher = x"601ec313775789a5b7a7f504bbf3d228f443e3ca4d62b59aca84e990cacaf5c52b0930daa23de94ce87017ba2d84988ddfc9c58db67aada613c2dd08457941a6", + }, + }; + char[16] iv = x"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"; + + Aes ctx; + foreach (i, t : tests) + { + // encrypt + ctx.init(t.aes, t.key, iv, CTR); + ctx.encrypt_buffer(t.plaintext, &out); + assert(out[:64] == t.cipher[:64], + "Test %d failed; invalid cipher; got: %s, want: %s", i+1, out, t.cipher); + + // decrypt + ctx.init(t.aes, t.key, iv, CTR); + ctx.decrypt_buffer(t.cipher, &out); + assert(out[:64] == t.plaintext[:64], + "Test %d failed; invalid plaintext; got: %s, want: %s", i+1, out, t.plaintext); + } + foreach (i, t : tests) + { + @pool() + { + // encrypt + ctx.init(t.aes, t.key, iv, CTR); + char[] cipher = ctx.tencrypt(t.plaintext); + assert(cipher[:64] == t.cipher[:64], + "Test %d failed; invalid cipher; got: %s, want: %s", i+1, cipher, t.cipher); + + // decrypt + ctx.init(t.aes, t.key, iv, CTR); + char[] text = ctx.tdecrypt(t.cipher); + assert(text[:64] == t.plaintext[:64], + "Test %d failed; invalid plaintext; got: %s, want: %s", i+1, text, t.plaintext); + }; + } +} + +fn void test_aes_xcrypt() @test +{ + char[64] out; + TestCase[] tests = { + { + .aes = AES128, + .key = x"2b7e151628aed2a6abf7158809cf4f3c", + .plaintext = x"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + .cipher = x"874d6191b620e3261bef6864990db6ce9806f66b7970fdff8617187bb9fffdff5ae4df3edbd5d35e5b4f09020db03eab1e031dda2fbe03d1792170a0f3009cee", + }, + { + .aes = AES192, + .key = x"8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + .plaintext = x"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + .cipher = x"1abc932417521ca24f2b0459fe7e6e0b090339ec0aa6faefd5ccc2c6f4ce8e941e36b26bd1ebc670d1bd1d665620abf74f78a7f6d29809585a97daec58c6b050", + }, + { + .aes = AES256, + .key = x"603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + .plaintext = x"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + .cipher = x"601ec313775789a5b7a7f504bbf3d228f443e3ca4d62b59aca84e990cacaf5c52b0930daa23de94ce87017ba2d84988ddfc9c58db67aada613c2dd08457941a6", + }, + }; + char[16] iv = x"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"; + + foreach (i, t : tests) + { + @pool() + { + // encrypt + char[] cipher; + switch (t.aes) + { + case AES128: cipher = aes128::tencrypt(t.key, iv, t.plaintext); + case AES192: cipher = aes192::tencrypt(t.key, iv, t.plaintext); + case AES256: cipher = aes256::tencrypt(t.key, iv, t.plaintext); + } + assert(cipher == t.cipher[:64], + "Test %d failed; invalid cipher; got: %s, want: %s", i + 1, cipher, t.cipher); + + // decrypt + char[] text; + switch (t.aes) + { + case AES128: text = aes128::tdecrypt(t.key, iv, t.cipher); + case AES192: text = aes192::tdecrypt(t.key, iv, t.cipher); + case AES256: text = aes256::tdecrypt(t.key, iv, t.cipher); + } + assert(text[:64] == t.plaintext[:64], + "Test %d failed; invalid plaintext; got: %h, want: %h", i + 1, text, t.plaintext); + }; + } +} +