Search code examples
node.jsgohmac

Convert go lang hmac into nodejs version


I try to convert one go hmac usecase into nodejs, for normal hmac I know how to convert it into nodejs. But I have the below go code.

Note: go hmac.New first arg is a custom hash which is HMAC. But I didn't find any alternative in nodejs version.

Nodejs [createHmac][1] only support string hash algorithm, and can't support custom hash algorithm. Does anyone know how to implement the same feature in nodejs?

package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "fmt"
    "hash"
)
func main() {
    hmacf := hmac.New(func() hash.Hash {
        return hmac.New(sha256.New, []byte("inner-key1"))
    }, []byte("inner-key2"));

    hmacf.Write([]byte("message"))
    fmt.Println(hex.EncodeToString(hmacf.Sum(nil)))
}

| [1]: https://nodejs.org/api/crypto.html#cryptocreatehmacalgorithm-key-options


Solution

  • HMAC by definition uses a digest internally and not an HMAC. Therefore, the Go implementation is actually a generalization of the HMAC definition and it is not surprising that most libraries do not support this, just as it is not supported out-of-the-box by NodeJS.
    However, because of the comparatively simple definition of HMAC, it is easy to implement the required functionality.

    The following is a recursive implementation that uses the crypto library:

    const crypto = require('crypto')
    
    function hmac_rec(data, keyList) {
        const digest = 'sha256', blockSizeOfDigest = 64
        var key = keyList.pop()
        if (keyList.length > 0) {
            // adjust key (according to HMAC specification)
            if (key.length > blockSizeOfDigest) {k = Buffer.allocUnsafe(blockSizeOfDigest).fill('\x00'); hmac_rec(key, [...keyList]).copy(k)}
            else if (key.length < blockSizeOfDigest) {k = Buffer.allocUnsafe(blockSizeOfDigest).fill('\x00'); key.copy(k)} 
            else k = key
            // create 'key xor ipad' and 'key xor opad' (according to HMAC specification)  
            var ik = Buffer.allocUnsafe(blockSizeOfDigest), ok = Buffer.allocUnsafe(blockSizeOfDigest)
            k.copy(ik); k.copy(ok)
            for (var i = 0; i < ik.length; i++) {ik[i] = 0x36 ^ ik[i]; ok[i] = 0x5c ^ ok[i]}
            // calculate HMac(HMac)
            var innerHMac = hmac_rec(Buffer.concat([ ik, data ]), [...keyList]) 
            var hMac = hmac_rec(Buffer.concat([ ok, innerHMac ]), [...keyList])
        } else {
            // calculate regular HMac(Hash)
            var hMac = crypto.createHmac(digest, key).update(data).digest();
        }
        return hMac 
    }
    

    Test:

    var keyList = [ Buffer.from('inner-key1'), Buffer.from('inner-key2') ]
    var data = Buffer.from('message', 'utf8')
    var result = hmac_rec(data, keyList)
    console.log(result.toString('hex')) // 48d2fcee3d16024d053db80f3e7140b853159f69d15a66d4cd6fad907b44e0cf
    

    in accordance with the result of the posted Go Code.