Have `hmac` outputs raw binary data rather than hexits lowercase

I’m really grateful for the hmac function but there is something I can’t achieve. It is to output the same string as PHP’s own hmac function with its “raw binary data” mode enabled.

{{ $test := hmac "sha256" "My Secret" "My Message" | base64Encode }}

outputs:
MGM4YjY0MTVhZDdkOGY4NTU4OTgxMDgyMDRmMzk3ZDZkODc2ZTJlODQ2MWY0NTZhM2JmYjAzMDJjODEwMzZiOA==

echo base64_encode(hash_hmac('sha256', "My Message", "My Secret", false));

outputs: MGM4YjY0MTVhZDdkOGY4NTU4OTgxMDgyMDRmMzk3ZDZkODc2ZTJlODQ2MWY0NTZhM2JmYjAzMDJjODEwMzZiOA==

All good, with the default data mode (hexits lowercase) Hugo and PHP get the same result. Unfortunately, it seems what I need is the output I get with the following where the last parameter is set to true (raw data):

echo base64_encode(hash_hmac('sha256', "My Message", "My Secret", true));

outputs: DItkFa19j4VYmBCCBPOX1th24uhGH0VqO/sDAsgQNrg=

Is there a way to achieve the same result with Hugo?

Yes the argument order is right (different in PHP).

For context, I’m desperately trying to work Apple News API and their security policy requires hmac on various concatenated parameters. I can successfully pass the auth with the php raw data method, but not with Hugo’s own.

Thanks a ton!

1 Like

What if we added an optional fourth (bool) arg?

{{ hmac "sha256" "My Secret" "My Message" true | encoding.Base64Encode }}

Or, we could add a HexDecode method to the encoding namespace, which wouldn’t mess with the piping sequence:

{{ "My Message" | hmac "sha256" "My Secret" | encoding.HexDecode | encoding.Base64Encode }}

encoding.HexDecode and encoding.HexEncode might also be useful for color conversions. These were implemented (but not merged) by @moorereason in #7605. I suspect these would be used infrequently, so I’m not sure it makes sense to give them aliases (e.g., hexDecode).

I guess if encoding.HexDecode can be useful elsewhere, this should be the way to go! I’m fairly new to this encoding/decoding methods…

Please confirm…

This:

{{ "My Message" | hmac "sha256" "My Secret" | encoding.HexDecode | encoding.Base64Encode }}

should produce this:

DItkFa19j4VYmBCCBPOX1th24uhGH0VqO/sDAsgQNrg=

Yes that’s the output of this code:

<?php
echo base64_encode(hash_hmac('sha256', "My Message", "My Secret", true));

which I just dumped in an online PHP sandbox.

As for what encoding.HexCode actually does. I leave this to you but this is the definition of the PHP function’s last parameter:

binary:
When set to true , outputs raw binary data. false outputs lowercase hexits.

Thank you so much for considering this!

Upon further consideration I think it’s better to add an optional arg to the hmac function. We’re dealing with strings not integers, so encode/decode functions are not going to be very useful in other contexts.

But I don’t really like the idea of a boolean arg (e.g., the PHP implementation).

What about this signature instead?

hmac HASH_TYPE KEY MESSAGE [ENCODING]

The ENCODING arg could be “hex” (default, current behavior) or “binary”. The only downside is that you can’t pipe the MESSAGE when specifying ENCODING.

{{ hmac "sha256" "My Secret" "My Message" "binary" | encoding.Base64Encode }}

Is it completely crazy to create new hash_type: sha256-hex md5-hex?

{{ "My Message" |  hmac "sha256-hex" "My Secret"  | encoding.Base64Encode }}

That would eliminate the need for an optional arg, but I think it makes things more complicated. We would need to handle:

  • md5, md5-hex, md5-binary
  • sha1, sha1-hex, sha1-binary
  • sha256, sha256-hex, shaw256-binary
  • sha512, sha512-hex, shaw512-binary

And if we want to add another hash type (e.g., sha384) or encoding option in the future, things get a bit ugly.

Follow-up to previous post: if we add the optional arg, should the options be “hex” and “binary”, or “hex” and “bin”?

My 50 cents:

  1. I think template funcs work best if they have 1 argument
  2. I think template funcs work best if they do 1 thing only
  3. I suspect that someone would want to use this new thing in a non-hmac setting

That said, wouldn’t this work:

{{ printf "%x" ("My Message" | hmac "sha256" "My Secret")  | encoding.Base64Encode }}

That produces:

MzA2MzM4NjIzNjM0MzEzNTYxNjQzNzY0Mzg2NjM4MzUzNTM4MzkzODMxMzAzODMyMzAzNDY2MzMzOTM3NjQzNjY0MzgzNzM2NjUzMjY1MzgzNDM2MzE2NjM0MzUzNjYxMzM2MjY2NjIzMDMzMzAzMjYzMzgzMTMwMzMzNjYyMzg=

Here’s an implementation with this signature:

hmac HASH_TYPE KEY MESSAGE [ENCODING]

https://github.com/gohugoio/hugo/pull/9710

No problem if this is rejected. I just wanted something functional to work from.

@regis Your example would be:

{{ hmac "sha256" "My Secret" "My Message" "binary" | base64Encode }}

which produces:

DItkFa19j4VYmBCCBPOX1th24uhGH0VqO/sDAsgQNrg=

Thanks, personally I also think the fewest parameters to a function the better but

  1. That would mean create hmac_sha256, hmac_md5 function etc rather than having this hmac catchall function.
  2. As much as I like Go Template Pipes, I don’t like that its usage dictates argument ordering for “non-pipe” usage…

I don’t think a lot of people are using the hmac function as it is, I think even fewer people are using it in a Go Template Pipe context. So we should be safe for breaking code.

I’m not sure what you mean. The proposed implementation is backwards compatible; this isn’t a breaking change. But if you need to specify ENCODING it must be the last arg.

I was thinking this might be only a breaking change for people who have been using this in their code… (very few people I would think):

{{ "My Message" | hmac "sha256" "My Secret" | base64Encode }}

As we now have new potential last argument, but as it’s optional it might not count?

I’m not sure why I added that other comment about pipe and argument ordering, it’s a general feeling I have but nothing in this thread invited this comment…

Thank you so much for implementing this, it’s perfect!

{{ "My Message" | hmac "sha256" "My Secret" | base64Encode }}

This will work as it always has, so we’re in good shape.

2 Likes