From e47ae684101981a7c20f3270994a61944059c825 Mon Sep 17 00:00:00 2001 From: cirakg Date: Sun, 18 Oct 2026 23:44:18 +0200 Subject: feat(crypto): add SHA-512, HMAC, PBKDF2, HKDF, AES-256 records and self-test - Sha512: FIPS 180-4 in Monkey C (no native SHA-512), rotations inlined with precomputed masks; constants derived from the primes, not typed. - Hmac: RFC 2104 over SHA-1/256/512 (native HMAC only supports SHA-256). - Kdf: resumable PBKDF2-HMAC-SHA256 job with calibration bounds (2,000-200,000 iterations) and HKDF-SHA256. - Aes: CBC with PKCS#7 over the native cipher; AES-256 chosen. - SealedBox: encrypt-then-MAC records (HKDF-split keys, length-prefixed aad, constant-time tag check before decryption). - SelfTest: known-answer tests for every primitive at start-up. - Bytes (wipe, constant-time equal), Clock (2038-safe widening, trusted clock), Jobs (bounded steps against the watchdog, timer runner). Co-Authored-By: Claude Opus 5.5 --- source/crypto/Hmac.mc | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 source/crypto/Hmac.mc (limited to 'source/crypto/Hmac.mc') 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(); + } +} -- cgit v1.2.3