| 1 | const std = @import("../std.zig"); |
| 2 | const crypto = std.crypto; |
| 3 | const debug = std.debug; |
| 4 | const mem = std.mem; |
| 5 | |
| 6 | pub const HmacMd5 = Hmac(crypto.hash.Md5); |
| 7 | pub const HmacSha1 = Hmac(crypto.hash.Sha1); |
| 8 | |
| 9 | pub const sha2 = struct { |
| 10 | pub const HmacSha224 = Hmac(crypto.hash.sha2.Sha224); |
| 11 | pub const HmacSha256 = Hmac(crypto.hash.sha2.Sha256); |
| 12 | pub const HmacSha384 = Hmac(crypto.hash.sha2.Sha384); |
| 13 | pub const HmacSha512 = Hmac(crypto.hash.sha2.Sha512); |
| 14 | }; |
| 15 | |
| 16 | pub fn Hmac(comptime Hash: type) type { |
| 17 | return struct { |
| 18 | const Self = @This(); |
| 19 | pub const mac_length = Hash.digest_length; |
| 20 | pub const key_length_min = 0; |
| 21 | pub const key_length = mac_length; // recommended key length |
| 22 | |
| 23 | o_key_pad: [Hash.block_length]u8, |
| 24 | hash: Hash, |
| 25 | |
| 26 | // HMAC(k, m) = H(o_key_pad || H(i_key_pad || msg)) where || is concatenation |
| 27 | pub fn create(out: *[mac_length]u8, msg: []const u8, key: []const u8) void { |
| 28 | var ctx = Self.init(key); |
| 29 | ctx.update(msg); |
| 30 | ctx.final(out); |
| 31 | } |
| 32 | |
| 33 | pub fn init(key: []const u8) Self { |
| 34 | var ctx: Self = undefined; |
| 35 | var scratch: [Hash.block_length]u8 = undefined; |
| 36 | var i_key_pad: [Hash.block_length]u8 = undefined; |
| 37 | |
| 38 | // Normalize key length to block size of hash |
| 39 | if (key.len > Hash.block_length) { |
| 40 | Hash.hash(key, scratch[0..mac_length], .{}); |
| 41 | @memset(scratch[mac_length..Hash.block_length], 0); |
| 42 | } else if (key.len < Hash.block_length) { |
| 43 | @memcpy(scratch[0..key.len], key); |
| 44 | @memset(scratch[key.len..Hash.block_length], 0); |
| 45 | } else { |
| 46 | @memcpy(&scratch, key); |
| 47 | } |
| 48 | |
| 49 | for (&ctx.o_key_pad, 0..) |*b, i| { |
| 50 | b.* = scratch[i] ^ 0x5c; |
| 51 | } |
| 52 | |
| 53 | for (&i_key_pad, 0..) |*b, i| { |
| 54 | b.* = scratch[i] ^ 0x36; |
| 55 | } |
| 56 | |
| 57 | ctx.hash = Hash.init(.{}); |
| 58 | ctx.hash.update(&i_key_pad); |
| 59 | return ctx; |
| 60 | } |
| 61 | |
| 62 | pub fn update(ctx: *Self, msg: []const u8) void { |
| 63 | ctx.hash.update(msg); |
| 64 | } |
| 65 | |
| 66 | pub fn final(ctx: *Self, out: *[mac_length]u8) void { |
| 67 | var scratch: [mac_length]u8 = undefined; |
| 68 | ctx.hash.final(&scratch); |
| 69 | var ohash = Hash.init(.{}); |
| 70 | ohash.update(&ctx.o_key_pad); |
| 71 | ohash.update(&scratch); |
| 72 | ohash.final(out); |
| 73 | } |
| 74 | }; |
| 75 | } |
| 76 | |
| 77 | const htest = @import("test.zig"); |
| 78 | |
| 79 | test "md5" { |
| 80 | var out: [HmacMd5.mac_length]u8 = undefined; |
| 81 | HmacMd5.create(out[0..], "", ""); |
| 82 | try htest.assertEqual("74e6f7298a9c2d168935f58c001bad88", out[0..]); |
| 83 | |
| 84 | HmacMd5.create(out[0..], "The quick brown fox jumps over the lazy dog", "key"); |
| 85 | try htest.assertEqual("80070713463e7749b90c2dc24911e275", out[0..]); |
| 86 | } |
| 87 | |
| 88 | test "sha1" { |
| 89 | var out: [HmacSha1.mac_length]u8 = undefined; |
| 90 | HmacSha1.create(out[0..], "", ""); |
| 91 | try htest.assertEqual("fbdb1d1b18aa6c08324b7d64b71fb76370690e1d", out[0..]); |
| 92 | |
| 93 | HmacSha1.create(out[0..], "The quick brown fox jumps over the lazy dog", "key"); |
| 94 | try htest.assertEqual("de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9", out[0..]); |
| 95 | } |
| 96 | |
| 97 | test "sha256" { |
| 98 | var out: [sha2.HmacSha256.mac_length]u8 = undefined; |
| 99 | sha2.HmacSha256.create(out[0..], "", ""); |
| 100 | try htest.assertEqual("b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad", out[0..]); |
| 101 | |
| 102 | sha2.HmacSha256.create(out[0..], "The quick brown fox jumps over the lazy dog", "key"); |
| 103 | try htest.assertEqual("f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8", out[0..]); |
| 104 | } |