aboutsummaryrefslogtreecommitdiff
path: root/source/crypto/Hmac.mc
blob: 736d2d4e186bb3d13c1b7e3bcda4b404f9cb9665 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// 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();
    }
}