String-Compare-ConstantTime-0.321/0000755000175000017500000000000013501713034015432 5ustar dougdougString-Compare-ConstantTime-0.321/t/0000755000175000017500000000000013501713034015675 5ustar dougdougString-Compare-ConstantTime-0.321/t/stats.t0000644000175000017500000000027213267237122017231 0ustar dougdoug#!/usr/bin/env perl use String::Compare::ConstantTime; use strict; use Test::More skip_all => q{Need to figure out a way to portable collect granular timing data -- see git history}; String-Compare-ConstantTime-0.321/t/accuracy.t0000644000175000017500000000301213501712612017651 0ustar dougdoug#!/usr/bin/env perl use strict; use warnings; use String::Compare::ConstantTime qw/equals/; use utf8; use Encode; use Test::More tests => 26; ok(equals("asdf", "asdf")); ok(!equals("asdf", "asdg")); ok(!equals("asdf", "asdfg")); ok(!equals("asdfg", "asdf")); ok(equals("a"x1000, "a"x1000)); ok(!equals("a"x1000, "a"x999 . "b")); ok(!equals("a"x400 . "b" . "a"x599, "a"x1000)); ok(equals("\x00"x65, "\x00"x65)); ok(!equals("\x00"x65, "\x00"x64)); ok(equals(1, 1)); ok(equals(10000000, 10000000)); ok(!equals(10000000, 10000070)); ok(equals("λ", "λ")); 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)); my $string_utf8_on = "äßλ"; ok( utf8::is_utf8($string_utf8_on), "utf8 flag on"); my $string_utf8_off = Encode::encode("utf8", $string_utf8_on); ok( !utf8::is_utf8($string_utf8_off), "utf8 flag off"); ok(equals($string_utf8_on, $string_utf8_off)); my $latin1_e_acute = "\xe9"; ok( !utf8::is_utf8($latin1_e_acute), "latin-1 e-acute has utf8 flag off"); my $utf8_e_acute = "é"; ok( utf8::is_utf8($utf8_e_acute), "UTF-8 e-acute has utf8 flag on"); ok(!equals($latin1_e_acute, $utf8_e_acute), "latin-1 vs UTF-8 not equal to us"); ok($latin1_e_acute eq $utf8_e_acute, "but perl thinks they are"); utf8::encode($utf8_e_acute); utf8::encode($latin1_e_acute); ok(equals($latin1_e_acute, $utf8_e_acute), "after encoding, they are equal to us"); String-Compare-ConstantTime-0.321/t/magic.t0000644000175000017500000000032113267237122017146 0ustar dougdoug#!/usr/bin/env perl use strict; use warnings; use String::Compare::ConstantTime qw/equals/; use Test::More tests => 2; ok equals substr( "asdfg", 0, 4 ), "asdf"; ok equals "asdf", substr( "asdfg", 0, 4 ); String-Compare-ConstantTime-0.321/MANIFEST0000644000175000017500000000051313501713034016562 0ustar dougdougChanges ConstantTime.xs COPYING lib/String/Compare/ConstantTime.pm Makefile.PL MANIFEST This list of files MANIFEST.SKIP README t/accuracy.t t/magic.t t/stats.t META.yml Module YAML meta-data (added by MakeMaker) META.json Module JSON meta-data (added by MakeMaker) String-Compare-ConstantTime-0.321/META.yml0000664000175000017500000000134513501713034016710 0ustar dougdoug--- abstract: 'Timing side-channel protected string compare' author: - 'Doug Hoyte ' build_requires: ExtUtils::MakeMaker: '0' configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 1 generated_by: 'ExtUtils::MakeMaker version 7.24, CPAN::Meta::Converter version 2.150010' license: perl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: String-Compare-ConstantTime no_index: directory: - t - inc requires: perl: '5.008000' resources: bugtracker: https://github.com/hoytech/String-Compare-ConstantTime/issues repository: git://github.com/hoytech/String-Compare-ConstantTime.git version: '0.321' x_serialization_backend: 'CPAN::Meta::YAML version 0.018' String-Compare-ConstantTime-0.321/Makefile.PL0000644000175000017500000000147413267237122017422 0ustar dougdouguse strict; use warnings; use ExtUtils::MakeMaker qw(6.48); my %args = ( NAME => 'String::Compare::ConstantTime', AUTHOR => ['Doug Hoyte '], ABSTRACT_FROM => 'lib/String/Compare/ConstantTime.pm', VERSION_FROM => 'lib/String/Compare/ConstantTime.pm', PREREQ_PM => { }, OBJECT => 'ConstantTime.o', LICENSE => 'perl', dist => { PREOP => 'pod2text $(VERSION_FROM) > $(DISTVNAME)/README', }, META_MERGE => { resources => { repository => 'git://github.com/hoytech/String-Compare-ConstantTime.git', bugtracker => 'https://github.com/hoytech/String-Compare-ConstantTime/issues', }, }, MIN_PERL_VERSION => 5.8.0, ); WriteMakefile(%args); String-Compare-ConstantTime-0.321/README0000664000175000017500000001450413501713034016320 0ustar dougdougNAME String::Compare::ConstantTime - Timing side-channel protected string compare 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); $hmac_ctx->add($data); my $digest = $hmac_ctx->digest; if (String::Compare::ConstantTime::equals($digest, $candidate_digest)) { ## The candidate digest is valid } DESCRIPTION This module provides one function, "equals" (not exported by default). 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. NOTE: This does byte-wise comparison of the underlying string storage, meaning that comparing strings with non-ASCII data with different states of the internal UTF-8 flag is not reliable. You should always encode your data to bytes before comparing. 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 "eq" and "cmp" and C's "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 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 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. 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: 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 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 tightest. 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 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 Authen::Passphrase has a good section on side-channel cryptanalysis such as it pertains to password storage (mostly, it doesn't). The famous TENEX password bug Example of a timing bug QSCAN Practical limits of the timing side channel NaCl: Crypto library designed to prevent side channel attacks AUTHOR Doug Hoyte, "" COPYRIGHT & LICENSE Copyright 2012-2018 Doug Hoyte. Contributions from Paul Cochrane. This module is licensed under the same terms as perl itself. String-Compare-ConstantTime-0.321/META.json0000664000175000017500000000232313501713034017055 0ustar dougdoug{ "abstract" : "Timing side-channel protected string compare", "author" : [ "Doug Hoyte " ], "dynamic_config" : 1, "generated_by" : "ExtUtils::MakeMaker version 7.24, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : "2" }, "name" : "String-Compare-ConstantTime", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "perl" : "5.008000" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/hoytech/String-Compare-ConstantTime/issues" }, "repository" : { "type" : "git", "url" : "git://github.com/hoytech/String-Compare-ConstantTime.git" } }, "version" : "0.321", "x_serialization_backend" : "JSON::PP version 2.27400_02" } String-Compare-ConstantTime-0.321/lib/0000755000175000017500000000000013501713034016200 5ustar dougdougString-Compare-ConstantTime-0.321/lib/String/0000755000175000017500000000000013501713034017446 5ustar dougdougString-Compare-ConstantTime-0.321/lib/String/Compare/0000755000175000017500000000000013501713034021034 5ustar dougdougString-Compare-ConstantTime-0.321/lib/String/Compare/ConstantTime.pm0000644000175000017500000001452013501712701024004 0ustar dougdougpackage String::Compare::ConstantTime; use strict; use warnings; our $VERSION = '0.321'; require XSLoader; XSLoader::load('String::Compare::ConstantTime', $VERSION); require Exporter; use base 'Exporter'; our @EXPORT_OK = qw(equals); ## equals is defined in ConstantTime.xs 1; __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); $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 (not exported by default). You should pass this function two strings of the same length. Just like perl's C, 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 will return false right away. B: This does byte-wise comparison of the underlying string storage, meaning that comparing strings with non-ASCII data with different states of the internal UTF-8 flag is not reliable. You should always encode your data to bytes before comparing. =head1 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 and C and C's C and C) 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 returns false right away the size of the secret string may be leaked (but not its contents). =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 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. 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". B: 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. =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 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 tightest. 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 be attempted, just as secure HMAC verifiers force attackers to guess the entire digest on each attempt. =head1 SEE ALSO L L has a good section on side-channel cryptanalysis such as it pertains to password storage (mostly, it doesn't). L L L L L =head1 AUTHOR Doug Hoyte, C<< >> =head1 COPYRIGHT & LICENSE Copyright 2012-2018 Doug Hoyte. Contributions from Paul Cochrane. This module is licensed under the same terms as perl itself. =cut String-Compare-ConstantTime-0.321/Changes0000644000175000017500000000217413501712765016743 0ustar dougdoug0.321 2019-06-17 * Remove unnecessary sv_len_utf8 calls (thanks David Golden) * Test and document mixed UTF-8 flag comparison (thanks David Golden) 0.320 2018-04-22 * Added Travis and Appveyor testing (thanks Paul Cochrane) * Use warnings pragmas (thanks Paul Cochrane) * Many documentation and meta-data fixes/updates (thanks Paul Cochrane) * Better test coverage for strings with/without utf8 flag (thanks Paul Cochrane) * Replace SvCUR with sv_len_utf8 to work around issue in debugging versions of perl (see https://github.com/hoytech/String-Compare-ConstantTime/issues/4) (thanks Paul Cochrane) 0.312 2017-02-14 * Now works with variables that have magic such as results of substr, tied variables, etc. (thanks James Raspass) 0.311 2015-10-24 * Don't ship MYMETA files anymore (thanks Adel Qalieh and Alexandr Ciornii) 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 0.20 2012-07-12 * Initial release String-Compare-ConstantTime-0.321/MANIFEST.SKIP0000644000175000017500000000030713501712610017327 0ustar dougdougMANIFEST.bak Makefile.old Makefile$ README.pod .gitignore .git/ blib/ pm_to_blib ConstantTime.o ConstantTime.c ConstantTime.bs String-Compare-ConstantTime-*.tar.gz .appveyor.yml .travis.yml MYMETA.* String-Compare-ConstantTime-0.321/ConstantTime.xs0000644000175000017500000000163313501712612020422 0ustar dougdoug#include "EXTERN.h" #include "perl.h" #include "XSUB.h" static int do_compare(unsigned char *a, unsigned char *b, size_t n) { size_t i; unsigned char r = 0; for (i = 0; i < n; i++) { r |= *a++ ^ *b++; } return r; } MODULE = String::Compare::ConstantTime PACKAGE = String::Compare::ConstantTime PROTOTYPES: ENABLE int equals(a, b) SV *a SV *b CODE: size_t alen; unsigned char *ap; size_t blen; unsigned char *bp; int r; SvGETMAGIC(a); SvGETMAGIC(b); if (SvOK(a) && SvOK(b)) { ap = SvPV(a, alen); 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; OUTPUT: RETVAL String-Compare-ConstantTime-0.321/COPYING0000644000175000017500000000007513267237122016477 0ustar dougdougThis module is licensed under the same terms as perl itself.