diff --git a/lib/std/crypto/aes.zig b/lib/std/crypto/aes.zig index 5e5ae04b58a6..8d8c7a82ff4d 100644 --- a/lib/std/crypto/aes.zig +++ b/lib/std/crypto/aes.zig @@ -52,6 +52,58 @@ test "ctr" { try testing.expectEqualSlices(u8, exp_out[0..], out[0..]); } +test "cbc encrypt" { + // NIST SP 800-38A pp 27-29 + const cbc = @import("modes.zig").cbcEncrypt; + + const key = [_]u8{ 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c }; + const iv = [_]u8{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }; + const in = [_]u8{ + 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a, + 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51, + 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef, + 0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10, + }; + const exp_out = [_]u8{ + 0x76, 0x49, 0xab, 0xac, 0x81, 0x19, 0xb2, 0x46, 0xce, 0xe9, 0x8e, 0x9b, 0x12, 0xe9, 0x19, 0x7d, + 0x50, 0x86, 0xcb, 0x9b, 0x50, 0x72, 0x19, 0xee, 0x95, 0xdb, 0x11, 0x3a, 0x91, 0x76, 0x78, 0xb2, + 0x73, 0xbe, 0xd6, 0xb8, 0xe3, 0xc1, 0x74, 0x3b, 0x71, 0x16, 0xe6, 0x9e, 0x22, 0x22, 0x95, 0x16, + 0x3f, 0xf1, 0xca, 0xa1, 0x68, 0x1f, 0xac, 0x09, 0x12, 0x0e, 0xca, 0x30, 0x75, 0x86, 0xe1, 0xa7, + }; + + var out: [exp_out.len]u8 = undefined; + + const ctx = Aes128.initEnc(key); + cbc(AesEncryptCtx(Aes128), ctx, out[0..], in[0..], iv); + try testing.expectEqualSlices(u8, exp_out[0..], out[0..]); +} + +test "cbc decrypt" { + // NIST SP 800-38A pp 27-29 + const cbc = @import("modes.zig").cbcDecrypt; + + const key = [_]u8{ 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c }; + const iv = [_]u8{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }; + const in = [_]u8{ + 0x76, 0x49, 0xab, 0xac, 0x81, 0x19, 0xb2, 0x46, 0xce, 0xe9, 0x8e, 0x9b, 0x12, 0xe9, 0x19, 0x7d, + 0x50, 0x86, 0xcb, 0x9b, 0x50, 0x72, 0x19, 0xee, 0x95, 0xdb, 0x11, 0x3a, 0x91, 0x76, 0x78, 0xb2, + 0x73, 0xbe, 0xd6, 0xb8, 0xe3, 0xc1, 0x74, 0x3b, 0x71, 0x16, 0xe6, 0x9e, 0x22, 0x22, 0x95, 0x16, + 0x3f, 0xf1, 0xca, 0xa1, 0x68, 0x1f, 0xac, 0x09, 0x12, 0x0e, 0xca, 0x30, 0x75, 0x86, 0xe1, 0xa7, + }; + const exp_out = [_]u8{ + 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a, + 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51, + 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef, + 0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10, + }; + + var out: [exp_out.len]u8 = undefined; + + const ctx = Aes128.initDec(key); + cbc(AesDecryptCtx(Aes128), ctx, out[0..], in[0..], iv); + try testing.expectEqualSlices(u8, exp_out[0..], out[0..]); +} + test "encrypt" { // Appendix B { diff --git a/lib/std/crypto/modes.zig b/lib/std/crypto/modes.zig index eed803a899c1..d47a3b30e84f 100644 --- a/lib/std/crypto/modes.zig +++ b/lib/std/crypto/modes.zig @@ -45,3 +45,71 @@ pub fn ctr(comptime BlockCipher: anytype, block_cipher: BlockCipher, dst: []u8, @memcpy(dst[i..][0..pad_slice.len], pad_slice); } } + +/// Cipher block chaining mode (decryption). +/// +/// First decrypts with cipher, then XORs with previous block (or initialization vector). +pub fn cbcDecrypt(BlockCipher: anytype, block_cipher: BlockCipher, dst: []u8, src: []const u8, iv: [BlockCipher.block_length]u8) void { + // TODO: Add support for parallel decryption + std.debug.assert(dst.len >= src.len); + const block_length = BlockCipher.block_length; + var i: usize = 0; + var previous_block: [block_length]u8 = iv; + while (i + block_length < src.len) : (i += block_length) { + var block: [block_length]u8 = undefined; + // xor block with last block or initialization vector + block_cipher.decrypt(&block, src[i..][0..block_length]); + for (block[0..], previous_block[0..]) |*byte, prev| { + byte.* ^= prev; + } + // update last block value + @memcpy(dst[i..][0..block_length], &block); + @memcpy(&previous_block, src[i..][0..block_length]); + } + // account for unaligned final block + if (i < src.len) { + var pad = [_]u8{0} ** block_length; + const src_slice = src[i..]; + @memcpy(pad[0..src_slice.len], src_slice); + block_cipher.decrypt(&pad, &pad); + for (pad[0..], previous_block[0..]) |*byte, prev| { + byte.* ^= prev; + } + const pad_slice = pad[0 .. src.len - i]; + @memcpy(dst[i..][0..pad_slice.len], pad_slice); + } +} + +/// Cipher block chaining mode (encryption). +/// +/// First XORs each block with the previous block (or initialization vector) and then +/// encrypts the block with cipher. +pub fn cbcEncrypt(BlockCipher: anytype, block_cipher: BlockCipher, dst: []u8, src: []const u8, iv: [BlockCipher.block_length]u8) void { + std.debug.assert(dst.len >= src.len); + const block_length = BlockCipher.block_length; + var i: usize = 0; + var previous_block: [block_length]u8 = iv; + while (i + block_length < src.len) : (i += block_length) { + var block: [block_length]u8 = undefined; + // xor block with last block or initialization vector + for (block[0..], src[i..][0..block_length], previous_block[0..]) |*byte, cur, prev| { + byte.* = cur ^ prev; + } + block_cipher.encrypt(&block, &block); + // update last block value + @memcpy(dst[i..][0..block_length], block[0..block_length]); + @memcpy(&previous_block, &block); + } + // account for unaligned final block + if (i < src.len) { + var pad = [_]u8{0} ** block_length; + const src_slice = src[i..]; + @memcpy(pad[0..src_slice.len], src_slice); + for (pad[0..], previous_block[0..]) |*byte, prev| { + byte.* ^= prev; + } + block_cipher.encrypt(&pad, &pad); + const pad_slice = pad[0 .. src.len - i]; + @memcpy(dst[i..][0..pad_slice.len], pad_slice); + } +}