ETOOBUSY ๐ minimal blogging for the impatient
Cryptopals 36 - Implement Secure Remote Password (SRP)
TL;DR
For how much I skipped implementing stuff in the previous post, I think I more than covered in this installment.
#!/usr/bin/env perl
use v5.24;
use warnings;
use experimental 'signatures';
no warnings 'experimental::signatures';
use File::Basename 'dirname';
use lib dirname(__FILE__);
package SRPSession;
use v5.24;
use warnings;
use experimental 'signatures';
no warnings 'experimental::signatures';
use DiffieHellman;
use Math::Prime::Util 'powmod';
use Math::BigInt;
use Digest;
sub new ($package, %args) {
die 'no email' unless defined $args{I};
die 'no password' unless defined $args{P};
$args{p} //= DiffieHellman->NIST_p;
$args{g} //= DiffieHellman->NIST_g;
$args{k} //= 3;
my $self = bless { %args }, $package;
$self->{$_} = Math::BigInt->new($self->{$_}) for qw< p g >;
$self->{n_hex} = length $self->{p}->to_hex;
$self->{secret_key} = $self->generate_secret_key;
$self->init;
return $self;
}
sub modexp ($self, $b, $e, $p) { Math::BigInt->new(powmod($b, $e, $p)) }
sub sha256_hex ($self, @data) {
Digest->new('SHA-256')->add(@data)->hexdigest;
}
sub sha256 ($self, @data) {
Digest->new('SHA-256')->add(@data)->digest;
}
sub hmac_sha256 ($self, $key, $data) {
my $ikey = $key ^ ("\x36" x length($key));
my $okey = $key ^ ("\x5c" x length($key));
return $self->sha256($okey, $self->sha256($ikey, $data));
}
sub hex2bigint ($self, $hex) { return Math::BigInt->from_hex($hex) }
sub generate_secret_key ($self) {
while ('necessary') {
my $candidate_hex = join '',
map { sprintf '%0x', int rand 16 } 1 .. $self->{n_hex};
my $candidate = Math::BigInt->from_hex($candidate_hex);
return $candidate if $candidate < $self->{p};
}
}
package SRPServerSession;
use v5.24;
use warnings;
use experimental 'signatures';
no warnings 'experimental::signatures';
use CryptoPals qw< random_octets >;
use parent -norequire => 'SRPSession';
sub init ($self) {
my $salt = $self->{salt} = random_octets(16);
my $x_hex = $self->sha256_hex($salt, $self->{P});
my $x = $self->hex2bigint($x_hex);
my $v = $self->{v} = $self->modexp($self->{g}, $x, $self->{p});
my $gb = $self->modexp($self->@{qw< g secret_key p>});
$self->{public_key} = ($v * $self->{k} + $gb) % $self->{p};
return $self;
}
sub fail_login { die "invalid login\n" }
sub login_phase1 ($self, $email, $client_public_key) {
fail_login() unless $email eq $self->{I};
$self->{client_pk} = $client_public_key;
my $u_hex = $self->sha256_hex($client_public_key, $self->{public_key});
my $u = $self->hex2bigint($u_hex);
my $v_u = $self->modexp($self->{v}, $u, $self->{p});
my $S = $self->{S} = $self->modexp($client_public_key * $v_u,
$self->{secret_key}, $self->{p});
$self->{K} = $self->sha256($S);
return $self->@{qw< salt public_key >};
}
sub login_phase2 ($self, $authenticator) {
my $expected = $self->hmac_sha256($self->@{qw< K salt >});
return $expected eq $authenticator;
}
package SRPClientSession;
use v5.24;
use warnings;
use experimental 'signatures';
no warnings 'experimental::signatures';
use parent -norequire => 'SRPSession';
sub init ($self) {
$self->{public_key} = $self->modexp($self->@{qw< g secret_key p>});
return $self;
}
sub public_key ($self) { return $self->{public_key} }
sub email ($self) { return $self->{I} }
sub login_phase1 ($self) { return $self->@{qw< I public_key >} }
sub login_phase2 ($self, $salt, $server_public_key) {
my $u_hex = $self->sha256_hex($self->{public_key}, $server_public_key);
my $u = $self->hex2bigint($u_hex);
my $x_hex = $self->sha256_hex($salt, $self->{P});
my $x = $self->hex2bigint($x_hex);
my $base = $self->{k} * $self->modexp($self->{g}, $x, $self->{p});
$base = ($server_public_key - $base) % $self->{p};
my $S = $self->{S} = $self->modexp($base,
$self->{secret_key} + $u * $x, $self->{p});
my $K = $self->{K} = $self->sha256($S);
return $self->hmac_sha256($K, $salt);
}
package main;
use v5.24;
use warnings;
use CryptoPals ':all';
use Data::Dumper;
use Test::More;
my %args = (
I => 'foo@example.com',
P => 'foo-bar-baz',
# g, p, k set according to defaults
);
my $server = SRPServerSession->new(%args);
my $client = SRPClientSession->new(%args);
my @c2s = $client->login_phase1;
my @s2c = $server->login_phase1(@c2s);
my $authenticator = $client->login_phase2(@s2c);
ok $server->login_phase2($authenticator), 'client authenticator OK';
done_testing();
There are two session sides: the server and the client. The two share a common base class where most of the lower-level heavy lifting is performed, like calculations and hashing.
Itโs been interesting to code the HMAC function according to the relevant Wikipedia page:
sub hmac_sha256 ($self, $key, $data) {
my $ikey = $key ^ ("\x36" x length($key));
my $okey = $key ^ ("\x5c" x length($key));
return $self->sha256($okey, $self->sha256($ikey, $data));
}
This arrangement should address the issues related to puttin the key either before or after the message for taking the HMAC.
The technique, as stated in the challenge text:
is basically Diffie Hellman with a tweak of mixing the password into the public keys.
It indeed worksโฆ and I suspect weโre going to be asked and break it soon!
Stay safe and secure!