summaryrefslogtreecommitdiff
path: root/source/crypto/Hmac.mc
diff options
context:
space:
mode:
Diffstat (limited to 'source/crypto/Hmac.mc')
-rw-r--r--source/crypto/Hmac.mc69
1 files changed, 69 insertions, 0 deletions
diff --git a/source/crypto/Hmac.mc b/source/crypto/Hmac.mc
new file mode 100644
index 0000000..ff620a4
--- /dev/null
+++ b/source/crypto/Hmac.mc
@@ -0,0 +1,69 @@
+// Author: cirakg & claude ai
+// Summary: HMAC (RFC 2104) over SHA-1, SHA-256 and SHA-512; native HMAC-SHA256 where Connect IQ provides it.
+//
+// Connect IQ's native HMAC accepts only SHA-256 (HMAC-SHA1 is rejected with
+// "Invalid :algorithm option value", verified with SDK 9.2.0). Most TOTP
+// secrets use SHA-1, so compute() builds HMAC from the hash functions as
+// RFC 2104 defines it:
+// HMAC(K, m) = H((K' xor opad) || H((K' xor ipad) || m))
+// where K' is the key padded to the hash's block size (hashed first if it is
+// longer than a block), ipad = 0x36 and opad = 0x5c repeated.
+//
+// Security notes: the padded key copies are wiped after use; the caller's key
+// array is never modified. Verified with RFC 2202 (SHA-1) and RFC 4231
+// (SHA-256, SHA-512), test/crypto/HmacTest.mc.
+
+import Toybox.Cryptography;
+import Toybox.Lang;
+
+module Hmac {
+
+ const IPAD = 0x36;
+ const OPAD = 0x5c;
+
+ // HMAC of message under key. SHA-256 uses the native implementation; SHA-1
+ // and SHA-512 use generic().
+ function compute(alg as Hashes.Algorithm, key as ByteArray, message as ByteArray) as ByteArray {
+ if (alg == Hashes.SHA256) {
+ return nativeSha256(key, message);
+ }
+ return generic(alg, key, message);
+ }
+
+ // RFC 2104 HMAC built on Hashes.digest, for any supported algorithm.
+ function generic(alg as Hashes.Algorithm, key as ByteArray, message as ByteArray) as ByteArray {
+ var block = Hashes.blockSize(alg);
+ var hashedKey = key.size() > block ? Hashes.digest(alg, key) : null;
+ var k = hashedKey != null ? hashedKey : key;
+
+ var inner = new [block]b;
+ var outer = new [block]b;
+ for (var i = 0; i < block; i++) {
+ var keyByte = i < k.size() ? k[i] : 0;
+ inner[i] = keyByte ^ IPAD;
+ outer[i] = keyByte ^ OPAD;
+ }
+ inner.addAll(message);
+ var innerHash = Hashes.digest(alg, inner);
+ outer.addAll(innerHash);
+ var mac = Hashes.digest(alg, outer);
+
+ Bytes.wipe(inner);
+ Bytes.wipe(outer);
+ Bytes.wipe(innerHash);
+ Bytes.wipe(hashedKey);
+ return mac;
+ }
+
+ // Native HMAC-SHA256 (Toybox.Cryptography).
+ function nativeSha256(key as ByteArray, message as ByteArray) as ByteArray {
+ var mac = new Cryptography.HashBasedMessageAuthenticationCode({
+ :algorithm => Cryptography.HASH_SHA256,
+ :key => key
+ });
+ if (message.size() > 0) {
+ mac.update(message);
+ }
+ return mac.digest();
+ }
+}