Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package perl-Crypt-SaltedHash for
openSUSE:Factory checked in at 2026-06-09 14:25:54
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/perl-Crypt-SaltedHash (Old)
and /work/SRC/openSUSE:Factory/.perl-Crypt-SaltedHash.new.2375 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "perl-Crypt-SaltedHash"
Tue Jun 9 14:25:54 2026 rev:12 rq:1358064 version:0.120.0
Changes:
--------
---
/work/SRC/openSUSE:Factory/perl-Crypt-SaltedHash/perl-Crypt-SaltedHash.changes
2026-05-21 18:33:38.422105271 +0200
+++
/work/SRC/openSUSE:Factory/.perl-Crypt-SaltedHash.new.2375/perl-Crypt-SaltedHash.changes
2026-06-09 14:28:01.647732368 +0200
@@ -1,0 +2,10 @@
+Sat May 23 07:44:13 UTC 2026 - Tina Müller <[email protected]>
+
+- updated to 0.120.0 (0.12)
+ see /usr/share/doc/packages/perl-Crypt-SaltedHash/Changes
+
+ 0.12 2026-05-23 07:29:34+01:00 Europe/London
+ - Add reference to Crypt::Passphrase::SaltedHash
+ - Removed DOS line-endings
+
+-------------------------------------------------------------------
Old:
----
Crypt-SaltedHash-0.11.tar.gz
New:
----
Crypt-SaltedHash-0.12.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ perl-Crypt-SaltedHash.spec ++++++
--- /var/tmp/diff_new_pack.Fk3qwo/_old 2026-06-09 14:28:02.555770056 +0200
+++ /var/tmp/diff_new_pack.Fk3qwo/_new 2026-06-09 14:28:02.555770056 +0200
@@ -18,10 +18,10 @@
%define cpan_name Crypt-SaltedHash
Name: perl-Crypt-SaltedHash
-Version: 0.110.0
+Version: 0.120.0
Release: 0
-# 0.11 -> normalize -> 0.110.0
-%define cpan_version 0.11
+# 0.12 -> normalize -> 0.120.0
+%define cpan_version 0.12
License: Artistic-1.0 OR GPL-1.0-or-later
Summary: Perl interface to functions that assist in working with salted
hashes
URL: https://metacpan.org/release/%{cpan_name}
++++++ Crypt-SaltedHash-0.11.tar.gz -> Crypt-SaltedHash-0.12.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Crypt-SaltedHash-0.11/Changes
new/Crypt-SaltedHash-0.12/Changes
--- old/Crypt-SaltedHash-0.11/Changes 2026-05-20 15:05:08.000000000 +0200
+++ new/Crypt-SaltedHash-0.12/Changes 2026-05-23 08:29:35.000000000 +0200
@@ -1,5 +1,9 @@
Revision history for Perl module Crypt::SaltedHash
+0.12 2026-05-23 07:29:34+01:00 Europe/London
+ - Add reference to Crypt::Passphrase::SaltedHash
+ - Removed DOS line-endings
+
0.11 2026-05-20 14:05:07+01:00 Europe/London
- Fixed metadata
- Moved author tests into xt
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Crypt-SaltedHash-0.11/MANIFEST
new/Crypt-SaltedHash-0.12/MANIFEST
--- old/Crypt-SaltedHash-0.11/MANIFEST 2026-05-20 15:05:08.000000000 +0200
+++ new/Crypt-SaltedHash-0.12/MANIFEST 2026-05-23 08:29:35.000000000 +0200
@@ -16,6 +16,7 @@
t/bug-localize-regex-vars.t
xt/author/clean-namespaces.t
xt/author/eof.t
+xt/author/eol.t
xt/author/minimum-version.t
xt/author/no-tabs.t
xt/author/pod-syntax.t
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Crypt-SaltedHash-0.11/META.json
new/Crypt-SaltedHash-0.12/META.json
--- old/Crypt-SaltedHash-0.11/META.json 2026-05-20 15:05:08.000000000 +0200
+++ new/Crypt-SaltedHash-0.12/META.json 2026-05-23 08:29:35.000000000 +0200
@@ -26,6 +26,7 @@
"Test::CleanNamespaces" : "0.15",
"Test::DistManifest" : "0",
"Test::EOF" : "0",
+ "Test::EOL" : "0",
"Test::Fixme" : "0",
"Test::MinimumVersion" : "0",
"Test::More" : "0.94",
@@ -60,7 +61,7 @@
"provides" : {
"Crypt::SaltedHash" : {
"file" : "lib/Crypt/SaltedHash.pm",
- "version" : "0.11"
+ "version" : "0.12"
}
},
"release_status" : "stable",
@@ -75,7 +76,7 @@
},
"x_authority" : "cpan:RRWO"
},
- "version" : "0.11",
+ "version" : "0.12",
"x_contributors" : [
"Chris Weyl <[email protected]>",
"David Steinbrunner <[email protected]>",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Crypt-SaltedHash-0.11/META.yml
new/Crypt-SaltedHash-0.12/META.yml
--- old/Crypt-SaltedHash-0.11/META.yml 2026-05-20 15:05:08.000000000 +0200
+++ new/Crypt-SaltedHash-0.12/META.yml 2026-05-23 08:29:35.000000000 +0200
@@ -20,7 +20,7 @@
provides:
Crypt::SaltedHash:
file: lib/Crypt/SaltedHash.pm
- version: '0.11'
+ version: '0.12'
requires:
Crypt::SysRandom: '0'
Digest: '0'
@@ -32,7 +32,7 @@
Authority: cpan:RRWO
bugtracker: http://rt.cpan.org/Public/Dist/Display.html?Name=Crypt-SaltedHash
repository: git://github.com/robrwo/perl-Crypt-SaltedHash.git
-version: '0.11'
+version: '0.12'
x_contributors:
- 'Chris Weyl <[email protected]>'
- 'David Steinbrunner <[email protected]>'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Crypt-SaltedHash-0.11/Makefile.PL
new/Crypt-SaltedHash-0.12/Makefile.PL
--- old/Crypt-SaltedHash-0.11/Makefile.PL 2026-05-20 15:05:08.000000000
+0200
+++ new/Crypt-SaltedHash-0.12/Makefile.PL 2026-05-23 08:29:35.000000000
+0200
@@ -30,7 +30,7 @@
"Test::More" => 0,
"warnings" => 0
},
- "VERSION" => "0.11",
+ "VERSION" => "0.12",
"test" => {
"TESTS" => "t/*.t"
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Crypt-SaltedHash-0.11/README
new/Crypt-SaltedHash-0.12/README
--- old/Crypt-SaltedHash-0.11/README 2026-05-20 15:05:08.000000000 +0200
+++ new/Crypt-SaltedHash-0.12/README 2026-05-23 08:29:35.000000000 +0200
@@ -21,6 +21,9 @@
modules that support more secure algorithms and hashing options, and
are extensible, such as Crypt::Passphrase.
+ You can use the Crypt::Passphrase::SaltedHash validator to migrate to
+ more secure algorithms.
+
DESCRIPTION
The Crypt::SaltedHash module provides an object oriented interface to
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Crypt-SaltedHash-0.11/cpanfile
new/Crypt-SaltedHash-0.12/cpanfile
--- old/Crypt-SaltedHash-0.11/cpanfile 2026-05-20 15:05:08.000000000 +0200
+++ new/Crypt-SaltedHash-0.12/cpanfile 2026-05-23 08:29:35.000000000 +0200
@@ -30,6 +30,7 @@
requires "Test::CleanNamespaces" => "0.15";
requires "Test::DistManifest" => "0";
requires "Test::EOF" => "0";
+ requires "Test::EOL" => "0";
requires "Test::Fixme" => "0";
requires "Test::MinimumVersion" => "0";
requires "Test::More" => "0.94";
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Crypt-SaltedHash-0.11/dist.ini
new/Crypt-SaltedHash-0.12/dist.ini
--- old/Crypt-SaltedHash-0.11/dist.ini 2026-05-20 15:05:08.000000000 +0200
+++ new/Crypt-SaltedHash-0.12/dist.ini 2026-05-23 08:29:35.000000000 +0200
@@ -94,8 +94,8 @@
; authordep Test::CleanNamespaces
[Test::EOF]
; authordep Test::EOF
-; [Test::EOL]
-; :version = 0.14
+[Test::EOL]
+:version = 0.14
[Test::Fixme]
[Test::MinimumVersion]
[Test::NoTabs]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Crypt-SaltedHash-0.11/lib/Crypt/SaltedHash.pm
new/Crypt-SaltedHash-0.12/lib/Crypt/SaltedHash.pm
--- old/Crypt-SaltedHash-0.11/lib/Crypt/SaltedHash.pm 2026-05-20
15:05:08.000000000 +0200
+++ new/Crypt-SaltedHash-0.12/lib/Crypt/SaltedHash.pm 2026-05-23
08:29:35.000000000 +0200
@@ -1,444 +1,446 @@
-package Crypt::SaltedHash;
-
-use v5.6.0;
-use strict;
-
-use Crypt::SysRandom ();
-use Digest ();
-use MIME::Base64 ();
-use POSIX ();
-
-our $VERSION = '0.11';
-
-=encoding latin1
-
-=head1 NAME
-
-Crypt::SaltedHash - Perl interface to functions that assist in working
-with salted hashes.
-
-=head1 SYNOPSIS
-
- use Crypt::SaltedHash;
-
- my $csh = Crypt::SaltedHash->new(algorithm => 'SHA-1');
- $csh->add('secret');
-
- my $salted = $csh->generate;
- my $valid = Crypt::SaltedHash->validate($salted, 'secret');
-
-
-=head1 STATUS
-
-This module is deprecated.
-
-This module has not had significant updates since 2006.
-There are newer modules that support more secure algorithms and hashing
options,
-and are extensible, such as L<Crypt::Passphrase>.
-
-=head1 DESCRIPTION
-
-The C<Crypt::SaltedHash> module provides an object oriented interface to
-create salted (or seeded) hashes of clear text data. The original
-formalization of this concept comes from RFC-3112 and is extended by the use
-of different digital algorithms.
-
-=head1 ABSTRACT
-
-=head2 Setting the data
-
-The process starts with 2 elements of data:
-
-=over
-
-=item *
-
-a clear text string (this could represent a password for instance).
-
-=item *
-
-the salt, a random seed of data. This is the value used to augment a hash in
order to
-ensure that 2 hashes of identical data yield different output.
-
-=back
-
-For the purposes of this abstract we will analyze the steps within code that
perform the necessary actions
-to achieve the endresult hashes. Cryptographers call this hash a digest. We
will not however go into an explanation
-of a one-way encryption scheme. Readers of this abstract are encouraged to get
information on that subject by
-their own.
-
-Theoretically, an implementation of a one-way function as an algorithm takes
input, and provides output, that are both
-in binary form; realistically though digests are typically encoded and stored
in a database or in a flat text or XML file.
-Take slappasswd5 for instance, it performs the exact functionality described
above. We will use it as a black box compiled
-piece of code for our analysis.
-
-In pseudocode we generate a salted hash as follows:
-
- Get the source string and salt as separate binary objects
- Concatenate the 2 binary values
- Hash the concatenation into SaltedPasswordHash
- Base64Encode(concat(SaltedPasswordHash, Salt))
-
-We take a clear text string and hash this into a binary object representing
the hashed value of the clear text string plus the random salt.
-Then we have the Salt value, which are typically 4 bytes of purely random
binary data represented as hexadecimal notation (Base16 as 8 bytes).
-
-Using SHA-1 as the hashing algorithm, SaltedPasswordHash is of length 20
(bytes) in raw binary form
-(40 bytes if we look at it in hex). Salt is then 4 bytes in raw binary form.
The SHA-1 algorithm generates
-a 160 bit hash string. Consider that 8 bits = 1 byte. So 160 bits = 20 bytes,
which is exactly what the
-algorithm gives us.
-
-The Base64 encoding of the binary result looks like:
-
- {SSHA}B0O0XSYdsk7g9K229ZEr73Lid7HBD9DX
-
-Take note here that the final output is a 32-byte string of data. The Base64
encoding process uses bit shifting, masking, and padding as per RFC-3548.
-
-A couple of examples of salted hashes using on the same exact clear-text
string:
-
- slappasswd -s testing123
- {SSHA}72uhy5xc1AWOLwmNcXALHBSzp8xt4giL
-
- slappasswd -s testing123
- {SSHA}zmIAVaKMmTngrUi4UlS0dzYwVAbfBTl7
-
- slappasswd -s testing123
- {SSHA}Be3F12VVvBf9Sy6MSqpOgAdEj6JCZ+0f
-
- slappasswd -s testing123
- {SSHA}ncHs4XYmQKJqL+VuyNQzQjwRXfvu6noa
-
-4 runs of slappasswd against the same clear text string each yielded unique
endresult hashes.
-The random salt is generated silently and never made visible.
-
-=head2 Extracting the data
-
-One of the keys to note is that the salt is dealt with twice in the process.
It is used once for the actual application of randomness to the
-given clear text string, and then it is stored within the final output as
purely Base64 encoded data. In order to perform an authentication
-query for instance, we must break apart the concatenation that was created for
storage of the data. We accomplish this by splitting
-up the binary data we get after Base64 decoding the stored hash.
-
-In pseudocode we would perform the extraction and verification operations as
such:
-
- Strip the hash identifier from the Digest
- Base64Decode(Digest, 20)
- Split Digest into 2 byte arrays, one for bytes 0 � 20(pwhash), one for
bytes 21 � 32 (salt)
- Get the target string and salt as separate binary object
- Concatenate the 2 binary values
- SHA hash the concatenation into targetPasswordHash
- Compare targetPasswordHash with pwhash
- Return corresponding Boolean value
-
-Our job is to split the original digest up into 2 distinct byte arrays, one of
the left 20 (0 - 20 including the null terminator) bytes and
-the other for the rest of the data. The left 0 � 20 bytes will represent the
salted binary value we will use for a byte-by-byte data
-match against the new clear text presented for verification. The string
presented for verification will have to be salted as well. The rest
-of the bytes (21 � 32) represent the random salt which when decoded will show
the exact hex characters that make up the once randomly
-generated seed.
-
-We are now ready to verify some data. Let's start with the 4 hashes presented
earlier. We will run them through our code to extract the
-random salt and then using that verify the clear text string hashed by
slappasswd. First, let's do a verification test with an erroneous
-password; this should fail the matching test:
-
- {SSHA}72uhy5xc1AWOLwmNcXALHBSzp8xt4giL Test123
- Hash extracted (in hex): ef6ba1cb9c5cd4058e2f098d71700b1c14b3a7cc
- Salt extracted (in hex): 6de2088b
- Hash length is: 20 Salt length is: 4
- Hash presented in hex: 256bc48def0ce04b0af90dfd2808c42588bf9542
- Hashes DON'T match: Test123
-
-The match failure test was successful as expected. Now let's use known valid
data through the same exact code:
-
- {SSHA}72uhy5xc1AWOLwmNcXALHBSzp8xt4giL testing123
- Hash extracted (in hex): ef6ba1cb9c5cd4058e2f098d71700b1c14b3a7cc
- Salt extracted (in hex): 6de2088b
- Hash length is: 20 Salt length is: 4
- Hash presented in hex: ef6ba1cb9c5cd4058e2f098d71700b1c14b3a7cc
- Hashes match: testing123
-
-The process used for salted passwords should now be clear. We see that salting
hashed data does indeed add another layer of security to the
-clear text one-way hashing process. But we also see that salted hashes should
also be protected just as if the data was in clear text form.
-Now that we have seen salted hashes actually work you should also realize that
in code it is possible to extract salt values and use them
-for various purposes. Obviously the usage can be on either side of the colored
hat line, but the data is there.
-
-=head1 METHODS
-
-=over 4
-
-=item B<new( [%options] )>
-
-Returns a new Crypt::SaltedHash object.
-Possible keys for I<%options> are:
-
-=over
-
-=item *
-
-I<algorithm>: It's also possible to use common string representations of the
-algorithm (e.g. "sha256", "SHA-384"). If the argument is missing, SHA-1 will
-be used by default.
-
-=item *
-
-I<salt>: You can specify your on salt. You can either specify it as a sequence
-of characters or as a hex encoded string of the form "HEX{...}". If the
argument is missing,
-a random seed is provided for you (recommended).
-
-=item *
-
-I<salt_len>: By default, the module assumes a salt length of 4 bytes (or 8,
if it is encoded in hex).
-If you choose a different length, you have to tell the I<validate> function
how long your seed was.
-
-=back
-
-=cut
-
-sub new {
- my ( $class, %options ) = @_;
-
- $options{algorithm} ||= 'SHA-1';
- $options{salt_len} ||= 4;
- $options{salt} ||= &__generate_hex_salt( $options{salt_len} * 2 );
-
- $options{algorithm} = uc( $options{algorithm} );
- $options{algorithm} .= '-1'
- if $options{algorithm} =~ m!SHA$!; # SHA => SHA-1, HMAC-SHA =>
HMAC-SHA-1
-
- my $digest = Digest->new( $options{algorithm} );
- my $self = {
- salt => $options{salt},
- algorithm => $options{algorithm},
- digest => $digest,
- scheme => &__make_scheme( $options{algorithm} ),
- };
-
- return bless $self, $class;
-}
-
-=item B<add( $data, ... )>
-
-Logically joins the arguments into a single string, and uses it to
-update the current digest state. For more details see L<Digest>.
-
-=cut
-
-sub add {
- my $self = shift;
- $self->obj->add(@_);
- return $self;
-}
-
-=item B<clear()>
-
-Resets the digest.
-
-=cut
-
-sub clear {
- my $self = shift;
- $self->{digest} = Digest->new( $self->{algorithm} );
- return $self;
-}
-
-=item B<salt_bin()>
-
-Returns the salt in binary form.
-
-=cut
-
-sub salt_bin {
- my $self = shift;
-
- return $self->{salt} =~ m!^HEX\{(.*)\}$!i ? pack( "H*", $1 ) :
$self->{salt};
-}
-
-=item B<salt_hex()>
-
-Returns the salt in hexadecimal form ('HEX{...}')
-
-=cut
-
-sub salt_hex {
- my $self = shift;
-
- return $self->{salt} =~ m!^HEX\{(.*)\}$!i
- ? $self->{salt}
- : 'HEX{' . join( '', unpack( 'H*', $self->{salt} ) ) . '}';
-}
-
-=item B<generate()>
-
-Generates the seeded hash. Uses the I<clone>-method of L<Digest> before
actually performing
-the digest calculation, so adding more cleardata after a call of I<generate>
to an instance of
-I<Crypt::SaltedHash> has the same effect as adding the data before the call of
I<generate>.
-
-=cut
-
-sub generate {
- my $self = shift;
-
- my $clone = $self->obj->clone;
- my $salt = $self->salt_bin;
-
- $clone->add($salt);
-
- my $gen = &MIME::Base64::encode_base64( $clone->digest . $salt, '' );
- my $scheme = $self->{scheme};
-
- return "{$scheme}$gen";
-}
-
-=item B<validate( $hasheddata, $cleardata, [$salt_len] )>
-
-Validates a hasheddata previously generated against cleardata. I<$salt_len>
defaults to 4 if not set.
-Returns 1 if the validation is successful, 0 otherwise.
-
-=cut
-
-sub validate {
- my ( undef, $hasheddata, $cleardata, $salt_len ) = @_;
-
- # trim white-spaces
- $hasheddata =~ s!^\s+!!;
- $hasheddata =~ s!\s+$!!;
-
- my $scheme = &__get_pass_scheme($hasheddata);
- $scheme = uc( $scheme ) if $scheme;
- my $algorithm = &__make_algorithm($scheme);
- my $hash = &__get_pass_hash($hasheddata) || '';
- my $salt = &__extract_salt( $hash, $salt_len );
-
- my $obj = __PACKAGE__->new(
- algorithm => $algorithm,
- salt => $salt,
- salt_len => $salt_len
- );
- $obj->add($cleardata);
-
- my $gen_hasheddata = $obj->generate;
- my $gen_hash = &__get_pass_hash($gen_hasheddata);
-
- return _secure_compare( $gen_hash, $hash );
-}
-
-=item B<obj()>
-
-Returns a handle to L<Digest> object.
-
-=cut
-
-sub obj {
- return shift->{digest};
-}
-
-=back
-
-=head1 FUNCTIONS
-
-I<none yet.>
-
-=cut
-
-sub __make_scheme {
-
- my $scheme = shift;
-
- my @parts = split /-/, $scheme;
- pop @parts if $parts[-1] eq '1'; # SHA-1 => SHA
-
- $scheme = join '', @parts;
-
- return uc("S$scheme");
-}
-
-sub __make_algorithm {
- my ( $algorithm ) = @_;
-
- $algorithm ||= '';
- local $1;
-
- if ( $algorithm =~ m!^S(.*)$! ) {
- $algorithm = $1;
-
- # print STDERR "algorithm: $algorithm\n";
- if ( $algorithm =~ m!([a-zA-Z]+)([0-9]+)! ) {
-
- my $name = uc($1);
- my $digits = $2;
-
- # print STDERR "name: $name\n";
- # print STDERR "digits: $digits\n";
-
- $name = "HMAC-$2" if $name =~ m!^HMAC(.*)$!; # HMAC-SHA-1
- $digits = "-$digits" unless $name =~ m!MD$!; # MD2, MD4, MD5
-
- $algorithm = "$name$digits";
- }
-
- }
-
- return $algorithm;
-}
-
-sub __get_pass_scheme {
- local $1;
- return unless $_[0] =~ m/{([^}]*)/;
- return $1;
-}
-
-sub __get_pass_hash {
- local $1;
- return unless $_[0] =~ m/}(.*)/;
- return $1;
-}
-
-sub __generate_hex_salt {
-
- my $length = shift || 8;
-
- my $salt = substr( unpack( "h*", Crypt::SysRandom::random_bytes(
POSIX::ceil( $length / 2 ) ) ), 0, $length );
-
- return "HEX{$salt}";
-}
-
-sub __extract_salt {
-
- my ( $hash, $salt_len ) = @_;
-
- my $binhash = &MIME::Base64::decode_base64($hash);
- my $binsalt = substr( $binhash, length($binhash) - ( $salt_len || 4 ) );
-
- return $binsalt;
-}
-
-sub _secure_compare {
- my ($left, $right) = @_;
- my $res = length $left != length $right;
- $right = $left if $res;
- $res |= ord(substr $left, $_, 1) ^ ord(substr $right, $_, 1) for 0 ..
length($left) - 1;
- return $res == 0;
-}
-
-=head1 SEE ALSO
-
-L<Digest>, L<MIME::Base64>
-
-=head1 AUTHOR
-
-Sascha Kiefer, <[email protected]>
-
-The current maintainer is Robert Rothenberg <[email protected]>
-
-=head1 ACKNOWLEDGMENTS
-
-The author is particularly grateful to Andres Andreu for his article: Salted
-hashes demystified - A Primer (L<http://www.securitydocs.com/library/3439>)
-
-=head1 COPYRIGHT AND LICENSE
-
-Copyright (C) 2005-2006, 2010, 2013, 2026 Sascha Kiefer
-
-This library is free software; you can redistribute it and/or modify
-it under the same terms as Perl itself.
-
-=cut
-
-1;
+package Crypt::SaltedHash;
+
+use v5.6.0;
+use strict;
+
+use Crypt::SysRandom ();
+use Digest ();
+use MIME::Base64 ();
+use POSIX ();
+
+our $VERSION = '0.12';
+
+=encoding latin1
+
+=head1 NAME
+
+Crypt::SaltedHash - Perl interface to functions that assist in working
+with salted hashes.
+
+=head1 SYNOPSIS
+
+ use Crypt::SaltedHash;
+
+ my $csh = Crypt::SaltedHash->new(algorithm => 'SHA-1');
+ $csh->add('secret');
+
+ my $salted = $csh->generate;
+ my $valid = Crypt::SaltedHash->validate($salted, 'secret');
+
+
+=head1 STATUS
+
+This module is deprecated.
+
+This module has not had significant updates since 2006.
+There are newer modules that support more secure algorithms and hashing
options,
+and are extensible, such as L<Crypt::Passphrase>.
+
+You can use the L<Crypt::Passphrase::SaltedHash> validator to migrate to more
secure algorithms.
+
+=head1 DESCRIPTION
+
+The C<Crypt::SaltedHash> module provides an object oriented interface to
+create salted (or seeded) hashes of clear text data. The original
+formalization of this concept comes from RFC-3112 and is extended by the use
+of different digital algorithms.
+
+=head1 ABSTRACT
+
+=head2 Setting the data
+
+The process starts with 2 elements of data:
+
+=over
+
+=item *
+
+a clear text string (this could represent a password for instance).
+
+=item *
+
+the salt, a random seed of data. This is the value used to augment a hash in
order to
+ensure that 2 hashes of identical data yield different output.
+
+=back
+
+For the purposes of this abstract we will analyze the steps within code that
perform the necessary actions
+to achieve the endresult hashes. Cryptographers call this hash a digest. We
will not however go into an explanation
+of a one-way encryption scheme. Readers of this abstract are encouraged to get
information on that subject by
+their own.
+
+Theoretically, an implementation of a one-way function as an algorithm takes
input, and provides output, that are both
+in binary form; realistically though digests are typically encoded and stored
in a database or in a flat text or XML file.
+Take slappasswd5 for instance, it performs the exact functionality described
above. We will use it as a black box compiled
+piece of code for our analysis.
+
+In pseudocode we generate a salted hash as follows:
+
+ Get the source string and salt as separate binary objects
+ Concatenate the 2 binary values
+ Hash the concatenation into SaltedPasswordHash
+ Base64Encode(concat(SaltedPasswordHash, Salt))
+
+We take a clear text string and hash this into a binary object representing
the hashed value of the clear text string plus the random salt.
+Then we have the Salt value, which are typically 4 bytes of purely random
binary data represented as hexadecimal notation (Base16 as 8 bytes).
+
+Using SHA-1 as the hashing algorithm, SaltedPasswordHash is of length 20
(bytes) in raw binary form
+(40 bytes if we look at it in hex). Salt is then 4 bytes in raw binary form.
The SHA-1 algorithm generates
+a 160 bit hash string. Consider that 8 bits = 1 byte. So 160 bits = 20 bytes,
which is exactly what the
+algorithm gives us.
+
+The Base64 encoding of the binary result looks like:
+
+ {SSHA}B0O0XSYdsk7g9K229ZEr73Lid7HBD9DX
+
+Take note here that the final output is a 32-byte string of data. The Base64
encoding process uses bit shifting, masking, and padding as per RFC-3548.
+
+A couple of examples of salted hashes using on the same exact clear-text
string:
+
+ slappasswd -s testing123
+ {SSHA}72uhy5xc1AWOLwmNcXALHBSzp8xt4giL
+
+ slappasswd -s testing123
+ {SSHA}zmIAVaKMmTngrUi4UlS0dzYwVAbfBTl7
+
+ slappasswd -s testing123
+ {SSHA}Be3F12VVvBf9Sy6MSqpOgAdEj6JCZ+0f
+
+ slappasswd -s testing123
+ {SSHA}ncHs4XYmQKJqL+VuyNQzQjwRXfvu6noa
+
+4 runs of slappasswd against the same clear text string each yielded unique
endresult hashes.
+The random salt is generated silently and never made visible.
+
+=head2 Extracting the data
+
+One of the keys to note is that the salt is dealt with twice in the process.
It is used once for the actual application of randomness to the
+given clear text string, and then it is stored within the final output as
purely Base64 encoded data. In order to perform an authentication
+query for instance, we must break apart the concatenation that was created for
storage of the data. We accomplish this by splitting
+up the binary data we get after Base64 decoding the stored hash.
+
+In pseudocode we would perform the extraction and verification operations as
such:
+
+ Strip the hash identifier from the Digest
+ Base64Decode(Digest, 20)
+ Split Digest into 2 byte arrays, one for bytes 0 � 20(pwhash), one for
bytes 21 � 32 (salt)
+ Get the target string and salt as separate binary object
+ Concatenate the 2 binary values
+ SHA hash the concatenation into targetPasswordHash
+ Compare targetPasswordHash with pwhash
+ Return corresponding Boolean value
+
+Our job is to split the original digest up into 2 distinct byte arrays, one of
the left 20 (0 - 20 including the null terminator) bytes and
+the other for the rest of the data. The left 0 � 20 bytes will represent the
salted binary value we will use for a byte-by-byte data
+match against the new clear text presented for verification. The string
presented for verification will have to be salted as well. The rest
+of the bytes (21 � 32) represent the random salt which when decoded will show
the exact hex characters that make up the once randomly
+generated seed.
+
+We are now ready to verify some data. Let's start with the 4 hashes presented
earlier. We will run them through our code to extract the
+random salt and then using that verify the clear text string hashed by
slappasswd. First, let's do a verification test with an erroneous
+password; this should fail the matching test:
+
+ {SSHA}72uhy5xc1AWOLwmNcXALHBSzp8xt4giL Test123
+ Hash extracted (in hex): ef6ba1cb9c5cd4058e2f098d71700b1c14b3a7cc
+ Salt extracted (in hex): 6de2088b
+ Hash length is: 20 Salt length is: 4
+ Hash presented in hex: 256bc48def0ce04b0af90dfd2808c42588bf9542
+ Hashes DON'T match: Test123
+
+The match failure test was successful as expected. Now let's use known valid
data through the same exact code:
+
+ {SSHA}72uhy5xc1AWOLwmNcXALHBSzp8xt4giL testing123
+ Hash extracted (in hex): ef6ba1cb9c5cd4058e2f098d71700b1c14b3a7cc
+ Salt extracted (in hex): 6de2088b
+ Hash length is: 20 Salt length is: 4
+ Hash presented in hex: ef6ba1cb9c5cd4058e2f098d71700b1c14b3a7cc
+ Hashes match: testing123
+
+The process used for salted passwords should now be clear. We see that salting
hashed data does indeed add another layer of security to the
+clear text one-way hashing process. But we also see that salted hashes should
also be protected just as if the data was in clear text form.
+Now that we have seen salted hashes actually work you should also realize that
in code it is possible to extract salt values and use them
+for various purposes. Obviously the usage can be on either side of the colored
hat line, but the data is there.
+
+=head1 METHODS
+
+=over 4
+
+=item B<new( [%options] )>
+
+Returns a new Crypt::SaltedHash object.
+Possible keys for I<%options> are:
+
+=over
+
+=item *
+
+I<algorithm>: It's also possible to use common string representations of the
+algorithm (e.g. "sha256", "SHA-384"). If the argument is missing, SHA-1 will
+be used by default.
+
+=item *
+
+I<salt>: You can specify your on salt. You can either specify it as a sequence
+of characters or as a hex encoded string of the form "HEX{...}". If the
argument is missing,
+a random seed is provided for you (recommended).
+
+=item *
+
+I<salt_len>: By default, the module assumes a salt length of 4 bytes (or 8,
if it is encoded in hex).
+If you choose a different length, you have to tell the I<validate> function
how long your seed was.
+
+=back
+
+=cut
+
+sub new {
+ my ( $class, %options ) = @_;
+
+ $options{algorithm} ||= 'SHA-1';
+ $options{salt_len} ||= 4;
+ $options{salt} ||= &__generate_hex_salt( $options{salt_len} * 2 );
+
+ $options{algorithm} = uc( $options{algorithm} );
+ $options{algorithm} .= '-1'
+ if $options{algorithm} =~ m!SHA$!; # SHA => SHA-1, HMAC-SHA =>
HMAC-SHA-1
+
+ my $digest = Digest->new( $options{algorithm} );
+ my $self = {
+ salt => $options{salt},
+ algorithm => $options{algorithm},
+ digest => $digest,
+ scheme => &__make_scheme( $options{algorithm} ),
+ };
+
+ return bless $self, $class;
+}
+
+=item B<add( $data, ... )>
+
+Logically joins the arguments into a single string, and uses it to
+update the current digest state. For more details see L<Digest>.
+
+=cut
+
+sub add {
+ my $self = shift;
+ $self->obj->add(@_);
+ return $self;
+}
+
+=item B<clear()>
+
+Resets the digest.
+
+=cut
+
+sub clear {
+ my $self = shift;
+ $self->{digest} = Digest->new( $self->{algorithm} );
+ return $self;
+}
+
+=item B<salt_bin()>
+
+Returns the salt in binary form.
+
+=cut
+
+sub salt_bin {
+ my $self = shift;
+
+ return $self->{salt} =~ m!^HEX\{(.*)\}$!i ? pack( "H*", $1 ) :
$self->{salt};
+}
+
+=item B<salt_hex()>
+
+Returns the salt in hexadecimal form ('HEX{...}')
+
+=cut
+
+sub salt_hex {
+ my $self = shift;
+
+ return $self->{salt} =~ m!^HEX\{(.*)\}$!i
+ ? $self->{salt}
+ : 'HEX{' . join( '', unpack( 'H*', $self->{salt} ) ) . '}';
+}
+
+=item B<generate()>
+
+Generates the seeded hash. Uses the I<clone>-method of L<Digest> before
actually performing
+the digest calculation, so adding more cleardata after a call of I<generate>
to an instance of
+I<Crypt::SaltedHash> has the same effect as adding the data before the call of
I<generate>.
+
+=cut
+
+sub generate {
+ my $self = shift;
+
+ my $clone = $self->obj->clone;
+ my $salt = $self->salt_bin;
+
+ $clone->add($salt);
+
+ my $gen = &MIME::Base64::encode_base64( $clone->digest . $salt, '' );
+ my $scheme = $self->{scheme};
+
+ return "{$scheme}$gen";
+}
+
+=item B<validate( $hasheddata, $cleardata, [$salt_len] )>
+
+Validates a hasheddata previously generated against cleardata. I<$salt_len>
defaults to 4 if not set.
+Returns 1 if the validation is successful, 0 otherwise.
+
+=cut
+
+sub validate {
+ my ( undef, $hasheddata, $cleardata, $salt_len ) = @_;
+
+ # trim white-spaces
+ $hasheddata =~ s!^\s+!!;
+ $hasheddata =~ s!\s+$!!;
+
+ my $scheme = &__get_pass_scheme($hasheddata);
+ $scheme = uc( $scheme ) if $scheme;
+ my $algorithm = &__make_algorithm($scheme);
+ my $hash = &__get_pass_hash($hasheddata) || '';
+ my $salt = &__extract_salt( $hash, $salt_len );
+
+ my $obj = __PACKAGE__->new(
+ algorithm => $algorithm,
+ salt => $salt,
+ salt_len => $salt_len
+ );
+ $obj->add($cleardata);
+
+ my $gen_hasheddata = $obj->generate;
+ my $gen_hash = &__get_pass_hash($gen_hasheddata);
+
+ return _secure_compare( $gen_hash, $hash );
+}
+
+=item B<obj()>
+
+Returns a handle to L<Digest> object.
+
+=cut
+
+sub obj {
+ return shift->{digest};
+}
+
+=back
+
+=head1 FUNCTIONS
+
+I<none yet.>
+
+=cut
+
+sub __make_scheme {
+
+ my $scheme = shift;
+
+ my @parts = split /-/, $scheme;
+ pop @parts if $parts[-1] eq '1'; # SHA-1 => SHA
+
+ $scheme = join '', @parts;
+
+ return uc("S$scheme");
+}
+
+sub __make_algorithm {
+ my ( $algorithm ) = @_;
+
+ $algorithm ||= '';
+ local $1;
+
+ if ( $algorithm =~ m!^S(.*)$! ) {
+ $algorithm = $1;
+
+ # print STDERR "algorithm: $algorithm\n";
+ if ( $algorithm =~ m!([a-zA-Z]+)([0-9]+)! ) {
+
+ my $name = uc($1);
+ my $digits = $2;
+
+ # print STDERR "name: $name\n";
+ # print STDERR "digits: $digits\n";
+
+ $name = "HMAC-$2" if $name =~ m!^HMAC(.*)$!; # HMAC-SHA-1
+ $digits = "-$digits" unless $name =~ m!MD$!; # MD2, MD4, MD5
+
+ $algorithm = "$name$digits";
+ }
+
+ }
+
+ return $algorithm;
+}
+
+sub __get_pass_scheme {
+ local $1;
+ return unless $_[0] =~ m/{([^}]*)/;
+ return $1;
+}
+
+sub __get_pass_hash {
+ local $1;
+ return unless $_[0] =~ m/}(.*)/;
+ return $1;
+}
+
+sub __generate_hex_salt {
+
+ my $length = shift || 8;
+
+ my $salt = substr( unpack( "h*", Crypt::SysRandom::random_bytes(
POSIX::ceil( $length / 2 ) ) ), 0, $length );
+
+ return "HEX{$salt}";
+}
+
+sub __extract_salt {
+
+ my ( $hash, $salt_len ) = @_;
+
+ my $binhash = &MIME::Base64::decode_base64($hash);
+ my $binsalt = substr( $binhash, length($binhash) - ( $salt_len || 4 ) );
+
+ return $binsalt;
+}
+
+sub _secure_compare {
+ my ($left, $right) = @_;
+ my $res = length $left != length $right;
+ $right = $left if $res;
+ $res |= ord(substr $left, $_, 1) ^ ord(substr $right, $_, 1) for 0 ..
length($left) - 1;
+ return $res == 0;
+}
+
+=head1 SEE ALSO
+
+L<Digest>, L<MIME::Base64>
+
+=head1 AUTHOR
+
+Sascha Kiefer, <[email protected]>
+
+The current maintainer is Robert Rothenberg <[email protected]>
+
+=head1 ACKNOWLEDGMENTS
+
+The author is particularly grateful to Andres Andreu for his article: Salted
+hashes demystified - A Primer (L<http://www.securitydocs.com/library/3439>)
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2005-2006, 2010, 2013, 2026 Sascha Kiefer
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+1;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Crypt-SaltedHash-0.11/t/00-report-prereqs.dd
new/Crypt-SaltedHash-0.12/t/00-report-prereqs.dd
--- old/Crypt-SaltedHash-0.11/t/00-report-prereqs.dd 2026-05-20
15:05:08.000000000 +0200
+++ new/Crypt-SaltedHash-0.12/t/00-report-prereqs.dd 2026-05-23
08:29:35.000000000 +0200
@@ -11,6 +11,7 @@
'Test::CleanNamespaces' => '0.15',
'Test::DistManifest' => '0',
'Test::EOF' => '0',
+ 'Test::EOL' => '0',
'Test::Fixme' => '0',
'Test::MinimumVersion' => '0',
'Test::More' => '0.94',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Crypt-SaltedHash-0.11/t/04Crypt-SaltedHash.t
new/Crypt-SaltedHash-0.12/t/04Crypt-SaltedHash.t
--- old/Crypt-SaltedHash-0.11/t/04Crypt-SaltedHash.t 2026-05-20
15:05:08.000000000 +0200
+++ new/Crypt-SaltedHash-0.12/t/04Crypt-SaltedHash.t 2026-05-23
08:29:35.000000000 +0200
@@ -1,59 +1,59 @@
-# Before `make install' is performed this script should be runnable with
-# `make test'. After `make install' it should work as `perl Crypt-SaltedHash.t'
-
-#########################
-
-# change 'tests => 1' to 'tests => last_test_to_print';
-
-use strict;
-use Test::More tests => 6;
-BEGIN { use_ok('Crypt::SaltedHash') }
-
-#########################
-
-my ( $csh, $salted, $valid );
-
-my %known_salts = (
- 'MD5' => '{SMD5}vfwtsKpZn1kZ5WXDKCFqUTEyMzQ=',
- # 'SHA-1' => '{SSHA}kRnWqCDFvZFoV7A6cTGBdq1Xv7cxMjM0',
-);
-
-foreach my $alg (keys %known_salts) {
-
- $csh = Crypt::SaltedHash->new( algorithm => $alg );
- $csh->add('secret');
-
- $salted = $csh->generate;
- $valid = Crypt::SaltedHash->validate( $salted, 'secret' );
-
- ok( $valid, "$alg: default test" );
-
- $csh = Crypt::SaltedHash->new( algorithm => $alg, salt_len => 32 );
- $csh->add('secret');
-
- $salted = $csh->generate;
- $valid = Crypt::SaltedHash->validate( $salted, 'secret', 32 );
-
- ok( $valid, "$alg: salt_len test" );
-
- $csh = Crypt::SaltedHash->new( algorithm => $alg );
-
- $csh->add('secret');
- $salted = $csh->generate;
- $csh->add('secret');
- $salted = $csh->generate;
-
- $valid = Crypt::SaltedHash->validate( $salted, 'secretsecret' );
-
- ok( $valid, "$alg: generate test" );
-
- $csh = Crypt::SaltedHash->new( algorithm => $alg, salt => '1234' );
- $csh->add('secret');
-
- ok( $csh->generate eq $known_salts{$alg}, "$alg: own bin-salt test" );
-
- $csh = Crypt::SaltedHash->new( algorithm => $alg, salt => 'HEX{31323334}'
);
- $csh->add('secret');
-
- ok( $csh->generate eq $known_salts{$alg}, "$alg: own hex-salt test" );
-}
+# Before `make install' is performed this script should be runnable with
+# `make test'. After `make install' it should work as `perl Crypt-SaltedHash.t'
+
+#########################
+
+# change 'tests => 1' to 'tests => last_test_to_print';
+
+use strict;
+use Test::More tests => 6;
+BEGIN { use_ok('Crypt::SaltedHash') }
+
+#########################
+
+my ( $csh, $salted, $valid );
+
+my %known_salts = (
+ 'MD5' => '{SMD5}vfwtsKpZn1kZ5WXDKCFqUTEyMzQ=',
+ # 'SHA-1' => '{SSHA}kRnWqCDFvZFoV7A6cTGBdq1Xv7cxMjM0',
+);
+
+foreach my $alg (keys %known_salts) {
+
+ $csh = Crypt::SaltedHash->new( algorithm => $alg );
+ $csh->add('secret');
+
+ $salted = $csh->generate;
+ $valid = Crypt::SaltedHash->validate( $salted, 'secret' );
+
+ ok( $valid, "$alg: default test" );
+
+ $csh = Crypt::SaltedHash->new( algorithm => $alg, salt_len => 32 );
+ $csh->add('secret');
+
+ $salted = $csh->generate;
+ $valid = Crypt::SaltedHash->validate( $salted, 'secret', 32 );
+
+ ok( $valid, "$alg: salt_len test" );
+
+ $csh = Crypt::SaltedHash->new( algorithm => $alg );
+
+ $csh->add('secret');
+ $salted = $csh->generate;
+ $csh->add('secret');
+ $salted = $csh->generate;
+
+ $valid = Crypt::SaltedHash->validate( $salted, 'secretsecret' );
+
+ ok( $valid, "$alg: generate test" );
+
+ $csh = Crypt::SaltedHash->new( algorithm => $alg, salt => '1234' );
+ $csh->add('secret');
+
+ ok( $csh->generate eq $known_salts{$alg}, "$alg: own bin-salt test" );
+
+ $csh = Crypt::SaltedHash->new( algorithm => $alg, salt => 'HEX{31323334}'
);
+ $csh->add('secret');
+
+ ok( $csh->generate eq $known_salts{$alg}, "$alg: own hex-salt test" );
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Crypt-SaltedHash-0.11/xt/author/eol.t
new/Crypt-SaltedHash-0.12/xt/author/eol.t
--- old/Crypt-SaltedHash-0.11/xt/author/eol.t 1970-01-01 01:00:00.000000000
+0100
+++ new/Crypt-SaltedHash-0.12/xt/author/eol.t 2026-05-23 08:29:35.000000000
+0200
@@ -0,0 +1,18 @@
+use strict;
+use warnings;
+
+# this test was generated with Dist::Zilla::Plugin::Test::EOL 0.19
+
+use Test::More 0.88;
+use Test::EOL;
+
+my @files = (
+ 'lib/Crypt/SaltedHash.pm',
+ 't/00-report-prereqs.dd',
+ 't/00-report-prereqs.t',
+ 't/04Crypt-SaltedHash.t',
+ 't/bug-localize-regex-vars.t'
+);
+
+eol_unix_ok($_, { trailing_whitespace => 1 }) foreach @files;
+done_testing;
++++++ _scmsync.obsinfo ++++++
--- /var/tmp/diff_new_pack.Fk3qwo/_old 2026-06-09 14:28:02.747778025 +0200
+++ /var/tmp/diff_new_pack.Fk3qwo/_new 2026-06-09 14:28:02.751778190 +0200
@@ -1,6 +1,6 @@
-mtime: 1779365402
-commit: 5c91a4f3554aef67046839374c76db5f53335b2fda21173c11cc7fc195b3517c
+mtime: 1779522253
+commit: 9ac219415234ba33e044aa62cda11ae7c167f578da67a0748831a3446889ae95
url: https://src.opensuse.org/perl/perl-Crypt-SaltedHash
-revision: 5c91a4f3554aef67046839374c76db5f53335b2fda21173c11cc7fc195b3517c
+revision: 9ac219415234ba33e044aa62cda11ae7c167f578da67a0748831a3446889ae95
projectscmsync: https://src.opensuse.org/perl/_ObsPrj
++++++ build.specials.obscpio ++++++
++++++ build.specials.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/.gitignore new/.gitignore
--- old/.gitignore 1970-01-01 01:00:00.000000000 +0100
+++ new/.gitignore 2026-05-23 09:44:13.000000000 +0200
@@ -0,0 +1 @@
+.osc