{ name => "DateTime::Format::Strptime",          url => "http://search.cpan.org/~drolsky/",   debian => 'libdatetime-format-strptime-perl' },
   { name => "DBI",             version => '1.50',  url => "http://search.cpan.org/~timb/",      debian => 'libdbi-perl' },
   { name => "DBD::Pg",         version => '1.49',  url => "http://search.cpan.org/~dbdpg/",     debian => 'libdbd-pg-perl' },
+  { name => "Digest::SHA",                         url => "http://search.cpan.org/~mshelor/",   debian => 'libdigest-sha-perl' },
   { name => "Email::Address",                      url => "http://search.cpan.org/~rjbs/",      debian => 'libemail-address-perl' },
   { name => "Email::MIME",                         url => "http://search.cpan.org/~rjbs/",      debian => 'libemail-mime-perl' },
   { name => "FCGI",            version => '0.72',  url => "http://search.cpan.org/~mstrout/",   debian => 'libfcgi-perl' },
   { name => "List::MoreUtils", version => '0.21',  url => "http://search.cpan.org/~vparseval/", debian => 'liblist-moreutils-perl' },
   { name => "List::UtilsBy",                       url => "http://search.cpan.org/~pevans/",    debian => 'liblist-utilsby-perl' },
   { name => "Params::Validate",                    url => "http://search.cpan.org/~drolsky/",   debian => 'libparams-validate-perl' },
+  { name => "PBKDF2::Tiny",    version => '0.005', url => "http://search.cpan.org/~arodland/", },
   { name => "PDF::API2",       version => '2.000', url => "http://search.cpan.org/~areibens/",  debian => 'libpdf-api2-perl' },
   { name => "Rose::Object",                        url => "http://search.cpan.org/~jsiracusa/", debian => 'librose-object-perl' },
   { name => "Rose::DB",                            url => "http://search.cpan.org/~jsiracusa/", debian => 'librose-db-perl' },
 );
 
 @optional_modules = (
-  { name => "Digest::SHA",                         url => "http://search.cpan.org/~mshelor/",   debian => 'libdigest-sha-perl' },
   { name => "IO::Socket::SSL",                     url => "http://search.cpan.org/~sullr/",     debian => 'libio-socket-ssl-perl' },
   { name => "Net::LDAP",                           url => "http://search.cpan.org/~gbarr/",     debian => 'libnet-ldap-perl' },
   # Net::SMTP is core since 5.7.3
 
--- /dev/null
+use strict;
+use warnings;
+
+package PBKDF2::Tiny;
+# ABSTRACT: Minimalist PBKDF2 (RFC 2898) with HMAC-SHA1 or HMAC-SHA2
+
+our $VERSION = '0.005';
+
+use Carp ();
+use Exporter 5.57 qw/import/;
+
+our @EXPORT_OK = qw/derive derive_hex verify verify_hex hmac digest_fcn/;
+
+my ( $BACKEND, $LOAD_ERR );
+for my $mod (qw/Digest::SHA Digest::SHA::PurePerl/) {
+    $BACKEND = $mod, last if eval "require $mod; 1";
+    $LOAD_ERR ||= $@;
+}
+die $LOAD_ERR if !$BACKEND;
+
+#--------------------------------------------------------------------------#
+# constants and lookup tables
+#--------------------------------------------------------------------------#
+
+# function coderef placeholder, block size in bytes, digest size in bytes
+my %DIGEST_TYPES = (
+    'SHA-1'   => [ undef, 64,  20 ],
+    'SHA-224' => [ undef, 64,  28 ],
+    'SHA-256' => [ undef, 64,  32 ],
+    'SHA-384' => [ undef, 128, 48 ],
+    'SHA-512' => [ undef, 128, 64 ],
+);
+
+for my $type ( keys %DIGEST_TYPES ) {
+    no strict 'refs';
+    ( my $name = lc $type ) =~ s{-}{};
+    $DIGEST_TYPES{$type}[0] = \&{"$BACKEND\::$name"};
+}
+
+my %INT = map { $_ => pack( "N", $_ ) } 1 .. 16;
+
+#--------------------------------------------------------------------------#
+# public functions
+#--------------------------------------------------------------------------#
+
+#pod =func derive
+#pod
+#pod     $dk = derive( $type, $password, $salt, $iterations, $dk_length )
+#pod
+#pod The C<derive> function outputs a binary string with the derived key.
+#pod The first argument indicates the digest function to use.  It must be one
+#pod of: SHA-1, SHA-224, SHA-256, SHA-384, or SHA-512.
+#pod
+#pod If a password or salt are not provided, they default to the empty string, so
+#pod don't do that!  L<RFC 2898
+#pod recommends|https://tools.ietf.org/html/rfc2898#section-4.1> a random salt of at
+#pod least 8 octets.  If you need a cryptographically strong salt, consider
+#pod L<Crypt::URandom>.
+#pod
+#pod The password and salt should encoded as octet strings. If not (i.e. if
+#pod Perl's internal 'UTF8' flag is on), then an exception will be thrown.
+#pod
+#pod The number of iterations defaults to 1000 if not provided.  If the derived
+#pod key length is not provided, it defaults to the output size of the digest
+#pod function.
+#pod
+#pod =cut
+
+sub derive {
+    my ( $type, $passwd, $salt, $iterations, $dk_length ) = @_;
+
+    my ( $digester, $block_size, $digest_length ) = digest_fcn($type);
+
+    $passwd = '' unless defined $passwd;
+    $salt   = '' unless defined $salt;
+    $iterations ||= 1000;
+    $dk_length  ||= $digest_length;
+
+    # we insist on octet strings for password and salt
+    Carp::croak("password must be an octet string, not a character string")
+      if utf8::is_utf8($passwd);
+    Carp::croak("salt must be an octet string, not a character string")
+      if utf8::is_utf8($salt);
+
+    my $key = ( length($passwd) > $block_size ) ? $digester->($passwd) : $passwd;
+    my $passes = int( $dk_length / $digest_length );
+    $passes++ if $dk_length % $digest_length; # need part of an extra pass
+
+    my $dk = "";
+    for my $i ( 1 .. $passes ) {
+        $INT{$i} ||= pack( "N", $i );
+        my $digest = my $result =
+          "" . hmac( $salt . $INT{$i}, $key, $digester, $block_size );
+        for my $iter ( 2 .. $iterations ) {
+            $digest = hmac( $digest, $key, $digester, $block_size );
+            $result ^= $digest;
+        }
+        $dk .= $result;
+    }
+
+    return substr( $dk, 0, $dk_length );
+}
+
+#pod =func derive_hex
+#pod
+#pod Works just like L</derive> but outputs a hex string.
+#pod
+#pod =cut
+
+sub derive_hex { unpack( "H*", &derive ) }
+
+#pod =func verify
+#pod
+#pod     $bool = verify( $dk, $type, $password, $salt, $iterations, $dk_length );
+#pod
+#pod The C<verify> function checks that a given derived key (in binary form) matches
+#pod the password and other parameters provided using a constant-time comparison
+#pod function.
+#pod
+#pod The first parameter is the derived key to check.  The remaining parameters
+#pod are the same as for L</derive>.
+#pod
+#pod =cut
+
+sub verify {
+    my ( $dk1, @derive_args ) = @_;
+
+    my $dk2 = derive(@derive_args);
+
+    # shortcut if input dk is the wrong length entirely; this is not
+    # constant time, but this doesn't really give much away as
+    # the keys are of different types anyway
+
+    return unless length($dk1) == length($dk2);
+
+    # if lengths match, do constant time comparison to avoid timing attacks
+    my $match = 1;
+    for my $i ( 0 .. length($dk1) - 1 ) {
+        $match &= ( substr( $dk1, $i, 1 ) eq substr( $dk2, $i, 1 ) ) ? 1 : 0;
+    }
+
+    return $match;
+}
+
+#pod =func verify_hex
+#pod
+#pod Works just like L</verify> but the derived key must be a hex string (without a
+#pod leading "0x").
+#pod
+#pod =cut
+
+sub verify_hex {
+    my $dk = pack( "H*", shift );
+    return verify( $dk, @_ );
+}
+
+#pod =func digest_fcn
+#pod
+#pod     ($fcn, $block_size, $digest_length) = digest_fcn('SHA-1');
+#pod     $digest = $fcn->($data);
+#pod
+#pod This function is used internally by PBKDF2::Tiny, but made available in case
+#pod it's useful to someone.
+#pod
+#pod Given one of the valid digest types, it returns a function reference that
+#pod digests a string of data. It also returns block size and digest length for that
+#pod digest type.
+#pod
+#pod =cut
+
+sub digest_fcn {
+    my ($type) = @_;
+
+    Carp::croak("Digest function '$type' not supported")
+      unless exists $DIGEST_TYPES{$type};
+
+    return @{ $DIGEST_TYPES{$type} };
+}
+
+#pod =func hmac
+#pod
+#pod     $key = $digest_fcn->($key) if length($key) > $block_size;
+#pod     $hmac = hmac( $data, $key, $digest_fcn, $block_size );
+#pod
+#pod This function is used internally by PBKDF2::Tiny, but made available in case
+#pod it's useful to someone.
+#pod
+#pod The first two arguments are the data and key inputs to the HMAC function.  Both
+#pod should be encoded as octet strings, as underlying HMAC/digest functions may
+#pod croak or may give unexpected results if Perl's internal UTF-8 flag is on.
+#pod
+#pod B<Note>: if the key is longer than the digest block size, it must be
+#pod preprocessed using the digesting function.
+#pod
+#pod The third and fourth arguments must be a digesting code reference (from
+#pod L</digest_fcn>) and block size.
+#pod
+#pod =cut
+
+# hmac function adapted from Digest::HMAC by Graham Barr and Gisle Aas.
+# Compared to that implementation, this *requires* a preprocessed
+# key and block size, which makes iterative hmac slightly more efficient.
+sub hmac {
+    my ( $data, $key, $digest_func, $block_size ) = @_;
+
+    my $k_ipad = $key ^ ( chr(0x36) x $block_size );
+    my $k_opad = $key ^ ( chr(0x5c) x $block_size );
+
+    &$digest_func( $k_opad, &$digest_func( $k_ipad, $data ) );
+}
+
+1;
+
+
+# vim: ts=4 sts=4 sw=4 et:
+
+__END__
+
+=pod
+
+=encoding UTF-8
+
+=head1 NAME
+
+PBKDF2::Tiny - Minimalist PBKDF2 (RFC 2898) with HMAC-SHA1 or HMAC-SHA2
+
+=head1 VERSION
+
+version 0.005
+
+=head1 SYNOPSIS
+
+    use PBKDF2::Tiny qw/derive verify/;
+
+    my $dk = derive( 'SHA-1', $pass, $salt, $iters );
+
+    if ( verify( $dk, 'SHA-1', $pass, $salt, $iters ) ) {
+        # password is correct
+    }
+
+=head1 DESCRIPTION
+
+This module provides an L<RFC 2898|https://tools.ietf.org/html/rfc2898>
+compliant PBKDF2 implementation using HMAC-SHA1 or HMAC-SHA2 in under 100 lines
+of code.  If you are using Perl 5.10 or later, it uses only core Perl modules.
+If you are on an earlier version of Perl, you need L<Digest::SHA> or
+L<Digest::SHA::PurePerl>.
+
+All documented functions are optionally exported.  No functions are exported by default.
+
+=head1 FUNCTIONS
+
+=head2 derive
+
+    $dk = derive( $type, $password, $salt, $iterations, $dk_length )
+
+The C<derive> function outputs a binary string with the derived key.
+The first argument indicates the digest function to use.  It must be one
+of: SHA-1, SHA-224, SHA-256, SHA-384, or SHA-512.
+
+If a password or salt are not provided, they default to the empty string, so
+don't do that!  L<RFC 2898
+recommends|https://tools.ietf.org/html/rfc2898#section-4.1> a random salt of at
+least 8 octets.  If you need a cryptographically strong salt, consider
+L<Crypt::URandom>.
+
+The password and salt should encoded as octet strings. If not (i.e. if
+Perl's internal 'UTF8' flag is on), then an exception will be thrown.
+
+The number of iterations defaults to 1000 if not provided.  If the derived
+key length is not provided, it defaults to the output size of the digest
+function.
+
+=head2 derive_hex
+
+Works just like L</derive> but outputs a hex string.
+
+=head2 verify
+
+    $bool = verify( $dk, $type, $password, $salt, $iterations, $dk_length );
+
+The C<verify> function checks that a given derived key (in binary form) matches
+the password and other parameters provided using a constant-time comparison
+function.
+
+The first parameter is the derived key to check.  The remaining parameters
+are the same as for L</derive>.
+
+=head2 verify_hex
+
+Works just like L</verify> but the derived key must be a hex string (without a
+leading "0x").
+
+=head2 digest_fcn
+
+    ($fcn, $block_size, $digest_length) = digest_fcn('SHA-1');
+    $digest = $fcn->($data);
+
+This function is used internally by PBKDF2::Tiny, but made available in case
+it's useful to someone.
+
+Given one of the valid digest types, it returns a function reference that
+digests a string of data. It also returns block size and digest length for that
+digest type.
+
+=head2 hmac
+
+    $key = $digest_fcn->($key) if length($key) > $block_size;
+    $hmac = hmac( $data, $key, $digest_fcn, $block_size );
+
+This function is used internally by PBKDF2::Tiny, but made available in case
+it's useful to someone.
+
+The first two arguments are the data and key inputs to the HMAC function.  Both
+should be encoded as octet strings, as underlying HMAC/digest functions may
+croak or may give unexpected results if Perl's internal UTF-8 flag is on.
+
+B<Note>: if the key is longer than the digest block size, it must be
+preprocessed using the digesting function.
+
+The third and fourth arguments must be a digesting code reference (from
+L</digest_fcn>) and block size.
+
+=begin Pod::Coverage
+
+
+
+
+=end Pod::Coverage
+
+=head1 SEE ALSO
+
+=over 4
+
+=item *
+
+L<Crypt::PBKDF2>
+
+=item *
+
+L<Digest::PBDKF2>
+
+=back
+
+=for :stopwords cpan testmatrix url annocpan anno bugtracker rt cpants kwalitee diff irc mailto metadata placeholders metacpan
+
+=head1 SUPPORT
+
+=head2 Bugs / Feature Requests
+
+Please report any bugs or feature requests through the issue tracker
+at L<https://github.com/dagolden/PBKDF2-Tiny/issues>.
+You will be notified automatically of any progress on your issue.
+
+=head2 Source Code
+
+This is open source software.  The code repository is available for
+public review and contribution under the terms of the license.
+
+L<https://github.com/dagolden/PBKDF2-Tiny>
+
+  git clone https://github.com/dagolden/PBKDF2-Tiny.git
+
+=head1 AUTHOR
+
+David Golden <dagolden@cpan.org>
+
+=head1 COPYRIGHT AND LICENSE
+
+This software is Copyright (c) 2014 by David Golden.
+
+This is free software, licensed under:
+
+  The Apache License, Version 2.0, January 2004
+
+=cut