diff options
| author | cirakg <cirakg@cirakg.xyz> | 2026-10-18 23:44:18 +0200 |
|---|---|---|
| committer | cirakg <cirakg@cirakg.xyz> | 2026-10-18 23:44:18 +0200 |
| commit | e47ae684101981a7c20f3270994a61944059c825 (patch) | |
| tree | c2baab8baafa1be446dad7f224f1a1cf68493825 /source/crypto/Hmac.mc | |
| parent | b70cde45b67e1f6bb65d598d25c1a5184bdab520 (diff) | |
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 <noreply@anthropic.com>
Diffstat (limited to 'source/crypto/Hmac.mc')
| -rw-r--r-- | source/crypto/Hmac.mc | 69 |
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(); + } +} |
