How can I decrypt a HMAC?

98,637

Solution 1

HMAC is a MAC/keyed hash, not a cipher. It's not designed to be decrypted. If you want to encrypt something, use a cipher, like AES, preferably in an authenticated mode like AES-GCM.

The only way to "decrypt" is guessing the whole input and then comparing the output.

Solution 2

Again to reiterate hashes aren't designed to be decrypted. However once you have a hash you can check any string is equal to that hash by putting it through the same encryption with the same secret.

var crypto = require('crypto')

var secret = 'alpha'
var string = 'bacon'

var hash = crypto.createHmac('SHA256', secret).update(string).digest('base64');
// => 'IbNSH3Lc5ffMHo/wnQuiOD4C0mx5FqDmVMQaAMKFgaQ='

if (hash === crypto.createHmac('SHA256', secret).update(string).digest('base64')) {
  console.log('match') // logs => 'match'
} else {
  console.log('no match')
}

Seems obvious, but very powerful.

Solution 3

As already been stated by CodesInChaos, HMAC with SHA256 can only be used to hash a value, which is a one-way trip only. If you want to be able to encrypt/decrypt you will have to use a cipher, such as aes or des.

Example on how encryption/decryption:

const crypto = require("crypto");

// key and iv   
var key = crypto.createHash("sha256").update("OMGCAT!", "ascii").digest();
var iv = "1234567890123456";

// this is the string we want to encrypt/decrypt
var secret = "ermagherd";

console.log("Initial: %s", secret);

// create a aes256 cipher based on our password
var cipher = crypto.createCipheriv("aes-256-cbc", key, iv);
// update the cipher with our secret string
cipher.update(secret, "ascii");
// save the encryption as base64-encoded
var encrypted = cipher.final("base64");

console.log("Encrypted: %s", encrypted);

// create a aes267 decipher based on our password
var decipher = crypto.createDecipheriv("aes-256-cbc", key, iv);
// update the decipher with our encrypted string
decipher.update(encrypted, "base64");

console.log("Decrypted: %s", decipher.final("ascii"));

Note: You have to save the cipher/decipher into their own variable, and also make sure not to chain .final after .update.

If you want to know what ciphers are available on your system, use the following command:

openssl list-cipher-algorithm
Share:
98,637

Related videos on Youtube

ThomasReggi
Author by

ThomasReggi

Updated on October 03, 2020

Comments

  • ThomasReggi
    ThomasReggi over 3 years

    I can make an HMAC using the following:

    var encrypt = crypto.createHmac("SHA256", secret).update(string).digest('base64');
    

    I am trying to decrypt an encoded HMAC with the secret:

    var decrypt = crypto.createDecipher("SHA256", secret).update(string).final("ascii");
    

    The following was unsuccessful. How can I decrypt a HMAC with the key?

    I get the following error:

    node-crypto : Unknown cipher SHA256
    
    crypto.js:155
      return (new Decipher).init(cipher, password);
                            ^
    Error: DecipherInit error
    
  • Justin Ethier
    Justin Ethier over 11 years
    Even then, you cannot decrypt it because multiple keys can hash to the same value. A hash is a one-way function. For more information: en.wikipedia.org/wiki/Hash_function
  • CodesInChaos
    CodesInChaos over 11 years
    @JustinEthier In many cases the input space is small enough that guessing works. In practice you will never guess a wrong input with the correct hash. You will either guess the correct input, or you won't find a matching input at all. | But since HMAC is keyed, only those who know the key can use this guessing attack, so this usually isn't a problem (with unkeyed hashes guessing the input is often a problem).
  • CodesInChaos
    CodesInChaos over 11 years
    Which chaining mode does that use? The lack of salt/iv is weird too.
  • mekwall
    mekwall over 11 years
    @CodesInChaos .update doesn't return a Cipher object, so you can't do .update(data).final() as in his example. The lack of salt/iv isn't weird? You'd have to use .createCipheriv and .createDecipheriv for that.
  • CodesInChaos
    CodesInChaos over 11 years
    I asked for the cryptographic chaining mode. You can't just encrypt data with AES, you need a chaining mode and a padding mode. For example ECB, CBC, GCM,... are common chaining modes. ECB is broken, and when using CBC you usually need to add authentication by yourself, which you didn't. | Encrypting with a password and no salt is weird. Unless the API creates a different salt for each call internally, that's a security weakness. | So your code has good chances of being insecure by using a bad mode or the lack of salt/IV.
  • mekwall
    mekwall over 11 years
    @CodesInChaos Oh, sorry for misunderstanding. It's autopadded and uses ECB per default. Didn't know that ECB was broken, so will change to CBC. As for key and iv, they are derived from the password (so it matches his example). As for authentication, this doesn't seem to be necessary in this case.
  • CodesInChaos
    CodesInChaos over 11 years
    Encryption without authentication is broken in many use cases. You can simply use a padding oracle(or similar) to get the recipient to decrypt it for you. | Deriving key+iv deterministically from the password means that you should only encrypt a single message with that password, since else you reuse a key+iv pair, which is bad. | Seems like the node.js crypto API is badly designed[like so many other crypto libs], since it's easy to create weak encryption with it, and hard to create strong encryption.
  • Maarten Bodewes
    Maarten Bodewes over 11 years
    -1 (sorry) for insecure example on how to use cryptography. It's not as broken as the examples in the official PHP API, but that's about it.
  • mekwall
    mekwall over 11 years
    @owlstead Updated to use key/iv. When it comes to authentication, I don't really think it's relevant to his question.
  • Maarten Bodewes
    Maarten Bodewes over 11 years
    @MarcusEkwall OK, removed downvote, but it should be a secure random IV, of course. Static IV's are of little help. If possible, you should also deploy a PBKDF instead of a hash to derive the key, but that is of somewhat less significance - if the password has enough entropy of course.
  • Maarten Bodewes
    Maarten Bodewes over 8 years
    Hmm, true, I've moved forward since that comment, destroying it.
  • KK.
    KK. over 6 years
    Clean-up of code to minimum w/o no need to introduce new concept iv:
  • TatianaP
    TatianaP over 5 years
    Not worked for long strings (secret). Needed update and final concatenate into a result: var encrypted = cipher.update(secret, "ascii", "base64") + cipher.final("base64"); var decrypted = decipher.update(encrypted, "base64", "ascii") + decipher.final("ascii");