Home  >  Article  >  Backend Development  >  hmac.New(h func() hash.Hash, key byte) hash.Hash equivalent in JavaScript

hmac.New(h func() hash.Hash, key byte) hash.Hash equivalent in JavaScript

WBOY
WBOYforward
2024-02-09 08:12:18456browse

hmac.New(h func() hash.Hash, key byte) hash.Hash 在 JavaScript 中等效

In PHP, we often need to use encryption algorithms to protect data security. HMAC (Hash-based Message Authentication Code) is a commonly used encryption algorithm used to verify data integrity and identity authentication. In PHP, we can create an HMAC instance using the hmac.New() function, which requires specifying a hash function and a key. Similarly, in JavaScript, we can use equivalent methods to achieve the same functionality. In this article, I'll show you how to create an HMAC instance using equivalent methods in JavaScript, as well as how to encrypt and decrypt data between PHP and JavaScript.

Question content

I almost got stuck in the js implementation of go lang hmac.new for several days. However, there was no success. I use the crypto, crypto-js and stablelib modules for implementation. The problem is that in go lang version, hmac instances can be created from hmac instances. For example (code block is correct and tested):

hmacf := hmac.New(func() hash.Hash {
    return hmac.New(func() hash.Hash {
        return hmac.New(func() hash.Hash {
            return hmac.New(sha256.New, []byte(SALT))
        }, []byte(path[0]))
    }, []byte(path[1]))
}, []byte(path[2]))

Actually, I don’t know how it works either! Because in all javascript related modules you cannot create hmac from hmac and they accept string values ​​that determine the hashing algorithm.

Maybe it's better to ask how to create hmac from hmac in javascript.

What is the solution?

When the output of the go version is the same as the output of your implementation; your solution is correct.

Workaround

According to the specification (rfc 2104), hmac uses digest functions internally, such as sha256.

However, your implementation applies (and is actually incompatible with) the hmac that uses another hmac internally instead of digest, where only the lowest level hmac uses regular digest internally. This creates a nested structure.

Based on the specification of regular hmac (with digest), this can be extended to hmac with hmac (instead of digest) used in go code:

hmac(k xor opad, hmac(k xor ipad, text)) s. rfc2104, Section 2. Definition of hmac

Due to differences from the specification, it may not be that easy to find a javascript library that supports such functionality out of the box.
While most libraries certainly support hmac, only allow specifying the digest (not hmac), e.g. nodejs's crypto module's crypto.createhmac(), see also Others Answer. I don't think this approach can be used to implement logic in go code.

If the other answers' approaches don't work, and you can't find another javascript library with the functionality you want, you can implement the logic in javascript yourself, since hmac's specification is relatively simple (see above).

The following code is an example implementation of the crypto module of nodejs:

var crypto = require('crypto')

const digest = 'sha256'
const blocksize = 64 // block size of the digest

// define input parameter
var salt = buffer.from('salt')
var path = [ buffer.from('alfa'), buffer.from('beta'), buffer.from('gamma') ]
var data = buffer.from('data')

// calculate hmac
var hmac = hmac(data, salt, path)
console.log(hmac.tostring('hex'))

function hmac(data, salt, path) {
    
    // create keylist
    var keylist = []
    keylist.push(salt)
    keylist = keylist.concat(path)

    // determine hmac recursively
    var result = hmac_rec(data, keylist)
    return result
}

function hmac_rec(data, keylist) {

    // adjust key (according to hmac specification)
    var key = keylist.pop()
    if (key.length > blocksize) {        
        k = buffer.allocunsafe(blocksize).fill('\x00');
        if (keylist.length > 0) {
            hmac_rec(key, [...keylist]).copy(k)
        } else {
            gethash(key).copy(k)
        }
    } else if (key.length < blocksize) {
        k = buffer.allocunsafe(blocksize).fill('\x00');
        key.copy(k)
    } else {
        k = key
    }

    // create 'key xor ipad' and 'key xor opad' (according to hmac specification)  
    var ik = buffer.allocunsafe(blocksize)
    var ok = buffer.allocunsafe(blocksize)
    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
    if (keylist.length > 0) {
        var innerhmac = hmac_rec(buffer.concat([ ik, data ]), [...keylist]) 
        var outerhmac = hmac_rec(buffer.concat([ ok, innerhmac ]), [...keylist])
    } else {
        var innerhmac = gethash(buffer.concat([ik, data]))
        var outerhmac = gethash(buffer.concat([ok, innerhmac]))
    }
  
    return outerhmac 
}

// calculate sha256 hash
function gethash(data){
    var hash = crypto.createhash(digest);
    hash.update(data)
    return hash.digest()
}

result:

2e631dcb4289f8256861a833ed985fa945cd714ebe7c3bd4ed4b4072b107b073

test:

The following go code produces the same result:

package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "fmt"
    "hash"
)

func main() {
    salt := "salt"
    path := []string{"alfa", "beta", "gamma"}
    hmacf := hmac.new(func() hash.hash {
        return hmac.new(func() hash.hash {
            return hmac.new(func() hash.hash {
                return hmac.new(sha256.new, []byte(salt))
            }, []byte(path[0]))
        }, []byte(path[1]))
    }, []byte(path[2]))
    hmacf.write([]byte("data"))
    result := hmacf.sum(nil)
    fmt.println(hex.encodetostring(result)) // 2e631dcb4289f8256861a833ed985fa945cd714ebe7c3bd4ed4b4072b107b073
}

edit:

Inspired by this article, here is a more compact/efficient implementation of hmac_rec() that uses hmac for the regular last iteration step (which also makes gethash() Obsolete):

function hmac_rec(data, keyList) {
    var key = keyList.pop()
    if (keyList.length > 0) {
        
        // adjust key (according to HMAC specification)
        if (key.length > blockSize) {        
            k = Buffer.allocUnsafe(blockSize).fill('\x00');
            hmac_rec(key, [...keyList]).copy(k)
        } else if (key.length < blockSize) {
            k = Buffer.allocUnsafe(blockSize).fill('\x00');
            key.copy(k)
        } else {
            k = key
        }
    
        // create 'key xor ipad' and 'key xor opad' (according to HMAC specification)  
        var ik = Buffer.allocUnsafe(blockSize)
        var ok = Buffer.allocUnsafe(blockSize)
        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
        var innerHMac = hmac_rec(Buffer.concat([ ik, data ]), [...keyList]) 
        var outerHMac = hmac_rec(Buffer.concat([ ok, innerHMac ]), [...keyList])
    } else {
        var outerHMac = crypto.createHmac(digest, key).update(data).digest();
    }  
    return outerHMac 
}

The above is the detailed content of hmac.New(h func() hash.Hash, key byte) hash.Hash equivalent in JavaScript. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:stackoverflow.com. If there is any infringement, please contact admin@php.cn delete