lib/reticulum/src/crypto/hmac.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2
3 const HmacSha256 = std.crypto.auth.hmac.sha2.HmacSha256;
4
5 pub const tag_length: u8 = HmacSha256.mac_length;
6
7 /// An HMAC-SHA256 state that takes its input in pieces and then gives the
8 /// 32-byte tag. The key derivation feeds three pieces into one of these for
9 /// every output block, so it needs no buffer holding them together.
10 pub const Signer = struct {
11 state: HmacSha256,
12
13 pub fn init(key: []const u8) Signer {
14 return .{ .state = HmacSha256.init(key) };
15 }
16
17 pub fn update(self: *Signer, data: []const u8) void {
18 self.state.update(data);
19 }
20
21 pub fn final(self: *Signer) [tag_length]u8 {
22 var tag: [tag_length]u8 = undefined;
23 self.state.final(&tag);
24 return tag;
25 }
26 };
27
28 /// Returns the 32-byte HMAC-SHA256 tag of some data under a key, following
29 /// Reticulum@1.5.0 RNS/Cryptography/HMAC.py:27-45.
30 pub fn sign(key: []const u8, data: []const u8) [tag_length]u8 {
31 var signer = Signer.init(key);
32 signer.update(data);
33 return signer.final();
34 }
35
36 /// Returns whether a tag matches the one the key and data give, following
37 /// Reticulum@1.5.0 RNS/Cryptography/HMAC.py:185-188. The comparison takes the
38 /// same time whatever the bytes are, so a failure says nothing about how far
39 /// the tag matched.
40 pub fn verify(key: []const u8, data: []const u8, tag: [tag_length]u8) bool {
41 const expected = sign(key, data);
42 return std.crypto.timing_safe.eql([tag_length]u8, expected, tag);
43 }