| 
							
							
							
						 |  |  | @@ -1,753 +0,0 @@ | 
		
	
		
			
				|  |  |  |  | package App::Netdisco::Web::Plugin::Passphrase; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | # ABSTRACT: Passphrases and Passwords as objects for Dancer | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =head1 NAME | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | Dancer::Plugin::Passphrase - Passphrases and Passwords as objects for Dancer | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =head1 SYNOPSIS | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | This plugin manages the hashing of passwords for Dancer apps, allowing  | 
		
	
		
			
				|  |  |  |  | developers to follow cryptography best practices without having to  | 
		
	
		
			
				|  |  |  |  | become a cryptography expert. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | It uses the bcrypt algorithm as the default, while also supporting any | 
		
	
		
			
				|  |  |  |  | hashing function provided by L<Digest>  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =head1 USAGE | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     package MyWebService; | 
		
	
		
			
				|  |  |  |  |     use Dancer ':syntax'; | 
		
	
		
			
				|  |  |  |  |     use Dancer::Plugin::Passphrase; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     post '/login' => sub { | 
		
	
		
			
				|  |  |  |  |         my $phrase = passphrase( param('my password') )->generate; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         # $phrase is now an object that contains RFC 2307 representation | 
		
	
		
			
				|  |  |  |  |         # of the hashed passphrase, along with the salt, and other metadata | 
		
	
		
			
				|  |  |  |  |          | 
		
	
		
			
				|  |  |  |  |         # You should store $phrase->rfc2307() for use later | 
		
	
		
			
				|  |  |  |  |     }; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     get '/protected' => sub { | 
		
	
		
			
				|  |  |  |  |         # Retrieve $stored_rfc_2307_string, like we created above. | 
		
	
		
			
				|  |  |  |  |         # IT MUST be a valid RFC 2307 string | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         if ( passphrase( param('my password') )->matches( $stored_rfc_2307 ) ) { | 
		
	
		
			
				|  |  |  |  |             # Passphrase matches! | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  |     }; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     get '/generate_new_password' => sub { | 
		
	
		
			
				|  |  |  |  |         return passphrase->generate_random; | 
		
	
		
			
				|  |  |  |  |     }; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =cut | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | use strict; | 
		
	
		
			
				|  |  |  |  | use feature 'switch'; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | use Dancer::Plugin; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | use Carp qw(carp croak); | 
		
	
		
			
				|  |  |  |  | use Data::Entropy::Algorithms qw(rand_bits rand_int); | 
		
	
		
			
				|  |  |  |  | use Digest; | 
		
	
		
			
				|  |  |  |  | use MIME::Base64 qw(decode_base64 encode_base64); | 
		
	
		
			
				|  |  |  |  | use Scalar::Util qw(blessed); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | our $VERSION = '2.0.0'; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | # Auto stringifies and returns the RFC 2307 representation | 
		
	
		
			
				|  |  |  |  | # of the object unless we are calling a method on it | 
		
	
		
			
				|  |  |  |  | use overload ( | 
		
	
		
			
				|  |  |  |  |     '""' => sub { | 
		
	
		
			
				|  |  |  |  |         if (blessed($_[0]) && $_[0]->isa('App::Netdisco::Web::Plugin::Passphrase')) { | 
		
	
		
			
				|  |  |  |  |             $_[0]->rfc2307(); | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  |     }, | 
		
	
		
			
				|  |  |  |  |     fallback => 1, | 
		
	
		
			
				|  |  |  |  | ); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | register passphrase => \&passphrase; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =head1 KEYWORDS | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =head2 passphrase | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | Given a plaintext password, it returns a Dancer::Plugin::Passphrase  | 
		
	
		
			
				|  |  |  |  | object that you can generate a new hash from, or match against a stored hash. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =cut | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | sub passphrase { | 
		
	
		
			
				|  |  |  |  |     # Dancer 2 keywords receive a reference to the DSL object as a first param. | 
		
	
		
			
				|  |  |  |  |     # We don't need it, so get rid of it, and just get the plaintext | 
		
	
		
			
				|  |  |  |  |     shift if blessed($_[0]) && $_[0]->isa('Dancer::Core::DSL'); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     my $plaintext = $_[0]; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     return bless { | 
		
	
		
			
				|  |  |  |  |         plaintext => $plaintext | 
		
	
		
			
				|  |  |  |  |     }, 'App::Netdisco::Web::Plugin::Passphrase'; | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =head1 MAIN METHODS | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =head2 generate | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | Generates an RFC 2307 representation of the hashed passphrase | 
		
	
		
			
				|  |  |  |  | that is suitable for storage in a database. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     my $pass = passphrase('my passphrase')->generate; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | You should store C<$phrase->rfc_2307()> in your database. For convenience | 
		
	
		
			
				|  |  |  |  | the object will automagically return the RFC 2307 representation when no | 
		
	
		
			
				|  |  |  |  | method is called on it. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | Accepts a hashref of options to specify what kind of hash should be  | 
		
	
		
			
				|  |  |  |  | generated. All options settable in the config file are valid. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | If you specify only the algorithm, the default settings for that algorithm will be used. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | A cryptographically random salt is used if salt is not defined. | 
		
	
		
			
				|  |  |  |  | Only if you specify the empty string will an empty salt be used | 
		
	
		
			
				|  |  |  |  | This is not recommended, and should only be used to upgrade old insecure hashes | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     my $phrase = passphrase('my password')->generate({ | 
		
	
		
			
				|  |  |  |  |         algorithm  => '',   # What algorithm is used to generate the hash | 
		
	
		
			
				|  |  |  |  |         cost       => '',   # Cost / Work Factor if using bcrypt | 
		
	
		
			
				|  |  |  |  |         salt       => '',   # Manually specify salt if using a salted digest | 
		
	
		
			
				|  |  |  |  |     }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =cut | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | sub generate { | 
		
	
		
			
				|  |  |  |  |     my ($self, $options) = @_; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     $self->_get_settings($options); | 
		
	
		
			
				|  |  |  |  |     $self->_calculate_hash; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     return $self; | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | sub generate_hash { | 
		
	
		
			
				|  |  |  |  |     carp "generate_hash method is deprecated"; | 
		
	
		
			
				|  |  |  |  |     return shift->generate(); | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =head2 matches | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | Matches a plaintext password against a stored hash. | 
		
	
		
			
				|  |  |  |  | Returns 1 if the hash of the password matches the stored hash. | 
		
	
		
			
				|  |  |  |  | Returns undef if they don't match or if there was an error | 
		
	
		
			
				|  |  |  |  | Fail-Secure, rather than Fail-Safe. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     passphrase('my password')->matches($stored_rfc_2307_string); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | $stored_rfc_2307_string B<MUST> be a valid RFC 2307 string, | 
		
	
		
			
				|  |  |  |  | as created by L<generate()|/"passphrase__generate"> | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | An RFC 2307 string is made up of a scheme identifier, followed by a | 
		
	
		
			
				|  |  |  |  | base64 encoded string. The base64 encoded string should contain | 
		
	
		
			
				|  |  |  |  | the password hash and the salt concatenated together - in that order. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     '{'.$scheme.'}'.encode_base64($hash . $salt, ''); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | Where C<$scheme> can be any of the following and their unsalted variants, | 
		
	
		
			
				|  |  |  |  | which have the leading S removed. CRYPT will be Bcrypt. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     SMD5 SSHA SSHA224 SSHA256 SSHA384 SSHA512 CRYPT | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | A complete RFC2307 string looks like this: | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     {SSHA}K3LAbIjRL5CpLzOlm3/HzS3qt/hUaGVTYWx0 | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | This is the format created by L<generate()|/"passphrase__generate"> | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =cut | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | sub matches { | 
		
	
		
			
				|  |  |  |  |     my ($self, $stored_hash) = @_; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     # Force auto stringification in case we were passed an object. | 
		
	
		
			
				|  |  |  |  |     ($stored_hash) = ($stored_hash =~ m/(.*)/s); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     my $new_hash = $self->_extract_settings($stored_hash)->_calculate_hash->rfc2307; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     return ($new_hash eq $stored_hash) ? 1 : undef; | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =head2 generate_random | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | Generates and returns any number of cryptographically random | 
		
	
		
			
				|  |  |  |  | characters from the url-safe base64 charater set. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     my $rand_pass = passphrase->generate_random; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | The passwords generated are suitable for use as | 
		
	
		
			
				|  |  |  |  | temporary passwords or one-time authentication tokens. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | You can configure the length and the character set | 
		
	
		
			
				|  |  |  |  | used by passing a hashref of options. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     my $rand_pass = passphrase->generate_random({ | 
		
	
		
			
				|  |  |  |  |         length  => 32, | 
		
	
		
			
				|  |  |  |  |         charset => ['a'..'z', 'A'..'Z'], | 
		
	
		
			
				|  |  |  |  |     }); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =cut | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | sub generate_random { | 
		
	
		
			
				|  |  |  |  |     my ($self, $options) = @_; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     # Default is 16 URL-safe base64 chars. Supported everywhere and a reasonable length | 
		
	
		
			
				|  |  |  |  |     my $length  = $options->{length}  || 16; | 
		
	
		
			
				|  |  |  |  |     my $charset = $options->{charset} || ['a'..'z', 'A'..'Z', '0'..'9', '-', '_']; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     return join '', map { @$charset[rand_int scalar @$charset] } 1..$length; | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =head1 ADDITIONAL METHODS | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | The methods are only applicable once you have called C<generate> | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     passphrase( 'my password' )->generate->rfc2307; # CORRECT | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     passphrase( 'my password' )->rfc2307;           # INCORRECT, Returns undef | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =head2 rfc2307 | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | Returns the rfc2307 representation from a C<Dancer::Plugin::Passphrase> object. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     passphrase('my password')->generate->rfc2307; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =cut | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | sub rfc2307 { | 
		
	
		
			
				|  |  |  |  |     return shift->{rfc2307} || undef; | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | sub as_rfc2307 { | 
		
	
		
			
				|  |  |  |  |     carp "as_rfc2307 method is deprecated"; | 
		
	
		
			
				|  |  |  |  |     return shift->rfc2307(); | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =head2 scheme | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | Returns the scheme name from a C<Dancer::Plugin::Passphrase> object. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | This is the scheme name as used in the RFC 2307 representation | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     passphrase('my password')->generate->scheme; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | The scheme name can be any of the following, and will always be capitalized | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     SMD5  SSHA  SSHA224  SSHA256  SSHA384  SSHA512  CRYPT | 
		
	
		
			
				|  |  |  |  |     MD5   SHA   SHA224   SHA256   SHA384   SHA512 | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =cut | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | sub scheme { | 
		
	
		
			
				|  |  |  |  |     return shift->{scheme} || undef; | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =head2 algorithm | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | Returns the algorithm name from a C<Dancer::Plugin::Passphrase> object. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | The algorithm name can be anything that is accepted by C<Digest->new($alg)> | 
		
	
		
			
				|  |  |  |  | This includes any modules in the C<Digest::> Namespace | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     passphrase('my password')->generate->algorithm; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =cut | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | sub algorithm { | 
		
	
		
			
				|  |  |  |  |     return shift->{algorithm} || undef; | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =head2 cost | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | Returns the bcrypt cost from a C<Dancer::Plugin::Passphrase> object. | 
		
	
		
			
				|  |  |  |  | Only works when using the bcrypt algorithm, returns undef for other algorithms | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     passphrase('my password')->generate->cost; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =cut | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | sub cost { | 
		
	
		
			
				|  |  |  |  |     return shift->{cost} || undef; | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =head2 salt_raw | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | Returns the raw salt from a C<Dancer::Plugin::Passphrase> object. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     passphrase('my password')->generate->salt_raw; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | Can be defined, but false - The empty string is technically a valid salt. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | Returns C<undef> if there is no salt. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =cut | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | sub salt_raw { | 
		
	
		
			
				|  |  |  |  |     return shift->{salt} // undef; | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | sub raw_salt { | 
		
	
		
			
				|  |  |  |  |     carp "raw_salt method is deprecated"; | 
		
	
		
			
				|  |  |  |  |     return shift->salt_raw(); | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =head2 hash_raw | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | Returns the raw hash from a C<Dancer::Plugin::Passphrase> object. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     passphrase('my password')->generate->hash_raw; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =cut | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | sub hash_raw { | 
		
	
		
			
				|  |  |  |  |     return shift->{hash} || undef; | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | sub raw_hash { | 
		
	
		
			
				|  |  |  |  |     carp "raw_hash method is deprecated"; | 
		
	
		
			
				|  |  |  |  |     return shift->hash_raw(); | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =head2 salt_hex | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | Returns the hex-encoded salt from a C<Dancer::Plugin::Passphrase> object. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | Can be defined, but false - The empty string is technically a valid salt. | 
		
	
		
			
				|  |  |  |  | Returns C<undef> if there is no salt. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     passphrase('my password')->generate->salt_hex; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =cut | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | sub salt_hex { | 
		
	
		
			
				|  |  |  |  |     return unpack("H*", shift->{salt}) // undef; | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =head2 hash_hex | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | Returns the hex-encoded hash from a C<Dancer::Plugin::Passphrase> object. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     passphrase('my password')->generate->hash_hex; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =cut | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | sub hash_hex { | 
		
	
		
			
				|  |  |  |  |     return unpack("H*", shift->{hash}) || undef; | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =head2 salt_base64 | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | Returns the base64 encoded salt from a C<Dancer::Plugin::Passphrase> object. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | Can be defined, but false - The empty string is technically a valid salt. | 
		
	
		
			
				|  |  |  |  | Returns C<undef> if there is no salt. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     passphrase('my password')->generate->salt_base64; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =cut | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | sub salt_base64 { | 
		
	
		
			
				|  |  |  |  |     return encode_base64(shift->{salt}, '') // undef; | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =head2 hash_base64 | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | Returns the base64 encoded hash from a C<Dancer::Plugin::Passphrase> object. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     passphrase('my password')->generate->hash_base64; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =cut | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | sub hash_base64 { | 
		
	
		
			
				|  |  |  |  |     return encode_base64(shift->{hash}, '') || undef; | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =head2 plaintext | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | Returns the plaintext password as originally supplied to the L<passphrase> keyword. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     passphrase('my password')->generate->plaintext; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =cut | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | sub plaintext { | 
		
	
		
			
				|  |  |  |  |     return shift->{plaintext} || undef; | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | # Actual generation of the hash, using the provided settings | 
		
	
		
			
				|  |  |  |  | sub _calculate_hash { | 
		
	
		
			
				|  |  |  |  |     my $self = shift; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     my $hasher = Digest->new( $self->algorithm ); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     given ($self->algorithm) { | 
		
	
		
			
				|  |  |  |  |         when ('Bcrypt') { | 
		
	
		
			
				|  |  |  |  |             $hasher->add($self->{plaintext}); | 
		
	
		
			
				|  |  |  |  |             $hasher->salt($self->salt_raw); | 
		
	
		
			
				|  |  |  |  |             $hasher->cost($self->cost); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |             $self->{hash} = $hasher->digest; | 
		
	
		
			
				|  |  |  |  |             $self->{rfc2307} | 
		
	
		
			
				|  |  |  |  |                 = '{CRYPT}$' | 
		
	
		
			
				|  |  |  |  |                 . $self->{type} . '$' | 
		
	
		
			
				|  |  |  |  |                 . $self->cost . '$' | 
		
	
		
			
				|  |  |  |  |                 . _en_bcrypt_base64($self->salt_raw) | 
		
	
		
			
				|  |  |  |  |                 . _en_bcrypt_base64($self->hash_raw); | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  |         default { | 
		
	
		
			
				|  |  |  |  |             $hasher->add($self->{plaintext}); | 
		
	
		
			
				|  |  |  |  |             $hasher->add($self->{salt}); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |             $self->{hash} = $hasher->digest; | 
		
	
		
			
				|  |  |  |  |             $self->{rfc2307} | 
		
	
		
			
				|  |  |  |  |                 = '{' . $self->{scheme} . '}' | 
		
	
		
			
				|  |  |  |  |                 . encode_base64($self->hash_raw . $self->salt_raw, ''); | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     return $self; | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | # Extracts the settings from an RFC 2307 string | 
		
	
		
			
				|  |  |  |  | sub _extract_settings { | 
		
	
		
			
				|  |  |  |  |     my ($self, $rfc2307_string) = @_; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     my ($scheme, $settings) = ($rfc2307_string =~ m/^{(\w+)}(.*)/s); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     unless ($scheme && $settings) { | 
		
	
		
			
				|  |  |  |  |         croak "An RFC 2307 compliant string must be passed to matches()"; | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     if ($scheme eq 'CRYPT'){ | 
		
	
		
			
				|  |  |  |  |         given ($settings) { | 
		
	
		
			
				|  |  |  |  |             when (/^\$2(?:a|x|y)\$/)     { | 
		
	
		
			
				|  |  |  |  |                 $scheme = 'Bcrypt'; | 
		
	
		
			
				|  |  |  |  |                 $settings =~ m{\A\$(2a|2x|2y)\$([0-9]{2})\$([./A-Za-z0-9]{22})}x; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |                 ($self->{type}, $self->{cost}, $self->{salt}) = ($1, $2, _de_bcrypt_base64($3)); | 
		
	
		
			
				|  |  |  |  |             } | 
		
	
		
			
				|  |  |  |  |             default { croak "Unknown CRYPT format: $_"; } | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     my $scheme_meta = { | 
		
	
		
			
				|  |  |  |  |         'MD5'     => { algorithm => 'MD5',     octets => 128 / 8 }, | 
		
	
		
			
				|  |  |  |  |         'SMD5'    => { algorithm => 'MD5',     octets => 128 / 8 }, | 
		
	
		
			
				|  |  |  |  |         'SHA'     => { algorithm => 'SHA-1',   octets => 160 / 8 }, | 
		
	
		
			
				|  |  |  |  |         'SSHA'    => { algorithm => 'SHA-1',   octets => 160 / 8 }, | 
		
	
		
			
				|  |  |  |  |         'SHA224'  => { algorithm => 'SHA-224', octets => 224 / 8 }, | 
		
	
		
			
				|  |  |  |  |         'SSHA224' => { algorithm => 'SHA-224', octets => 224 / 8 }, | 
		
	
		
			
				|  |  |  |  |         'SHA256'  => { algorithm => 'SHA-256', octets => 256 / 8 }, | 
		
	
		
			
				|  |  |  |  |         'SSHA256' => { algorithm => 'SHA-256', octets => 256 / 8 }, | 
		
	
		
			
				|  |  |  |  |         'SHA384'  => { algorithm => 'SHA-384', octets => 384 / 8 }, | 
		
	
		
			
				|  |  |  |  |         'SSHA384' => { algorithm => 'SHA-384', octets => 384 / 8 }, | 
		
	
		
			
				|  |  |  |  |         'SHA512'  => { algorithm => 'SHA-512', octets => 512 / 8 }, | 
		
	
		
			
				|  |  |  |  |         'SSHA512' => { algorithm => 'SHA-512', octets => 512 / 8 }, | 
		
	
		
			
				|  |  |  |  |         'Bcrypt'  => { algorithm => 'Bcrypt',  octets => 128 / 8 }, | 
		
	
		
			
				|  |  |  |  |     }; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     $self->{scheme}    = $scheme; | 
		
	
		
			
				|  |  |  |  |     $self->{algorithm} = $scheme_meta->{$scheme}->{algorithm}; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     if (!defined $self->{salt}) { | 
		
	
		
			
				|  |  |  |  |         $self->{salt} = substr(decode_base64($settings), $scheme_meta->{$scheme}->{octets}); | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     return $self; | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | # Gets the settings from config.yml, and merges them with any custom | 
		
	
		
			
				|  |  |  |  | # settings given to the constructor | 
		
	
		
			
				|  |  |  |  | sub _get_settings { | 
		
	
		
			
				|  |  |  |  |     my ($self, $options) = @_; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     $self->{algorithm} = $options->{algorithm} || | 
		
	
		
			
				|  |  |  |  |                          plugin_setting->{algorithm} || | 
		
	
		
			
				|  |  |  |  |                          'Bcrypt'; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     my $plugin_setting = plugin_setting->{$self->algorithm}; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     # Specify empty string to get an unsalted hash | 
		
	
		
			
				|  |  |  |  |     # Leaving it undefs results in 128 random bits being used as salt | 
		
	
		
			
				|  |  |  |  |     # bcrypt requires this amount, and is reasonable for other algorithms | 
		
	
		
			
				|  |  |  |  |     $self->{salt} = $options->{salt} // | 
		
	
		
			
				|  |  |  |  |                     $plugin_setting->{salt} // | 
		
	
		
			
				|  |  |  |  |                     rand_bits(128); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     # RFC 2307 scheme is based on the algorithm, with a prefixed 'S' for salted | 
		
	
		
			
				|  |  |  |  |     $self->{scheme} = join '', $self->algorithm =~ /[\w]+/g; | 
		
	
		
			
				|  |  |  |  |     $self->{scheme} = 'S'.$self->{scheme} if $self->{salt}; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     given ($self->{scheme}) { | 
		
	
		
			
				|  |  |  |  |         when ('SHA1')    { $self->{scheme} = 'SHA';   } | 
		
	
		
			
				|  |  |  |  |         when ('SSHA1')   { $self->{scheme} = 'SSHA';  } | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     # Bcrypt requires a cost parameter | 
		
	
		
			
				|  |  |  |  |     if ($self->algorithm eq 'Bcrypt') { | 
		
	
		
			
				|  |  |  |  |         $self->{scheme} = 'CRYPT'; | 
		
	
		
			
				|  |  |  |  |         $self->{type} = '2a'; | 
		
	
		
			
				|  |  |  |  |         $self->{cost} = $options->{cost} || | 
		
	
		
			
				|  |  |  |  |                         $plugin_setting->{cost} || | 
		
	
		
			
				|  |  |  |  |                         4; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         $self->{cost} = 31 if $self->cost > 31; | 
		
	
		
			
				|  |  |  |  |         $self->{cost} = sprintf("%02d", $self->cost); | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     return $self; | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | # From Crypt::Eksblowfish::Bcrypt. | 
		
	
		
			
				|  |  |  |  | # Bcrypt uses it's own variation on base64 | 
		
	
		
			
				|  |  |  |  | sub _en_bcrypt_base64 { | 
		
	
		
			
				|  |  |  |  |     my ($octets) = @_; | 
		
	
		
			
				|  |  |  |  |     my $text = encode_base64($octets, ''); | 
		
	
		
			
				|  |  |  |  |     $text =~ tr{A-Za-z0-9+/=}{./A-Za-z0-9}d; | 
		
	
		
			
				|  |  |  |  |     return $text; | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | # And the decoder of bcrypt's custom base64 | 
		
	
		
			
				|  |  |  |  | sub _de_bcrypt_base64 { | 
		
	
		
			
				|  |  |  |  |     my ($text) = @_; | 
		
	
		
			
				|  |  |  |  |     $text =~ tr{./A-Za-z0-9}{A-Za-z0-9+/}; | 
		
	
		
			
				|  |  |  |  |     $text .= "=" x (3 - (length($text) + 3) % 4); | 
		
	
		
			
				|  |  |  |  |     return decode_base64($text); | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | register_plugin for_versions => [ 1, 2 ]; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | 1; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =head1 MORE INFORMATION | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =head2 Purpose | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | The aim of this module is to help you store new passwords in a secure manner,  | 
		
	
		
			
				|  |  |  |  | whilst still being able to verify and upgrade older passwords. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | Cryptography is a vast and complex field. Many people try to roll their own  | 
		
	
		
			
				|  |  |  |  | methods for securing user data, but succeed only in coming up with  | 
		
	
		
			
				|  |  |  |  | a system that has little real security. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | This plugin provides a simple way of managing that complexity, allowing  | 
		
	
		
			
				|  |  |  |  | developers to follow crypto best practice without having to become an expert. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =head2 Rationale | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | The module defaults to hashing passwords using the bcrypt algorithm, returning them | 
		
	
		
			
				|  |  |  |  | in RFC 2307 format. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | RFC 2307 describes an encoding system for passphrase hashes, as used in the "userPassword" | 
		
	
		
			
				|  |  |  |  | attribute in LDAP databases. It encodes hashes as ASCII text, and supports several  | 
		
	
		
			
				|  |  |  |  | passphrase schemes by starting the encoding with an alphanumeric scheme identifier enclosed  | 
		
	
		
			
				|  |  |  |  | in braces. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | RFC 2307 only specifies the C<MD5>, and C<SHA> schemes - however in real-world usage, | 
		
	
		
			
				|  |  |  |  | schemes that are salted are widely supported, and are thus provided by this module. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | Bcrypt is an adaptive hashing algorithm that is designed to resist brute  | 
		
	
		
			
				|  |  |  |  | force attacks by including a cost (aka work factor). This cost increases  | 
		
	
		
			
				|  |  |  |  | the computational effort it takes to compute the hash. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | SHA and MD5 are designed to be fast, and modern machines compute a billion  | 
		
	
		
			
				|  |  |  |  | hashes a second. With computers getting faster every day, brute forcing  | 
		
	
		
			
				|  |  |  |  | SHA hashes is a very real problem that cannot be easily solved. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | Increasing the cost of generating a bcrypt hash is a trivial way to make  | 
		
	
		
			
				|  |  |  |  | brute forcing ineffective. With a low cost setting, bcrypt is just as secure  | 
		
	
		
			
				|  |  |  |  | as a more traditional SHA+salt scheme, and just as fast. Increasing the cost | 
		
	
		
			
				|  |  |  |  | as computers become more powerful keeps you one step ahead | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | For a more detailed description of why bcrypt is preferred, see this article:  | 
		
	
		
			
				|  |  |  |  | L<http://codahale.com/how-to-safely-store-a-password/> | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =head2 Configuration | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | In your applications config file, you can set the default hashing algorithm, | 
		
	
		
			
				|  |  |  |  | and the default settings for every supported algorithm. Calls to | 
		
	
		
			
				|  |  |  |  | L<generate()|/"passphrase__generate"> will use the default settings | 
		
	
		
			
				|  |  |  |  | for that algorithm specified in here. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | You can override these defaults when you call L<generate()|/"passphrase__generate">. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | If you do no configuration at all, the default is to bcrypt with a cost of 4, and  | 
		
	
		
			
				|  |  |  |  | a strong psuedo-random salt. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     plugins: | 
		
	
		
			
				|  |  |  |  |         Passphrase: | 
		
	
		
			
				|  |  |  |  |             default: Bcrypt | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |             Bcrypt: | 
		
	
		
			
				|  |  |  |  |                 cost: 8 | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =head2 Storage in a database | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | You should be storing the RFC 2307 string in your database, it's the easiest way | 
		
	
		
			
				|  |  |  |  | to use this module. You could store the C<raw_salt>, C<raw_hash>, and C<scheme> | 
		
	
		
			
				|  |  |  |  | separately, but this strongly discouraged. RFC 2307 strings are specifically | 
		
	
		
			
				|  |  |  |  | designed for storing hashed passwords, and should be used wherever possible. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | The length of the string produced by L<generate()|/"passphrase__generate"> can | 
		
	
		
			
				|  |  |  |  | vary dependent on your settings. Below is a table of the lengths generated | 
		
	
		
			
				|  |  |  |  | using default settings. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | You will need to make sure your database columns are at least this long. | 
		
	
		
			
				|  |  |  |  | If the string gets truncated, the password can I<never> be validated. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     ALGORITHM   LENGTH  EXAMPLE RFC 2307 STRING | 
		
	
		
			
				|  |  |  |  |      | 
		
	
		
			
				|  |  |  |  |     Bcrypt      68      {CRYPT}$2a$04$MjkMhQxasFQod1qq56DXCOvWu6YTWk9X.EZGnmSSIbbtyEBIAixbS | 
		
	
		
			
				|  |  |  |  |     SHA-512     118     {SSHA512}lZG4dZ5EU6dPEbJ1kBPPzEcupFloFSIJjiXCwMVxJXOy/x5qhBA5XH8FiUWj7u59onQxa97xYdqje/fwY5TDUcW1Urplf3KHMo9NO8KO47o= | 
		
	
		
			
				|  |  |  |  |     SHA-384     98      {SSHA384}SqZF5YYyk4NdjIM8YgQVfRieXDxNG0dKH4XBcM40Eblm+ribCzdyf0JV7i2xJvVHZsFSQNcuZPKtiTMzDyOU+w== | 
		
	
		
			
				|  |  |  |  |     SHA-256     74      {SSHA256}xsJHNzPlNCpOZ41OkTfQOU35ZY+nRyZFaM8lHg5U2pc0xT3DKNlGW2UTY0NPYsxU | 
		
	
		
			
				|  |  |  |  |     SHA-224     70      {SSHA224}FTHNkvKOdyX1d6f45iKLVxpaXZiHel8pfilUT1dIZ5u+WIUyhDGxLnx72X0= | 
		
	
		
			
				|  |  |  |  |     SHA-1       55      {SSHA}Qsaao/Xi/bYTRMQnpHuD3y5nj02wbdcw5Cek2y2nLs3pIlPh | 
		
	
		
			
				|  |  |  |  |     MD5         51      {SMD5}bgfLiUQWgzUm36+nBhFx62bi0xdwTp+UpEeNKDxSLfM= | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =head2 Common Mistakes | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | Common mistakes people make when creating their own solution. If any of these  | 
		
	
		
			
				|  |  |  |  | seem familiar, you should probably be using this module | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =over | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =item Passwords are stored as plain text for a reason | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | There is never a valid reason to store a password as plain text. | 
		
	
		
			
				|  |  |  |  | Passwords should be reset and not emailed to customers when they forget. | 
		
	
		
			
				|  |  |  |  | Support people should be able to login as a user without knowing the users password. | 
		
	
		
			
				|  |  |  |  | No-one except the user should know the password - that is the point of authentication. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =item No-one will ever guess our super secret algorithm! | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | Unless you're a cryptography expert with many years spent studying  | 
		
	
		
			
				|  |  |  |  | super-complex maths, your algorithm is almost certainly not as secure  | 
		
	
		
			
				|  |  |  |  | as you think. Just because it's hard for you to break doesn't mean | 
		
	
		
			
				|  |  |  |  | it's difficult for a computer. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =item Our application-wide salt is "Sup3r_S3cret_L0ng_Word" - No-one will ever guess that. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | This is common misunderstanding of what a salt is meant to do. The purpose of a  | 
		
	
		
			
				|  |  |  |  | salt is to make sure the same password doesn't always generate the same hash. | 
		
	
		
			
				|  |  |  |  | A fresh salt needs to be created each time you hash a password. It isn't meant  | 
		
	
		
			
				|  |  |  |  | to be a secret key. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =item We generate our random salt using C<rand>. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | C<rand> isn't actually random, it's a non-unform pseudo-random number generator,  | 
		
	
		
			
				|  |  |  |  | and not suitable for cryptographic applications. Whilst this module also defaults to  | 
		
	
		
			
				|  |  |  |  | a PRNG, it is better than the one provided by C<rand>. Using a true RNG is a config | 
		
	
		
			
				|  |  |  |  | option away, but is not the default as it it could potentially block output if the | 
		
	
		
			
				|  |  |  |  | system does not have enough entropy to generate a truly random number | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =item We use C<md5(pass.salt)>, and the salt is from C</dev/random> | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | MD5 has been broken for many years. Commodity hardware can find a  | 
		
	
		
			
				|  |  |  |  | hash collision in seconds, meaning an attacker can easily generate  | 
		
	
		
			
				|  |  |  |  | the correct MD5 hash without using the correct password. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =item We use C<sha(pass.salt)>, and the salt is from C</dev/random> | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | SHA isn't quite as broken as MD5, but it shares the same theoretical  | 
		
	
		
			
				|  |  |  |  | weaknesses. Even without hash collisions, it is vulnerable to brute forcing. | 
		
	
		
			
				|  |  |  |  | Modern hardware is so powerful it can try around a billion hashes a second.  | 
		
	
		
			
				|  |  |  |  | That means every 7 chracter password in the range [A-Za-z0-9] can be cracked  | 
		
	
		
			
				|  |  |  |  | in one hour on your average desktop computer. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =item If the only way to break the hash is to brute-force it, it's secure enough | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | It is unlikely that your database will be hacked and your hashes brute forced. | 
		
	
		
			
				|  |  |  |  | However, in the event that it does happen, or SHA512 is broken, using this module | 
		
	
		
			
				|  |  |  |  | gives you an easy way to change to a different algorithm, while still allowing | 
		
	
		
			
				|  |  |  |  | you to validate old passphrases | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =back | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =head1 KNOWN ISSUES | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | If you see errors like this | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     Wide character in subroutine entry | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | or | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     Input must contain only octets | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | The C<MD5>, C<bcrypt>, and C<SHA> algorithms can't handle chracters with an ordinal | 
		
	
		
			
				|  |  |  |  | value above 255, producing errors like this if they encounter them. | 
		
	
		
			
				|  |  |  |  | It is not possible for this plugin to automagically work out the correct | 
		
	
		
			
				|  |  |  |  | encoding for a given string. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | If you see errors like this, then you probably need to use the L<Encode> module | 
		
	
		
			
				|  |  |  |  | to encode your text as UTF-8 (or whatever encoding it is) before giving it  | 
		
	
		
			
				|  |  |  |  | to C<passphrase>. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | Text encoding is a bag of hurt, and errors like this are probably indicitive | 
		
	
		
			
				|  |  |  |  | of deeper problems within your app's code. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | You will save yourself a lot of trouble if you read up on the | 
		
	
		
			
				|  |  |  |  | L<Encode> module sooner rather than later. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | For further reading on UTF-8, unicode, and text encoding in perl, | 
		
	
		
			
				|  |  |  |  | see L<http://training.perl.com/OSCON2011/index.html> | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =head1 SEE ALSO | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | L<Dancer>, L<Digest>, L<Crypt::Eksblowfish::Bcrypt>, L<Dancer::Plugin::Bcrypt> | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =head1 AUTHOR | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | James Aitken <jaitken@cpan.org> | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =head1 COPYRIGHT AND LICENSE | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | This software is copyright (c) 2012 by James Aitken. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | This is free software; you can redistribute it and/or modify it under | 
		
	
		
			
				|  |  |  |  | the same terms as the Perl 5 programming language system itself. | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | =cut |