Skip Menu |

This queue is for tickets about the Authen-Passphrase CPAN distribution.

Report information
The Basics
Id: 68043
Status: open
Priority: 0/
Queue: Authen-Passphrase

People
Owner: Nobody in particular
Requestors: MORIYA [...] cpan.org
Cc:
AdminCc:

Bug Information
Severity: Wishlist
Broken in: (no value)
Fixed in: (no value)



Subject: [PATCH] Adding a feature which implements "hash based key stretching"
Hello, the attached patch tries to add "hash based key stretching" (*1) optional feature to Authen::Passphrase::SaltedDigest and to change t/smd5.t and t/ssha.t for verify the feature. The feature was mentioned in some standard books (*2) and implemented in several software such as crypt (*3) and WAF (web application framework) (*4). Users of the nice module will can opt for a stronger way to hash a salted passphrase. Feel free to adjust accordingly for the next release if you like it. [A supplementary explanation] Sorry to be speculation, perhaps you will think that the "optional" feature should be implemented on the side of an WAF such as Catalyst::Authentication::Credential::Password. However, in my opinion, the it is not the best way. Let me humbly say, the recent trend is not to depend on WAF for model-associated procedures but to handle it by home-brew POPO (plain old perl object). In doing so, we will gain more maintainability and more testability because it POPO model will can be used in both WAF and others such as CLI (command-line interface) and test scripts. [Footnotes] *1) http://en.wikipedia.org/wiki/Key_stretching#Hash_based_key_stretching *2) "Cryptography Engineering" by Niels Ferguson, Bruce Schneier and Tadayoshi Kohno (ISBN-13: 978-0470474242) *3) man 3 crypt http://www.kernel.org/doc/man-pages/online/pages/man3/crypt.3.html *4) "restful_authentication", the famous plug-in for Ruby on Rails https://github.com/technoweenie/restful-authentication I appreciate your consideration. Regards, -- MORIYA Masaki, alias Gardejo
Subject: add-stretching.patch
diff -crN original\lib\Authen\Passphrase\SaltedDigest.pm patched\lib\Authen\Passphrase\SaltedDigest.pm *** original\lib\Authen\Passphrase\SaltedDigest.pm Sat Jul 31 03:41:06 2010 --- patched\lib\Authen\Passphrase\SaltedDigest.pm Tue May 10 01:18:44 2011 *************** *** 151,156 **** --- 151,161 ---- A passphrase that will be accepted. + =item B<stretch_count> + + A stretching frequency, as a positive integer. Defaults to the empty + integer, yielding a scheme that will be stretched one time. + =back The digest algorithm must be given, and either the hash or the passphrase. *************** *** 205,216 **** --- 210,226 ---- if exists($self->{hash}) || defined($passphrase); $passphrase = $value; + } elsif($attr eq "stretch_count") { + croak "\"$value\" is not a valid stretch count" + unless $value == int($value) && $value > 0; + $self->{stretch_count} = $value; } else { croak "unrecognised attribute `$attr'"; } } croak "algorithm not specified" unless exists $self->{algorithm}; $self->{salt} = "" unless exists $self->{salt}; + $self->{stretch_count} = 1 unless exists $self->{stretch_count}; if(defined $passphrase) { $self->{hash} = $self->_hash_of($passphrase); } elsif(exists $self->{hash}) { *************** *** 248,254 **** ); sub from_rfc2307 { ! my($class, $userpassword) = @_; return $class->SUPER::from_rfc2307($userpassword) unless $userpassword =~ /\A\{([-0-9A-Za-z]+)\}/; my $scheme = uc($1); --- 258,264 ---- ); sub from_rfc2307 { ! my($class, $userpassword, $stretch_count) = @_; return $class->SUPER::from_rfc2307($userpassword) unless $userpassword =~ /\A\{([-0-9A-Za-z]+)\}/; my $scheme = uc($1); *************** *** 267,273 **** --- 277,290 ---- if length($hash_and_salt) < $hash_len; croak "too much hash data for {$scheme}" if !$salt_allowed && length($hash_and_salt) > $hash_len; + if (defined $stretch_count) { + croak "\"$stretch_count\" is not a valid stretch count" + unless $stretch_count == int($stretch_count) && $stretch_count > 0; + } else { + $stretch_count = 1; + } return $class->new(algorithm => $algorithm, + stretch_count => $stretch_count, salt => substr($hash_and_salt, $hash_len), hash => substr($hash_and_salt, 0, $hash_len)); } *************** *** 373,381 **** } else { croak "algorithm specifier `$alg' is of an unrecognised type"; } ! $ctx->add($passphrase); ! $ctx->add($self->{salt}); ! return $ctx->digest; } sub match { --- 390,403 ---- } else { croak "algorithm specifier `$alg' is of an unrecognised type"; } ! my $digest = ""; ! foreach my $count (1 .. $self->{stretch_count}) { ! $ctx->add($digest); ! $ctx->add($passphrase); ! $ctx->add($self->{salt}); ! $digest = $ctx->digest; ! } ! return $digest; } sub match { diff -crN original\t\smd5.t patched\t\smd5.t *** original\t\smd5.t Sat Jul 31 03:41:06 2010 --- patched\t\smd5.t Tue May 10 01:10:52 2011 *************** *** 1,7 **** use warnings; use strict; ! use Test::More tests => 81; use MIME::Base64 2.21 qw(encode_base64); --- 1,7 ---- use warnings; use strict; ! use Test::More tests => 86; use MIME::Base64 2.21 qw(encode_base64); *************** *** 25,38 **** is $ppr->hash_hex, "04bd9d36c5c5497984a2670ed2442f9d"; like $ppr->as_rfc2307, qr/\A\{SMD5\}/; ! $ppr = Authen::Passphrase::SaltedDigest ! ->new(algorithm => "MD5", salt_random => 13, ! passphrase => "wibble"); ! ok $ppr; ! is length($ppr->salt), 13; ! is length($ppr->hash), 16; ! like $ppr->as_rfc2307, qr/\A\{SMD5\}/; ! ok $ppr->match("wibble"); my %pprs; my $i = 0; --- 25,40 ---- is $ppr->hash_hex, "04bd9d36c5c5497984a2670ed2442f9d"; like $ppr->as_rfc2307, qr/\A\{SMD5\}/; ! foreach my $stretch_count (1, 10) { ! $ppr = Authen::Passphrase::SaltedDigest ! ->new(algorithm => "MD5", salt_random => 13, ! passphrase => "wibble", stretch_count => $stretch_count); ! ok $ppr; ! is length($ppr->salt), 13; ! is length($ppr->hash), 16; ! like $ppr->as_rfc2307, qr/\A\{SMD5\}/; ! ok $ppr->match("wibble"); ! } my %pprs; my $i = 0; diff -crN original\t\ssha.t patched\t\ssha.t *** original\t\ssha.t Sat Jul 31 03:41:06 2010 --- patched\t\ssha.t Tue May 10 01:28:16 2011 *************** *** 1,7 **** use warnings; use strict; ! use Test::More tests => 70; use MIME::Base64 2.21 qw(encode_base64); --- 1,7 ---- use warnings; use strict; ! use Test::More tests => 80; use MIME::Base64 2.21 qw(encode_base64); *************** *** 13,18 **** --- 13,41 ---- is $ppr->algorithm, "SHA-1"; is $ppr->salt_hex, "f5895a0c"; is $ppr->hash_hex, "4aedd0ba6148469d7115f459e3d8706899a67bea"; + + my $salt_hex = "112233"; + my $hash_hex = "893a490e37b4795e29f7f93f15c9b8d30b25b506"; + my $rightphrase = "passphrase"; + $ppr = Authen::Passphrase::SaltedDigest + ->from_rfc2307("{SSHA}iTpJDje0eV4p9/k/Fcm40wsltQYRIjM=", 10); + ok $ppr; + is $ppr->salt_hex, $salt_hex; + is $ppr->hash_hex, $hash_hex; + ok $ppr->match($rightphrase); + + my @options = ({hash_hex => $hash_hex}, {passphrase => $rightphrase}); + foreach my $option (@options) { + my $ppr = Authen::Passphrase::SaltedDigest + ->new(algorithm => "SHA-1", + salt_hex => $salt_hex, + %$option, + stretch_count => 10); + ok $ppr; + foreach my $passphrase ($rightphrase, $rightphrase . 'blahblahblah') { + ok ($ppr->match($passphrase) xor $passphrase ne $rightphrase); + } + } my %pprs; my $i = 0;
I'm sorry, I made a typo: Show quoted text
> the it is not the best way.
I collect it as the below: Show quoted text
> it is not the best way.
-- MORIYA Masaki, alias Gardejo
Subject: Re: [rt.cpan.org #68043] [PATCH] Adding a feature which implements "hash based key stretching"
Date: Mon, 9 May 2011 17:52:31 +0100
To: MORIYA Masaki via RT <bug-Authen-Passphrase [...] rt.cpan.org>
From: Zefram <zefram [...] fysh.org>
MORIYA Masaki via RT wrote: Show quoted text
>the attached patch tries to add "hash based key stretching" (*1) >optional feature to Authen::Passphrase::SaltedDigest
This stretching scheme, if it is indeed used for passphrase hashing (which I need to research a bit), is indeed suitable material for the Authen::Passphrase suite. However, I'm not convinced that A:P:SaltedDigest is the right place for it. I think it would fit better as a separate class, since there are many other ways in which the SaltedDigest scheme can be modified. This is akin to the way the multi-block extensions of traditional DES crypt() are implemented in separate modules A:P:BigCrypt and A:P:Crypt16 rather than being included in A:P:DESCrypt. (Though I did include BSDi's minor extension in the latter.) In any case, your proposed extension to the from_rfc2307 interface is not acceptable. The purpose of the RFC 2307 format is to represent the whole passphrase recogniser in one text string, and the method interface reflects that. The stretch count cannot be a separate parameter that changes the interpretation of an RFC 2307 string; it needs to be incorporated into the string. I'll omit any scheme for doing this until there's a de facto standard. I'll examine the pages you've linked to. I'll probably implement the stretching scheme as a separate class under Authen::Passphrase, with (initially) no textual format. -zefram
Thank you very much for prompt and detailed reply. I am glad that you considered implementing of the stretching scheme in Authen:Passphrase family. Then, I was convinced of an interpretation of RFC 2307 by your explanation. Please excuse my not having studied. I learned so much. Regards, -- MORIYA Masaki, alias Gardejo
I'm sorry to keep pestering you, I'm afraid I was utterly mistaken about implementation of the said scheme. To be exact: package Authen::Passphrase::SaltedAndStretchedDigest; # tentative name # ... (snip) ... sub _hash_of { # ... (snip) ... my $hash_hex = ""; my $salt_hex = $self->salt_hex; for (1 .. $self->{stretch_count}) { $ctx->add($hash_hex); $ctx->add($passphrase); $ctx->add($salt_hex); $hash_hex = $ctx->hexdigest; } return pack('H*', $hash_hex); } # ... (snip) ... The scheme has been written in other languages (Java, PHP (and Japanese)) here: http://neta.ywcafe.net/001184.html In addition, I confirmed the above implementation: my $ppr = Authen::Passphrase::SaltedAndStretchedDigest->new( passphrase => 'mypassword#1234', salt_hex => '93ab3eaaf393e331ac6384185e68062736fc11d24617461a214afafe2e52af89', stretch_count => 1000, ); is $ppr->hash_hex, '2564c88ee01c69d19354b39807536010759f7cbc1f25811bff20101150b8c841'; These passed and returned values are same as ones in the linked page. Regards, -- MORIYA Masaki, alias Gardejo
I am really sorry, the missing line in the recent example is: algorithm => 'SHA-256', From now on, I will be calm during a correspondence. Regards, -- MORIYA Masaki, alias Gardejo
From: felix.ostmann [...] thewar.de
Am Mo 09. Mai 2011, 18:26:03, MORIYA schrieb: Show quoted text
> I am really sorry, the missing line in the recent example is: > > algorithm => 'SHA-256', > > From now on, I will be calm during a correspondence. > > Regards,
Perhaps the best is to implement Crypt::PBKDF2 or a small own (non Moose) PBKDF2 implementation ...