Perl SHA-256 and HMAC with Digest::SHA
Hash messages, authenticate them with a key, and compare tags without leaking timing.
On this page
Perl scalars and modules.
- Hash messages, authenticate them with a key, and compare tags without leaking timing.
- Predict the sample output and explain the state changes.
- Complete the practice task and check the boundary cases.
The concept
Digest::SHA is part of core Perl. sha256_hex returns the SHA-256 digest of a string as 64 hexadecimal characters. A cryptographic hash is deterministic and one-way, and any change to the input gives a completely different digest, so hashes are used to check integrity. Plain hashes are not suitable for storing passwords because they are fast to guess.
hmac_sha256_hex takes the data and then the key, in that order, and produces a keyed tag. Only someone who knows the key can create a valid tag, so a matching tag proves both integrity and authenticity. The tag below is the RFC 4231 test value for key Jefe. When comparing a received tag with the expected one, do not stop at the first different character, because the time taken could reveal how much matched. The secure_equal subroutine looks at every character.
Code example
use strict;
use warnings;
use Digest::SHA qw(sha256_hex hmac_sha256_hex);
print sha256_hex("abc"), "\n";
print sha256_hex("abd"), "\n";
sub secure_equal {
my ($x, $y) = @_;
return 0 unless length($x) == length($y);
my $diff = 0;
$diff |= ord(substr($x, $_, 1)) ^ ord(substr($y, $_, 1)) for 0 .. length($x) - 1;
return $diff == 0;
}
my $message = "what do ya want for nothing?";
my $tag = hmac_sha256_hex($message, "Jefe");
print "$tag\n";
print "valid: ", (secure_equal($tag, hmac_sha256_hex($message, "Jefe")) ? "yes" : "no"), "\n";
print "forged: ", (secure_equal($tag, hmac_sha256_hex("what do ya want for something?", "Jefe")) ? "yes" : "no"), "\n";ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad a52d159f262b2c6ddb724a61840befc36eb30c88877a4030b65cbe86298449c9 5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843 valid: yes forged: no
Follow the code
Open the walkthrough to inspect the example one step at a time.
Show guided walkthrough
use strict;use warnings;use Digest::SHA qw(sha256_hex hmac_sha256_hex); print sha256_hex("abc"), "\n";print sha256_hex("abd"), "\n"; sub secure_equal { my ($x, $y) = @_; return 0 unless length($x) == length($y); my $diff = 0; $diff |= ord(substr($x, $_, 1)) ^ ord(substr($y, $_, 1)) for 0 .. length($x) - 1; return $diff == 0;} my $message = "what do ya want for nothing?";my $tag = hmac_sha256_hex($message, "Jefe");print "$tag\n";print "valid: ", (secure_equal($tag, hmac_sha256_hex($message, "Jefe")) ? "yes" : "no"), "\n";print "forged: ", (secure_equal($tag, hmac_sha256_hex("what do ya want for something?", "Jefe")) ? "yes" : "no"), "\n";Follow the example
Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.
View the complete dry-run table
| Step / line | What happens | State after step | Output so far |
|---|---|---|---|
| A standard SHA-256 test vector. | — | ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad | |
| One changed letter gives an unrelated digest. | — | ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad a52d159f262b2c6ddb724a61840befc36eb30c88877a4030b65cbe86298449c9 | |
| XOR the characters pair by pair and OR the differences together. The loop always visits every position. | — | ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad a52d159f262b2c6ddb724a61840befc36eb30c88877a4030b65cbe86298449c9 | |
| The data comes first and the key second. | — | ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad a52d159f262b2c6ddb724a61840befc36eb30c88877a4030b65cbe86298449c9 | |
| The published RFC 4231 value. | — | ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad a52d159f262b2c6ddb724a61840befc36eb30c88877a4030b65cbe86298449c9 5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843 | |
| Recomputing the tag with the same key gives the same value. | — | ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad a52d159f262b2c6ddb724a61840befc36eb30c88877a4030b65cbe86298449c9 5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843 valid: yes | |
| Changing the message changes the tag, so the forgery is detected. | — | ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad a52d159f262b2c6ddb724a61840befc36eb30c88877a4030b65cbe86298449c9 5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843 valid: yes forged: no |
Behind the scenes
How Perl processes this code
Perl compiles the script internally before executing it. strict and warnings help identify mistakes.
Cost and common mistakes
O(m) for a message of m bytes.
- The argument order of hmac_sha256_hex is data, then key. Swapping them gives a valid-looking but wrong tag.
- Never use eq to compare secrets such as tags or tokens; use a constant-time comparison.
- SHA-256 alone is not a password hash. Use a slow, salted algorithm such as bcrypt, scrypt, or Argon2 from a maintained module.
Try it yourself
What does an HMAC prove that a plain hash does not?
Reveal answer and reasoning
It proves the sender knew the secret key, so the message is authentic as well as unmodified.