// 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; // (:glance): also loaded by the glance, which shows the favourite code (GlanceCode). (:glance) 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(); } }