New Upstream Release - libcrypt-argon2-perl
Ready changes
Summary
Merged new upstream version: 0.017 (was: 0.016).
Resulting package
Built on 2023-04-10T10:01 (took 8m58s)
The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:
apt install -t fresh-releases libcrypt-argon2-perl-dbgsymapt install -t fresh-releases libcrypt-argon2-perl
Lintian Result
Diff
diff --git a/Changes b/Changes
index 53b1288..bb2429d 100644
--- a/Changes
+++ b/Changes
@@ -1,5 +1,11 @@
Revision history for Crypt-Argon2
+0.017 2023-04-07 16:21:47+02:00 Europe/Amsterdam
+ - Add argon2_pass and argon2_raw
+ - Add argon2_types
+ - Remove argon2_crypt
+ - Add $type_regex
+
0.016 2023-03-23 03:43:57+01:00 Europe/Amsterdam
- Reenable linking to pthread
diff --git a/MANIFEST b/MANIFEST
index 5ce703d..7c2d465 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -27,5 +27,6 @@ src/encoding.h
src/thread.c
src/thread.h
t/test.t
+typemap
xt/author/pod-coverage.t
xt/author/pod-syntax.t
diff --git a/META.json b/META.json
index 93de60c..7c59e2c 100644
--- a/META.json
+++ b/META.json
@@ -53,7 +53,7 @@
"provides" : {
"Crypt::Argon2" : {
"file" : "lib/Crypt/Argon2.pm",
- "version" : "0.016"
+ "version" : "0.017"
}
},
"release_status" : "stable",
@@ -67,7 +67,7 @@
"web" : "https://github.com/Leont/crypt-argon2"
}
},
- "version" : "0.016",
+ "version" : "0.017",
"x_generated_by_perl" : "v5.36.0",
"x_serialization_backend" : "Cpanel::JSON::XS version 4.29",
"x_spdx_expression" : "CC0-1.0"
diff --git a/META.yml b/META.yml
index b1bb13b..29bd6b9 100644
--- a/META.yml
+++ b/META.yml
@@ -19,7 +19,7 @@ name: Crypt-Argon2
provides:
Crypt::Argon2:
file: lib/Crypt/Argon2.pm
- version: '0.016'
+ version: '0.017'
requires:
Exporter: '5.57'
Time::HiRes: '0'
@@ -30,7 +30,7 @@ requires:
resources:
bugtracker: https://github.com/Leont/crypt-argon2/issues
repository: git://github.com/Leont/crypt-argon2.git
-version: '0.016'
+version: '0.017'
x_generated_by_perl: v5.36.0
x_serialization_backend: 'YAML::Tiny version 1.73'
x_spdx_expression: CC0-1.0
diff --git a/README b/README
index 7cdeaf0..2bd87a8 100644
--- a/README
+++ b/README
@@ -1,5 +1,5 @@
This archive contains the distribution Crypt-Argon2,
-version 0.016:
+version 0.017:
Perl interface to the Argon2 key derivation functions
diff --git a/debian/changelog b/debian/changelog
index 36cd107..e658f08 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,5 +1,6 @@
-libcrypt-argon2-perl (0.016-1) UNRELEASED; urgency=medium
+libcrypt-argon2-perl (0.017-1) UNRELEASED; urgency=medium
+ [ gregor herrmann ]
TODO: double-check march-native patch
* Team upload.
@@ -11,7 +12,10 @@ libcrypt-argon2-perl (0.016-1) UNRELEASED; urgency=medium
* Add patch to remove -march=native compiler flag from Build.PL.
Thanks to Leon Timmermans (upstream author) for the heads-up.
- -- gregor herrmann <gregoa@debian.org> Fri, 24 Mar 2023 19:02:12 +0100
+ [ Debian Janitor ]
+ * New upstream release.
+
+ -- gregor herrmann <gregoa@debian.org> Mon, 10 Apr 2023 09:53:09 -0000
libcrypt-argon2-perl (0.013-1) unstable; urgency=medium
diff --git a/debian/patches/no-march-native.patch b/debian/patches/no-march-native.patch
index e81212c..5c91658 100644
--- a/debian/patches/no-march-native.patch
+++ b/debian/patches/no-march-native.patch
@@ -4,9 +4,11 @@ Forwarded: not-needed
Author: gregor herrmann <gregoa@debian.org>
Last-Update: 2023-03-24
---- a/Build.PL
-+++ b/Build.PL
-@@ -9,11 +9,10 @@
+Index: libcrypt-argon2-perl.git/Build.PL
+===================================================================
+--- libcrypt-argon2-perl.git.orig/Build.PL
++++ libcrypt-argon2-perl.git/Build.PL
+@@ -9,11 +9,10 @@ my %opt_args = (
source => 'opt/opt.c',
object_file => File::Spec->devnull,
include_dirs => [ 'include', 'src' ],
diff --git a/lib/Crypt/Argon2.pm b/lib/Crypt/Argon2.pm
index a411a42..3c4db37 100644
--- a/lib/Crypt/Argon2.pm
+++ b/lib/Crypt/Argon2.pm
@@ -1,17 +1,20 @@
package Crypt::Argon2;
-$Crypt::Argon2::VERSION = '0.016';
+$Crypt::Argon2::VERSION = '0.017';
use strict;
use warnings;
use Exporter 5.57 'import';
our @EXPORT_OK = qw/
+ argon2_raw argon2_pass argon2_verify
argon2id_raw argon2id_pass argon2id_verify
argon2i_raw argon2i_pass argon2i_verify
- argon2d_raw argon2_needs_rehash
- argon2_verify argon2_crypt/;
+ argon2d_raw argon2_pass argon2_verify
+ argon2_needs_rehash argon2_types/;
use XSLoader;
XSLoader::load(__PACKAGE__, __PACKAGE__->VERSION || 0);
+our $type_regex = qr/argon2(?:i|d|id)/;
+
my %multiplier = (
k => 1,
M => 1024,
@@ -30,18 +33,8 @@ sub argon2_needs_rehash {
return 0;
}
-sub argon2_verify {
- my ($name) = $_[0] =~ $regex or return !!0;
- my $verify = do { no strict; \&{"$name\_verify"} };
- goto &{$verify};
-}
-
-sub argon2_crypt {
- my ($password, $settings) = @_;
- my ($name, $version, $m_got, $t_got, $parallel_got, $salt, $hash) = $settings =~ $regex or return undef;
- my $length = length $hash ? int(3 / 4 * length $hash) : 16;
- my $pass = do { no strict; \&{"$name\_pass"} };
- return eval { $pass->($password, $salt, $t_got, $m_got, $parallel_got, $length) };
+sub argon2_types {
+ return qw/argon2id argon2i argon2d/;
}
1;
@@ -60,11 +53,11 @@ Crypt::Argon2 - Perl interface to the Argon2 key derivation functions
=head1 VERSION
-version 0.016
+version 0.017
=head1 SYNOPSIS
- use Crypt::Argon2 qw/argon2id_pass argon2id_verify/;
+ use Crypt::Argon2 qw/argon2id_pass argon2_verify/;
sub add_pass {
my ($user, $password) = @_;
@@ -76,7 +69,7 @@ version 0.016
sub check_password {
my ($user, $password) = @_;
my $encoded = fetch_encoded($user);
- return argon2id_verify($encoded, $password);
+ return argon2_verify($encoded, $password);
}
=head1 DESCRIPTION
@@ -87,12 +80,16 @@ To find appropriate parameters, the bundled program C<argon2-calibrate> can be u
=head1 FUNCTIONS
-=head2 argon2id_pass($password, $salt, $t_cost, $m_factor, $parallelism, $tag_size)
+=head2 argon2_pass($type, $password, $salt, $t_cost, $m_factor, $parallelism, $tag_size)
This function processes the C<$password> with the given C<$salt> and parameters. It encodes the resulting tag and the parameters as a password string (e.g. C<$argon2id$v=19$m=65536,t=2,p=1$c29tZXNhbHQ$wWKIMhR9lyDFvRz9YTZweHKfbftvj+qf+YFY4NeBbtA>).
=over 4
+=item * C<$type>
+
+The argon2 type that is used. This must be one of C<'argon2id'>, C<'argon2i'> or C<'argon2d'>.
+
=item * C<$password>
This is the password that is to be turned into a cryptographic key.
@@ -119,49 +116,39 @@ This is the size of the raw result in bytes. Typical values are 16 or 32.
=back
-=head2 argon2id_verify($encoded, $password)
-
-This verifies that the C<$password> matches C<$encoded>. All parameters and the tag value are extracted from C<$encoded>, so no further arguments are necessary.
-
-=head2 argon2id_raw($password, $salt, $t_cost, $m_factor, $parallelism, $tag_size)
-
-This function processes the C<$password> with the given C<$salt> and parameters much like C<argon2i_pass>, but returns the binary tag instead of a formatted string.
-
-=head2 argon2i_pass($password, $salt, $t_cost, $m_factor, $parallelism, $tag_size)
-
-This function processes the C<$password> with the given C<$salt> and parameters much like argon2id_pass, but uses the argon2i variant instead.
-
-=head2 argon2i_verify($encoded, $password)
+=head2 argon2_verify($encoded, $password)
This verifies that the C<$password> matches C<$encoded>. All parameters and the tag value are extracted from C<$encoded>, so no further arguments are necessary.
-=head2 argon2i_raw($password, $salt, $t_cost, $m_factor, $parallelism, $tag_size)
+=head2 argon2_raw($type, $password, $salt, $t_cost, $m_factor, $parallelism, $tag_size)
-This function processes the C<$password> with the given C<$salt> and parameters much like C<argon2i_pass>, but returns the binary tag instead of a formatted string.
+This function processes the C<$password> with the given C<$salt> and parameters much like C<argon2_pass>, but returns the binary tag instead of a formatted string.
-=head2 argon2d_pass($password, $salt, $t_cost, $m_factor, $parallelism, $tag_size)
+=head2 argon2id_pass($password, $salt, $t_cost, $m_factor, $parallelism, $tag_size)
+=func argon2i_pass($password, $salt, $t_cost, $m_factor, $parallelism, $tag_size)
+=func argon2d_pass($password, $salt, $t_cost, $m_factor, $parallelism, $tag_size)
-This function processes the C<$password> with the given C<$salt> and parameters much like argon2id_pass, but uses the argon2d variant instead.
+This function processes the C<$password> much like C<argon2_pass> does, but the C<$type> argument is set like the function name.
-=head2 argon2d_verify($encoded, $password
+=head2 argon2id_verify($encoded, $password)
+=func argon2i_verify($encoded, $password)
+=func argon2d_verify($encoded, $password)
-This verifies that the C<$password> matches C<$encoded>. All parameters and the tag value are extracted from C<$encoded>, so no further arguments are necessary.
+This verifies that the C<$password> matches C<$encoded> and the given type. All parameters and the tag value are extracted from C<$encoded>, so no further arguments are necessary.
-=head2 argon2d_raw($password, $salt, $t_cost, $m_factor, $parallelism, $tag_size)
+=head2 argon2id_raw($password, $salt, $t_cost, $m_factor, $parallelism, $tag_size)
+=func argon2i_raw($password, $salt, $t_cost, $m_factor, $parallelism, $tag_size)
+=func argon2d_raw($password, $salt, $t_cost, $m_factor, $parallelism, $tag_size)
-This function processes the C<$password> with the given C<$salt> and parameters much like C<argon2i_pass>, but returns a binary tag for argon2d instead of a formatted string for argon2i.
+This function processes the C<$password> much like C<argon2_raw> does, but the C<$type> argument is set like the function name.
=head2 argon2_needs_rehash($encoded, $type, $t_cost, $m_cost, $parallelism, $salt_length, $output_length)
This function checks if a password-encoded string needs a rehash. It will return true if the C<$type> (valid values are C<argon2i>, C<argon2id> or C<argon2d>), C<$t_cost>, C<$m_cost>, C<$parallelism>, C<$salt_length> or C<$output_length> arguments mismatches or any of the parameters of the password-encoded hash.
-=head2 argon2_verify($encoded, $password)
-
-This will verify the hash using C<argon2id_verify>, C<argon2i_verify> or C<argon2d_verify>, depending on the identifier in C<$encoded>.
-
-=head2 argon2_crypt($password, $settings)
+=head2 argon2_types
-This function implements a C<crypt()> like interface to argon2. C<$password> is a password, but C<$settings> is a settings string (a password hash that may lack anything beyond the final C<$>).
+This returns all supported argon2 subtypes. Currently that's C<'argon2id'>, C<'argon2i'> and C<'argon2d'>.
=head2 ACKNOWLEDGEMENTS
diff --git a/lib/Crypt/Argon2.xs b/lib/Crypt/Argon2.xs
index d76dfd8..c4794f4 100644
--- a/lib/Crypt/Argon2.xs
+++ b/lib/Crypt/Argon2.xs
@@ -31,87 +31,122 @@ static size_t S_parse_size(pTHX_ SV* value, int type) {
}
#define parse_size(value, type) S_parse_size(aTHX_ value, type)
-MODULE = Crypt::Argon2 PACKAGE = Crypt::Argon2
+static enum Argon2_type S_find_argon2_type(pTHX_ const char* name, size_t name_len) {
+ if (name_len == 8 && strnEQ(name, "argon2id", 8))
+ return Argon2_id;
+ else if (name_len == 7 && strnEQ(name, "argon2i", 7))
+ return Argon2_i;
+ else if (name_len == 7 && strnEQ(name, "argon2d", 7))
+ return Argon2_d;
+ Perl_croak(aTHX_ "No such argon2 type %s", name);
+}
+#define find_argon2_type(name, len) S_find_argon2_type(aTHX_ name, len)
-SV* argon2d_pass(SV* password, SV* salt, int t_cost, SV* m_factor, int parallelism, size_t output_length)
- ALIAS:
- argon2d_pass = Argon2_d
- argon2i_pass = Argon2_i
- argon2id_pass = Argon2_id
- PREINIT:
- char *password_raw, *salt_raw;
+static enum Argon2_type S_get_argon2_type(pTHX_ SV* name_sv) {
+ STRLEN name_len;
+ const char* name = SvPV(name_sv, name_len);
+ return find_argon2_type(name, name_len);
+}
+#define get_argon2_type(name) S_get_argon2_type(aTHX_ name)
+
+static SV* S_argon2_pass(pTHX_ enum Argon2_type type, SV* password, SV* salt, int t_cost, SV* m_factor, int parallelism, size_t output_length) {
+ int m_cost = parse_size(m_factor, type);
STRLEN password_len, salt_len;
- int rc, encoded_length, m_cost;
- CODE:
- m_cost = parse_size(m_factor, ix);
- password_raw = SvPVbyte(password, password_len);
- salt_raw = SvPVbyte(salt, salt_len);
- encoded_length = argon2_encodedlen(t_cost, m_cost, parallelism, salt_len, output_length, ix);
- RETVAL = newSV(encoded_length - 1);
- SvPOK_only(RETVAL);
- rc = argon2_hash(t_cost, m_cost, parallelism,
+ const char* password_raw = SvPVbyte(password, password_len);
+ const char* salt_raw = SvPVbyte(salt, salt_len);
+ size_t encoded_length = argon2_encodedlen(t_cost, m_cost, parallelism, salt_len, output_length, type);
+ SV* result = newSV(encoded_length - 1);
+ SvPOK_only(result);
+ int rc = argon2_hash(t_cost, m_cost, parallelism,
password_raw, password_len,
salt_raw, salt_len,
NULL, output_length,
- SvPVX(RETVAL), encoded_length,
- ix, ARGON2_VERSION_NUMBER
+ SvPVX(result), encoded_length,
+ type, ARGON2_VERSION_NUMBER
);
if (rc != ARGON2_OK) {
- SvREFCNT_dec(RETVAL);
- Perl_croak(aTHX_ "Couldn't compute %s tag: %s", argon2_type2string(ix, FALSE), argon2_error_message(rc));
+ SvREFCNT_dec(result);
+ Perl_croak(aTHX_ "Couldn't compute %s tag: %s", argon2_type2string(type, FALSE), argon2_error_message(rc));
}
- SvCUR(RETVAL) = encoded_length - 1;
- OUTPUT:
- RETVAL
+ SvCUR(result) = encoded_length - 1;
+ return result;
+}
+#define argon2_pass(type, password, salt, t_cost, m_factor, parallelism, output_length) S_argon2_pass(aTHX_ type, password, salt, t_cost, m_factor, parallelism, output_length)
-SV* argon2d_raw(SV* password, SV* salt, int t_cost, SV* m_factor, int parallelism, size_t output_length)
- ALIAS:
- argon2d_raw = Argon2_d
- argon2i_raw = Argon2_i
- argon2id_raw = Argon2_id
- PREINIT:
- char *password_raw, *salt_raw;
+static SV* S_argon2_raw(pTHX_ enum Argon2_type type, SV* password, SV* salt, int t_cost, SV* m_factor, int parallelism, size_t output_length) {
+ int m_cost = parse_size(m_factor, type);
STRLEN password_len, salt_len;
- int rc, m_cost;
- CODE:
- m_cost = parse_size(m_factor, ix);
- password_raw = SvPVbyte(password, password_len);
- salt_raw = SvPVbyte(salt, salt_len);
- RETVAL = newSV(output_length);
- SvPOK_only(RETVAL);
- rc = argon2_hash(t_cost, m_cost, parallelism,
+ const char* password_raw = SvPVbyte(password, password_len);
+ const char* salt_raw = SvPVbyte(salt, salt_len);
+ SV* result = newSV(output_length);
+ SvPOK_only(result);
+ int rc = argon2_hash(t_cost, m_cost, parallelism,
password_raw, password_len,
salt_raw, salt_len,
- SvPVX(RETVAL), output_length,
+ SvPVX(result), output_length,
NULL, 0,
- ix, ARGON2_VERSION_NUMBER
+ type, ARGON2_VERSION_NUMBER
);
if (rc != ARGON2_OK) {
- SvREFCNT_dec(RETVAL);
- Perl_croak(aTHX_ "Couldn't compute %s tag: %s", argon2_type2string(ix, FALSE), argon2_error_message(rc));
+ SvREFCNT_dec(result);
+ Perl_croak(aTHX_ "Couldn't compute %s tag: %s", argon2_type2string(type, FALSE), argon2_error_message(rc));
}
- SvCUR(RETVAL) = output_length;
- OUTPUT:
+ SvCUR(result) = output_length;
+ return result;
+}
+#define argon2_raw(type, password, salt, t_cost, m_factor, parallelism, output_length) S_argon2_raw(aTHX_ type, password, salt, t_cost, m_factor, parallelism, output_length)
+
+MODULE = Crypt::Argon2 PACKAGE = Crypt::Argon2
+
+SV* argon2_pass(enum Argon2_type type, SV* password, SV* salt, int t_cost, SV* m_factor, int parallelism, size_t output_length)
+
+SV* argon2id_pass(SV* password, SV* salt, int t_cost, SV* m_factor, int parallelism, size_t output_length)
+ALIAS:
+ argon2d_pass = Argon2_d
+ argon2i_pass = Argon2_i
+ argon2id_pass = Argon2_id
+CODE:
+ RETVAL = argon2_pass(ix, password, salt, t_cost, m_factor, parallelism, output_length);
+OUTPUT:
RETVAL
-SV* argon2d_verify(SV* encoded, SV* password)
+
+SV* argon2_raw(enum Argon2_type type, SV* password, SV* salt, int t_cost, SV* m_factor, int parallelism, size_t output_length)
+
+SV* argon2id_raw(SV* password, SV* salt, int t_cost, SV* m_factor, int parallelism, size_t output_length)
+ALIAS:
+ argon2d_raw = Argon2_d
+ argon2i_raw = Argon2_i
+ argon2id_raw = Argon2_id
+CODE:
+ RETVAL = argon2_raw(ix, password, salt, t_cost, m_factor, parallelism, output_length);
+OUTPUT:
+ RETVAL
+
+bool argon2d_verify(SV* encoded, SV* password)
ALIAS:
argon2d_verify = Argon2_d
argon2i_verify = Argon2_i
argon2id_verify = Argon2_id
+ argon2_verify = 4
PREINIT:
- char* password_raw;
- STRLEN password_len;
+ const char* password_raw, *encoded_raw;
+ STRLEN password_len, encoded_len;
int status;
CODE:
+ encoded_raw = SvPVbyte(encoded, encoded_len);
+ if (ix == 4) {
+ const char* second_dollar = memchr(encoded_raw + 1, '$', encoded_len - 1);
+ ix = find_argon2_type(encoded_raw + 1, second_dollar - encoded_raw - 1);
+ }
password_raw = SvPVbyte(password, password_len);
status = argon2_verify(SvPVbyte_nolen(encoded), password_raw, password_len, ix);
switch(status) {
case ARGON2_OK:
- RETVAL = &PL_sv_yes;
+ RETVAL = TRUE;
break;
case ARGON2_VERIFY_MISMATCH:
- RETVAL = &PL_sv_no;
+ RETVAL = FALSE;
break;
default:
Perl_croak(aTHX_ "Could not verify %s tag: %s", argon2_type2string(ix, FALSE), argon2_error_message(status));
diff --git a/script/argon2-calibrate b/script/argon2-calibrate
index 746d73f..8e4d283 100644
--- a/script/argon2-calibrate
+++ b/script/argon2-calibrate
@@ -76,7 +76,7 @@ argon2-calibrate - a script to find the appropriate argon2 parameters
=head1 VERSION
-version 0.016
+version 0.017
=head1 DESCRIPTION
diff --git a/t/test.t b/t/test.t
index def136a..7da480d 100644
--- a/t/test.t
+++ b/t/test.t
@@ -4,7 +4,7 @@ use strict;
use warnings;
use Test::More 0.90;
-use Crypt::Argon2 qw/argon2i_pass argon2i_raw argon2i_verify argon2id_pass argon2_needs_rehash/;
+use Crypt::Argon2 qw/argon2i_pass argon2i_raw argon2_verify argon2_pass argon2_needs_rehash/;
sub hashtest {
my ($t_cost, $m_cost, $parallelism, $password, $salt, $hexref, $mcfref) = @_;
@@ -12,7 +12,7 @@ sub hashtest {
subtest "argon2i($t_cost, $m_cost, $parallelism, $password, $salt)", sub {
my $encoded = argon2i_pass($password, $salt, $t_cost, $m_cost, $parallelism, 32);
is($encoded, $mcfref, "$t_cost:$m_cost:$parallelism($password, $salt) encodes as expected");
- ok(argon2i_verify($encoded, $password), "$t_cost:$m_cost:$parallelism($password, $salt) matches as expected");
+ ok(argon2_verify($encoded, $password), "$t_cost:$m_cost:$parallelism($password, $salt) matches as expected");
my $hex = unpack "H*", argon2i_raw($password, $salt, $t_cost, $m_cost, $parallelism, 32);
is($hex, $hexref, "$t_cost:$m_cost:$parallelism($password, $salt) verifies as expected");
};
@@ -49,7 +49,7 @@ if ($ENV{EXTENDED_TESTING} || $ENV{AUTHOR_TESTING}) {
}
subtest 'needs_rehash', sub {
- my $encoded = argon2id_pass('password', 'saltsalt', 2, '64M', 1, 32);
+ my $encoded = argon2_pass('argon2id', 'password', 'saltsalt', 2, '64M', 1, 32);
ok(!argon2_needs_rehash($encoded, 'argon2id', 2, '64M', 1, 32, 8), 'No rehash with same parameters');
ok(argon2_needs_rehash($encoded, 'argon2i', 2, '64M', 1, 32, 8), 'Rehash with different argon2 variant');
ok(argon2_needs_rehash($encoded, 'argon2id', 3, '64M', 1, 32, 8), 'Rehash with different time cost');
diff --git a/typemap b/typemap
new file mode 100644
index 0000000..16b0ee4
--- /dev/null
+++ b/typemap
@@ -0,0 +1,5 @@
+enum Argon2_type T_ARGON2_TYPE
+
+INPUT
+T_ARGON2_TYPE
+ $var = get_argon2_type($arg);
Debdiff
[The following lists of changes regard files as different if they have different names, permissions or owners.]
Files in second set of .debs but not in first
-rw-r--r-- root/root /usr/lib/debug/.build-id/6f/09fae11f9974c3310f7c16a4f88b25fcf10f52.debug
Files in first set of .debs but not in second
-rw-r--r-- root/root /usr/lib/debug/.build-id/b4/6ae56b90804ad7cbffc6c6365c148225ed81df.debug
No differences were encountered between the control files of package libcrypt-argon2-perl
Control files of package libcrypt-argon2-perl-dbgsym: lines which differ (wdiff format)
Build-Ids: b46ae56b90804ad7cbffc6c6365c148225ed81df 6f09fae11f9974c3310f7c16a4f88b25fcf10f52