The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
Changes 05
ConstantTime.xs 915
META.json 513
META.yml 36
MYMETA.json 513
MYMETA.yml 36
Makefile.PL 615
README 4564
README.pod 980
lib/String/Compare/ConstantTime.pm 1724
t/accuracy.t 15
11 files changed (This is a version diff) 192166
@@ -1,3 +1,8 @@
+0.310  2014-09-23
+  * Fix segfault when passed in undef (thanks Ichinose Shogo)
+  * Documentation updates
+  * Add github repo and license meta info
+
 0.300  2012-10-09
   * Change version number format
   * Skip unreliable stats test for now
@@ -34,16 +34,22 @@ equals(a, b)
         unsigned char *bp;
         int r;
 
-        alen = SvCUR(a);
-        ap = SvPV(a, alen);
-
-        blen = SvCUR(b);
-        bp = SvPV(b, blen);
-
-        if (alen == blen) {
-          r = !do_compare(ap, bp, alen);
-        } else {
+        if (SvOK(a) && SvOK(b)) {
+          alen = SvCUR(a);
+          ap = SvPV(a, alen);
+
+          blen = SvCUR(b);
+          bp = SvPV(b, blen);
+
+          if (alen == blen) {
+            r = !do_compare(ap, bp, alen);
+          } else {
+            r = 0;
+          }
+        } else if (SvOK(a) || SvOK(b)) {
           r = 0;
+        } else {
+          r = 1;
         }
 
         RETVAL = r;
@@ -4,9 +4,9 @@
       "unknown"
    ],
    "dynamic_config" : 1,
-   "generated_by" : "ExtUtils::MakeMaker version 6.62, CPAN::Meta::Converter version 2.112620",
+   "generated_by" : "ExtUtils::MakeMaker version 6.94, CPAN::Meta::Converter version 2.131560",
    "license" : [
-      "unknown"
+      "perl_5"
    ],
    "meta-spec" : {
       "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec",
@@ -22,12 +22,12 @@
    "prereqs" : {
       "build" : {
          "requires" : {
-            "ExtUtils::MakeMaker" : 0
+            "ExtUtils::MakeMaker" : "0"
          }
       },
       "configure" : {
          "requires" : {
-            "ExtUtils::MakeMaker" : 0
+            "ExtUtils::MakeMaker" : "0"
          }
       },
       "runtime" : {
@@ -35,5 +35,13 @@
       }
    },
    "release_status" : "stable",
-   "version" : "0.300"
+   "resources" : {
+      "bugtracker" : {
+         "web" : "https://github.com/hoytech/String-Compare-ConstantTime/issues"
+      },
+      "repository" : {
+         "url" : "git://github.com/hoytech/String-Compare-ConstantTime.git"
+      }
+   },
+   "version" : "0.310"
 }
@@ -7,8 +7,8 @@ build_requires:
 configure_requires:
   ExtUtils::MakeMaker: 0
 dynamic_config: 1
-generated_by: 'ExtUtils::MakeMaker version 6.62, CPAN::Meta::Converter version 2.112620'
-license: unknown
+generated_by: 'ExtUtils::MakeMaker version 6.94, CPAN::Meta::Converter version 2.131560'
+license: perl
 meta-spec:
   url: http://module-build.sourceforge.net/META-spec-v1.4.html
   version: 1.4
@@ -18,4 +18,7 @@ no_index:
     - t
     - inc
 requires: {}
-version: 0.300
+resources:
+  bugtracker: https://github.com/hoytech/String-Compare-ConstantTime/issues
+  repository: git://github.com/hoytech/String-Compare-ConstantTime.git
+version: 0.310
@@ -4,9 +4,9 @@
       "unknown"
    ],
    "dynamic_config" : 0,
-   "generated_by" : "ExtUtils::MakeMaker version 6.62, CPAN::Meta::Converter version 2.112620",
+   "generated_by" : "ExtUtils::MakeMaker version 6.94, CPAN::Meta::Converter version 2.131560",
    "license" : [
-      "unknown"
+      "perl_5"
    ],
    "meta-spec" : {
       "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec",
@@ -22,12 +22,12 @@
    "prereqs" : {
       "build" : {
          "requires" : {
-            "ExtUtils::MakeMaker" : 0
+            "ExtUtils::MakeMaker" : "0"
          }
       },
       "configure" : {
          "requires" : {
-            "ExtUtils::MakeMaker" : 0
+            "ExtUtils::MakeMaker" : "0"
          }
       },
       "runtime" : {
@@ -35,5 +35,13 @@
       }
    },
    "release_status" : "stable",
-   "version" : "0.300"
+   "resources" : {
+      "bugtracker" : {
+         "web" : "https://github.com/hoytech/String-Compare-ConstantTime/issues"
+      },
+      "repository" : {
+         "url" : "git://github.com/hoytech/String-Compare-ConstantTime.git"
+      }
+   },
+   "version" : "0.310"
 }
@@ -7,8 +7,8 @@ build_requires:
 configure_requires:
   ExtUtils::MakeMaker: 0
 dynamic_config: 0
-generated_by: 'ExtUtils::MakeMaker version 6.62, CPAN::Meta::Converter version 2.112620'
-license: unknown
+generated_by: 'ExtUtils::MakeMaker version 6.94, CPAN::Meta::Converter version 2.131560'
+license: perl
 meta-spec:
   url: http://module-build.sourceforge.net/META-spec-v1.4.html
   version: 1.4
@@ -18,4 +18,7 @@ no_index:
     - t
     - inc
 requires: {}
-version: 0.300
+resources:
+  bugtracker: https://github.com/hoytech/String-Compare-ConstantTime/issues
+  repository: git://github.com/hoytech/String-Compare-ConstantTime.git
+version: 0.310
@@ -2,11 +2,7 @@ use strict;
 
 use ExtUtils::MakeMaker;
 
-my $preop =
-    'perldoc -uT $(VERSION_FROM) | tee $(DISTVNAME)/README.pod > README.pod;' .
-    'pod2text README.pod | tee $(DISTVNAME)/README > README';
-
-WriteMakefile(
+my %args = (
     NAME              => 'String::Compare::ConstantTime',
     VERSION_FROM      => 'lib/String/Compare/ConstantTime.pm',
     PREREQ_PM         => {
@@ -15,7 +11,20 @@ WriteMakefile(
     DEFINE            => '',
     INC               => '-I.',
     OBJECT            => 'ConstantTime.o',
+    LICENSE           => 'perl',
     dist => {
-      PREOP => $preop,
+      PREOP => 'pod2text $(VERSION_FROM) > $(DISTVNAME)/README',
     },
 );
+
+my $eummv = eval ($ExtUtils::MakeMaker::VERSION);
+if ($eummv >= 6.45) {
+    $args{META_MERGE} = {
+        resources => {
+            repository => 'git://github.com/hoytech/String-Compare-ConstantTime.git',
+            bugtracker => 'https://github.com/hoytech/String-Compare-ConstantTime/issues',
+        },
+    };
+}
+
+WriteMakefile(%args);
@@ -4,6 +4,14 @@ NAME
 
 SYNOPSIS
         use String::Compare::ConstantTime;
+
+        if (String::Compare::ConstantTime::equals($secret_data, $user_supplied_data)) {
+          ## The strings are eq
+        }
+
+    An example with HMACs:
+
+        use String::Compare::ConstantTime;
         use Digest::HMAC_SHA1; ## or whatever
 
         my $hmac_ctx = Digest::HMAC_SHA1->new($key);
@@ -17,28 +25,29 @@ SYNOPSIS
 DESCRIPTION
     This module provides one function, "equals" (not exported by default).
 
-    You should pass this function two strings of the same length. It will
-    return true if they are string-wise identical and false otherwise, just
-    like "eq". However, comparing any two differing strings will take a
-    fixed amount of time, unlike "eq".
-
-    NOTE: If the lengths of the strings are different, "equals" will return
-    false right away. Also, comparing two identical strings will take a
-    different amount of time than comparing two differing strings.
+    You should pass this function two strings of the same length. Just like
+    perl's "eq", it will return true if they are string-wise identical and
+    false otherwise. However, comparing any two differing strings of the
+    same length will take a fixed amount of time. If the lengths of the
+    strings are different, "equals" will return false right away.
 
 TIMING SIDE-CHANNEL
     Some programs take different amounts of time to run depending on the
-    input values provided to them. When untrusted parties control input,
-    they might be able to learn information you might otherwise not want
-    them to know. This is called a "timing side-channel".
+    input values provided to them. Untrusted parties can sometimes learn
+    information you might not want them to know by measuring this time. This
+    is called a "timing side-channel".
 
     Most routines that compare strings (like perl's "eq" and "cmp" and C's
-    "strcmp" and "memcmp") start scanning from the start and terminate as
-    soon as they determine the strings won't match. This is good for
-    efficiency but bad because it opens a timing side-channel. If one of the
-    strings being compared is a secret and the other is controlled by some
-    untrusted party, it is sometimes possible for this untrusted party to
-    learn the secret using a timing side-channel.
+    "strcmp" and "memcmp") start scanning from the start of the strings and
+    terminate as soon as they determine the strings won't match. This is
+    good for efficiency but bad because it opens a timing side-channel. If
+    one of the strings being compared is a secret and the other is
+    controlled by some untrusted party, it is sometimes possible for this
+    untrusted party to learn the secret using a timing side-channel.
+
+    If the lengths of the strings are different, because "equals" returns
+    false right away the size of the secret string may be leaked (but not
+    its contents).
 
 HMAC
     HMACs are "Message Authentication Codes" built on top of cryptographic
@@ -52,68 +61,78 @@ HMAC
     digest is is the same as the candidate digest then the message is
     considered authenticated.
 
-    A very common side-channel attack against services that verify unlimited
+    A common side-channel attack against services that verify unlimited
     numbers of messages automatically is to create a forged message and then
     just send some random junk as the candidate digest. Continue sending
-    this message and the junk digest, varying the first character in the
-    digest. Repeat many times. If you find a particular digest that
-    statistically takes a longer time to be rejected than the other digests,
-    it is probably because this particular digest has the first character
-    correct and the service's final string comparison is running a little
-    longer.
+    this message and junk digests that vary by the first character. Repeat
+    many times. If you find a particular digest that statistically takes a
+    longer time to be rejected than the other digests, it is probably
+    because this particular digest has the first character correct and the
+    service's final string comparison is running slightly longer.
 
     At this point, you keep this first character fixed and start varying the
-    second character. Repeat until all the characters are solved or until
-    the amount of remaining possibilities are so small you can brute force
-    it. At this point, your candidate digest is considered valid and you
-    have forged a message.
+    second character until it is solved. Repeat until all the characters are
+    solved or until the amount of remaining possibilities are so small you
+    can brute force it. At this point, your candidate digest is considered
+    valid and you have forged a message.
 
     Note that this particular attack doesn't allow the attacker to recover
-    the secret input key, but the attacker can still use the service itself
-    to produce a valid digest for any message.
+    the secret input key to the HMAC but nevertheless can produce a valid
+    digest for any message given enough time because the service that
+    validates the HMAC is acting as an "oracle".
+
+    NOTE: Although this module protects against a common attack against
+    applications that store state in browser cookies, it is in no way an
+    endorsement of this practise.
 
 LOCK-PICKING ANALOGY
     Pin tumbler locks are susceptible to being picked in a similar way to an
     attacker forging HMAC digests using a timing side-channel.
 
-    The most common way to pick cheap pin tumbler locks is to apply torque
+    The traditional way to pick cheap pin tumbler locks is to apply torque
     to the lock cylinder so that the pins are pressed against the cylinder.
     However, because of slight manufacturing discrepancies one particular
-    pin will be the widest by a slight margin and will actually be the only
-    pin pressed against the cylinder (the cheaper the lock, the higher the
-    manufacturing "tolerances"). The attacker lifts this pin until the
+    pin will be the widest by a slight margin and will be pressed against
+    the cylinder tighter than the others (the cheaper the lock, the higher
+    the manufacturing tolerances). The attacker lifts this pin until the
     cylinder gives a little bit, indicating that this pin has been solved
     and the next widest pin is now the one being pressed against the
-    cylinder. This process is repeated until all the pins are solved and the
-    lock opens.
+    cylinder the tighest. This process is repeated until all the pins are
+    solved and the lock opens.
 
     Just like an attacker trying to solve HMAC digests can work on one
     character at a time, a lock pick can work on each pin in isolation. To
     protect against this, quality locks force all pins to be fixed into
-    place before the cylinder rotation can begin just as secure HMAC
+    place before the cylinder rotation can be attempted, just as secure HMAC
     verifiers force attackers to guess the entire digest on each attempt.
 
 SEE ALSO
-    <The String-Compare-ConstantTime github repo>
+    The String-Compare-ConstantTime github repo
+    <https://github.com/hoytech/String-Compare-ConstantTime>
 
     Authen::Passphrase has a good section on side-channel cryptanalysis such
-    as it pertains to password storage.
+    as it pertains to password storage (mostly, it doesn't).
 
-    <The famous TENEX password bug>
+    The famous TENEX password bug
+    <http://www.meadhbh.org/services/passwords>
 
-    <Example of a timing bug>
+    Example of a timing bug
+    <http://rdist.root.org/2009/05/28/timing-attack-in-google-keyczar-librar
+    y/>
 
-    <QSCAN>
+    QSCAN <http://hcsw.org/nmap/QSCAN>
 
-    <Practical limits of the timing side channel>
+    Practical limits of the timing side channel
+    <http://www.cs.rice.edu/~dwallach/pub/crosby-timing2009.pdf>
 
-    <NaCl: Crypto library designed to prevent side channel attacks>
+    NaCl: Crypto library designed to prevent side channel attacks
+    <http://nacl.cr.yp.to/>
 
 AUTHOR
     Doug Hoyte, "<doug@hcsw.org>"
 
 COPYRIGHT & LICENSE
-    Copyright 2012 Doug Hoyte.
+    Copyright 2012-2013 Doug Hoyte.
 
     This module is licensed under the same terms as perl itself.
 
@@ -1,98 +0,0 @@
-=encoding utf-8
-
-=head1 NAME
- 
-String::Compare::ConstantTime - Timing side-channel protected string compare
- 
-
-=head1 SYNOPSIS
-
-    use String::Compare::ConstantTime;
-    use Digest::HMAC_SHA1; ## or whatever
-
-    my $hmac_ctx = Digest::HMAC_SHA1->new($key);
-    $hmac_ctx->add($data);
-    my $digest = $hmac_ctx->digest;
-
-    if (String::Compare::ConstantTime::equals($digest, $candidate_digest)) {
-      ## The candidate digest is valid
-    }
-
-
-
-=head1 DESCRIPTION
-
-This module provides one function, C<equals> (not exported by default).
-
-You should pass this function two strings of the same length. It will return true if they are string-wise identical and false otherwise, just like C<eq>. However, comparing any two differing strings will take a fixed amount of time, unlike C<eq>.
-
-B<NOTE>: If the lengths of the strings are different, C<equals> will return false right away. Also, comparing two identical strings will take a different amount of time than comparing two differing strings.
-
-
-
-=head1 TIMING SIDE-CHANNEL
-
-Some programs take different amounts of time to run depending on the input values provided to them. When untrusted parties control input, they might be able to learn information you might otherwise not want them to know. This is called a "timing side-channel".
-
-Most routines that compare strings (like perl's C<eq> and C<cmp> and C's C<strcmp> and C<memcmp>) start scanning from the start and terminate as soon as they determine the strings won't match. This is good for efficiency but bad because it opens a timing side-channel. If one of the strings being compared is a secret and the other is controlled by some untrusted party, it is sometimes possible for this untrusted party to learn the secret using a timing side-channel.
-
-
-
-=head1 HMAC
-
-HMACs are "Message Authentication Codes" built on top of cryptographic hashes. The HMAC algorithm produces digests that are included along with a message in order to verify that whoever created the message knows a particular secret password, and that this message hasn't been tampered with since.
-
-To verify a candidate digest included with a message, you re-compute the digest using the message and the secret password. If this computed digest is is the same as the candidate digest then the message is considered authenticated.
-
-A very common side-channel attack against services that verify unlimited numbers of messages automatically is to create a forged message and then just send some random junk as the candidate digest. Continue sending this message and the junk digest, varying the first character in the digest. Repeat many times. If you find a particular digest that statistically takes a longer time to be rejected than the other digests, it is probably because this particular digest has the first character correct and the service's final string comparison is running a little longer.
-
-At this point, you keep this first character fixed and start varying the second character. Repeat until all the characters are solved or until the amount of remaining possibilities are so small you can brute force it. At this point, your candidate digest is considered valid and you have forged a message.
-
-Note that this particular attack doesn't allow the attacker to recover the secret input key, but the attacker can still use the service itself to produce a valid digest for any message.
-
-
-
-=head1 LOCK-PICKING ANALOGY
-
-Pin tumbler locks are susceptible to being picked in a similar way to an attacker forging HMAC digests using a timing side-channel.
-
-The most common way to pick cheap pin tumbler locks is to apply torque to the lock cylinder so that the pins are pressed against the cylinder. However, because of slight manufacturing discrepancies one particular pin will be the widest by a slight margin and will actually be the only pin pressed against the cylinder (the cheaper the lock, the higher the manufacturing "tolerances"). The attacker lifts this pin until the cylinder gives a little bit, indicating that this pin has been solved and the next widest pin is now the one being pressed against the cylinder. This process is repeated until all the pins are solved and the lock opens.
-
-Just like an attacker trying to solve HMAC digests can work on one character at a time, a lock pick can work on each pin in isolation. To protect against this, quality locks force all pins to be fixed into place before the cylinder rotation can begin just as secure HMAC verifiers force attackers to guess the entire digest on each attempt.
-
-
-
-
-=head1 SEE ALSO
-
-L<The String-Compare-ConstantTime github repo|https://github.com/hoytech/String-Compare-ConstantTime>
-
-L<Authen::Passphrase> has a good section on side-channel cryptanalysis such as it pertains to password storage.
-
-L<The famous TENEX password bug|http://www.meadhbh.org/services/passwords>
-
-L<Example of a timing bug|http://rdist.root.org/2009/05/28/timing-attack-in-google-keyczar-library/>
-
-L<QSCAN|http://hcsw.org/nmap/QSCAN>
-
-L<Practical limits of the timing side channel|http://www.cs.rice.edu/~dwallach/pub/crosby-timing2009.pdf>
-
-L<NaCl: Crypto library designed to prevent side channel attacks|http://nacl.cr.yp.to/>
-
-
-
-=head1 AUTHOR
-
-Doug Hoyte, C<< <doug@hcsw.org> >>
-
-
-
-=head1 COPYRIGHT & LICENSE
-
-Copyright 2012 Doug Hoyte.
-
-This module is licensed under the same terms as perl itself.
-
-
-=cut
-
@@ -2,7 +2,7 @@ package String::Compare::ConstantTime;
 
 use strict;
 
-our $VERSION = '0.300';
+our $VERSION = '0.310';
 
 require XSLoader;
 XSLoader::load('String::Compare::ConstantTime', $VERSION);
@@ -17,18 +17,23 @@ our @EXPORT_OK = qw(equals);
 
 __END__
 
-
-
 =encoding utf-8
 
 =head1 NAME
- 
+
 String::Compare::ConstantTime - Timing side-channel protected string compare
- 
 
 =head1 SYNOPSIS
 
     use String::Compare::ConstantTime;
+
+    if (String::Compare::ConstantTime::equals($secret_data, $user_supplied_data)) {
+      ## The strings are eq
+    }
+
+An example with HMACs:
+
+    use String::Compare::ConstantTime;
     use Digest::HMAC_SHA1; ## or whatever
 
     my $hmac_ctx = Digest::HMAC_SHA1->new($key);
@@ -45,17 +50,17 @@ String::Compare::ConstantTime - Timing side-channel protected string compare
 
 This module provides one function, C<equals> (not exported by default).
 
-You should pass this function two strings of the same length. It will return true if they are string-wise identical and false otherwise, just like C<eq>. However, comparing any two differing strings will take a fixed amount of time, unlike C<eq>.
-
-B<NOTE>: If the lengths of the strings are different, C<equals> will return false right away. Also, comparing two identical strings will take a different amount of time than comparing two differing strings.
+You should pass this function two strings of the same length. Just like perl's C<eq>, it will return true if they are string-wise identical and false otherwise. However, comparing any two differing strings of the same length will take a fixed amount of time. If the lengths of the strings are different, C<equals> will return false right away.
 
 
 
 =head1 TIMING SIDE-CHANNEL
 
-Some programs take different amounts of time to run depending on the input values provided to them. When untrusted parties control input, they might be able to learn information you might otherwise not want them to know. This is called a "timing side-channel".
+Some programs take different amounts of time to run depending on the input values provided to them. Untrusted parties can sometimes learn information you might not want them to know by measuring this time. This is called a "timing side-channel".
 
-Most routines that compare strings (like perl's C<eq> and C<cmp> and C's C<strcmp> and C<memcmp>) start scanning from the start and terminate as soon as they determine the strings won't match. This is good for efficiency but bad because it opens a timing side-channel. If one of the strings being compared is a secret and the other is controlled by some untrusted party, it is sometimes possible for this untrusted party to learn the secret using a timing side-channel.
+Most routines that compare strings (like perl's C<eq> and C<cmp> and C's C<strcmp> and C<memcmp>) start scanning from the start of the strings and terminate as soon as they determine the strings won't match. This is good for efficiency but bad because it opens a timing side-channel. If one of the strings being compared is a secret and the other is controlled by some untrusted party, it is sometimes possible for this untrusted party to learn the secret using a timing side-channel.
+
+If the lengths of the strings are different, because C<equals> returns false right away the size of the secret string may be leaked (but not its contents).
 
 
 
@@ -65,11 +70,13 @@ HMACs are "Message Authentication Codes" built on top of cryptographic hashes. T
 
 To verify a candidate digest included with a message, you re-compute the digest using the message and the secret password. If this computed digest is is the same as the candidate digest then the message is considered authenticated.
 
-A very common side-channel attack against services that verify unlimited numbers of messages automatically is to create a forged message and then just send some random junk as the candidate digest. Continue sending this message and the junk digest, varying the first character in the digest. Repeat many times. If you find a particular digest that statistically takes a longer time to be rejected than the other digests, it is probably because this particular digest has the first character correct and the service's final string comparison is running a little longer.
+A common side-channel attack against services that verify unlimited numbers of messages automatically is to create a forged message and then just send some random junk as the candidate digest. Continue sending this message and junk digests that vary by the first character. Repeat many times. If you find a particular digest that statistically takes a longer time to be rejected than the other digests, it is probably because this particular digest has the first character correct and the service's final string comparison is running slightly longer.
+
+At this point, you keep this first character fixed and start varying the second character until it is solved. Repeat until all the characters are solved or until the amount of remaining possibilities are so small you can brute force it. At this point, your candidate digest is considered valid and you have forged a message.
 
-At this point, you keep this first character fixed and start varying the second character. Repeat until all the characters are solved or until the amount of remaining possibilities are so small you can brute force it. At this point, your candidate digest is considered valid and you have forged a message.
+Note that this particular attack doesn't allow the attacker to recover the secret input key to the HMAC but nevertheless can produce a valid digest for any message given enough time because the service that validates the HMAC is acting as an "oracle".
 
-Note that this particular attack doesn't allow the attacker to recover the secret input key, but the attacker can still use the service itself to produce a valid digest for any message.
+B<NOTE>: Although this module protects against a common attack against applications that store state in browser cookies, it is in no way an endorsement of this practise.
 
 
 
@@ -77,9 +84,9 @@ Note that this particular attack doesn't allow the attacker to recover the secre
 
 Pin tumbler locks are susceptible to being picked in a similar way to an attacker forging HMAC digests using a timing side-channel.
 
-The most common way to pick cheap pin tumbler locks is to apply torque to the lock cylinder so that the pins are pressed against the cylinder. However, because of slight manufacturing discrepancies one particular pin will be the widest by a slight margin and will actually be the only pin pressed against the cylinder (the cheaper the lock, the higher the manufacturing "tolerances"). The attacker lifts this pin until the cylinder gives a little bit, indicating that this pin has been solved and the next widest pin is now the one being pressed against the cylinder. This process is repeated until all the pins are solved and the lock opens.
+The traditional way to pick cheap pin tumbler locks is to apply torque to the lock cylinder so that the pins are pressed against the cylinder. However, because of slight manufacturing discrepancies one particular pin will be the widest by a slight margin and will be pressed against the cylinder tighter than the others (the cheaper the lock, the higher the manufacturing tolerances). The attacker lifts this pin until the cylinder gives a little bit, indicating that this pin has been solved and the next widest pin is now the one being pressed against the cylinder the tighest. This process is repeated until all the pins are solved and the lock opens.
 
-Just like an attacker trying to solve HMAC digests can work on one character at a time, a lock pick can work on each pin in isolation. To protect against this, quality locks force all pins to be fixed into place before the cylinder rotation can begin just as secure HMAC verifiers force attackers to guess the entire digest on each attempt.
+Just like an attacker trying to solve HMAC digests can work on one character at a time, a lock pick can work on each pin in isolation. To protect against this, quality locks force all pins to be fixed into place before the cylinder rotation can be attempted, just as secure HMAC verifiers force attackers to guess the entire digest on each attempt.
 
 
 
@@ -88,7 +95,7 @@ Just like an attacker trying to solve HMAC digests can work on one character at
 
 L<The String-Compare-ConstantTime github repo|https://github.com/hoytech/String-Compare-ConstantTime>
 
-L<Authen::Passphrase> has a good section on side-channel cryptanalysis such as it pertains to password storage.
+L<Authen::Passphrase> has a good section on side-channel cryptanalysis such as it pertains to password storage (mostly, it doesn't).
 
 L<The famous TENEX password bug|http://www.meadhbh.org/services/passwords>
 
@@ -110,7 +117,7 @@ Doug Hoyte, C<< <doug@hcsw.org> >>
 
 =head1 COPYRIGHT & LICENSE
 
-Copyright 2012 Doug Hoyte.
+Copyright 2012-2013 Doug Hoyte.
 
 This module is licensed under the same terms as perl itself.
 
@@ -4,7 +4,7 @@ use strict;
 
 use utf8;
 
-use Test::More tests => 15;
+use Test::More tests => 18;
 
 
 ok(equals("asdf", "asdf"));
@@ -28,3 +28,7 @@ ok(equals("λλλλλλλ", "λλλλλλλ"));
 
 ok(equals(join("", ( map { chr } (0 .. 255) )) x 10,
            join("", ( map { chr } (0 .. 255) )) x 10));
+
+ok(!equals("asdf", undef));
+ok(!equals(undef, "asdf"));
+ok(equals(undef, undef));