Perl · Course navigation

Perl SHA-256 and HMAC with Digest::SHA

Hash messages, authenticate them with a key, and compare tags without leaking timing.

On this page
Before you start

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

Perl
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";
Output
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
Guided walkthroughReady
1use strict;
2use warnings;
3use Digest::SHA qw(sha256_hex hmac_sha256_hex);
4
5print sha256_hex("abc"), "\n";
6print sha256_hex("abd"), "\n";
7
8sub secure_equal {
9 my ($x, $y) = @_;
10 return 0 unless length($x) == length($y);
11 my $diff = 0;
12 $diff |= ord(substr($x, $_, 1)) ^ ord(substr($y, $_, 1)) for 0 .. length($x) - 1;
13 return $diff == 0;
14}
15
16my $message = "what do ya want for nothing?";
17my $tag = hmac_sha256_hex($message, "Jefe");
18print "$tag\n";
19print "valid: ", (secure_equal($tag, hmac_sha256_hex($message, "Jefe")) ? "yes" : "no"), "\n";
20print "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
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput 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.

Further reading