Crypt-JWT
view release on metacpan or search on metacpan
lib/Crypt/KeyWrap.pm view on Meta::CPAN
}
croak "pbes2_key_unwrap: invalid alg '$alg'";
return undef;
}
# RSA KW
# https://tools.ietf.org/html/rfc7518#section-4.2
# https://tools.ietf.org/html/rfc7518#section-4.3
sub rsa_key_wrap {
my ($kek_public, $pt_data, $alg) = @_;
croak "rsa_key_wrap: no Crypt::PK::RSA" unless ref $kek_public eq 'Crypt::PK::RSA';
my ($padding, $hash_name);
if ($alg eq 'RSA-OAEP') { ($padding, $hash_name) = ('oaep', 'SHA1') }
elsif ($alg eq 'RSA-OAEP-256') { ($padding, $hash_name) = ('oaep', 'SHA256') }
elsif ($alg eq 'RSA1_5') { $padding = 'v1.5' }
croak "rsa_key_wrap: invalid algorithm '$alg'" unless $padding;
my $ct_data = $kek_public->encrypt($pt_data, $padding, $hash_name);
return $ct_data;
}
sub rsa_key_unwrap {
my ($kek_private, $ct_data, $alg) = @_;
croak "rsa_key_unwrap: no Crypt::PK::RSA" unless ref $kek_private eq 'Crypt::PK::RSA';
croak "rsa_key_unwrap: no private key" unless $kek_private->is_private;
my ($padding, $hash_name);
if ($alg eq 'RSA-OAEP') { ($padding, $hash_name) = ('oaep', 'SHA1') }
elsif ($alg eq 'RSA-OAEP-256') { ($padding, $hash_name) = ('oaep', 'SHA256') }
elsif ($alg eq 'RSA1_5') { $padding = 'v1.5' }
croak "rsa_key_unwrap: invalid algorithm '$alg'" unless $padding;
my $pt_data = $kek_private->decrypt($ct_data, $padding, $hash_name);
return $pt_data;
}
# ConcatKDF - http://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Ar2.pdf
# ECDH KW - https://tools.ietf.org/html/rfc7518#section-4.6
sub _concat_kdf {
my ($hash_name, $key_size, $shared_secret, $algorithm, $apu, $apv) = @_;
$apu = _decode_ecdh_info('apu', $apu);
$apv = _decode_ecdh_info('apv', $apv);
my $hsize = Crypt::Digest->hashsize($hash_name);
my $count = int($key_size / $hsize);
$count++ if ($key_size % $hsize) > 0;
my $data = '';
for my $i (1..$count) {
$data .= digest_data($hash_name, pack("N", $i) .
$shared_secret .
pack("N", length($algorithm)) . $algorithm .
pack("N", length($apu)) . $apu .
pack("N", length($apv)) . $apv .
pack("N", 8 *$key_size));
}
return substr($data, 0, $key_size);
}
sub ecdh_key_wrap {
my ($kek_public, $enc, $apu, $apv) = @_;
croak "ecdh_key_wrap: no Crypt::PK::ECC" unless ref $kek_public eq 'Crypt::PK::ECC';
my $encryption_key_size = 256;
if ($enc =~ /^A(128|192|256)CBC-HS/) {
$encryption_key_size = $1*2;
}
if ($enc =~ /^A(128|192|256)GCM/) {
$encryption_key_size = $1;
}
my $ephemeral = Crypt::PK::ECC->new()->generate_key($kek_public->curve2hash);
my $shared_secret = $ephemeral->shared_secret($kek_public);
my $ct_data = _concat_kdf('SHA256', $encryption_key_size/8, $shared_secret, $enc, $apu, $apv);
return ($ct_data, $ephemeral->export_key_jwk('public'));
}
sub ecdh_key_unwrap {
my ($kek_private, $enc, $epk, $apu, $apv) = @_;
croak "ecdh_key_unwrap: no Crypt::PK::ECC" unless ref $kek_private eq 'Crypt::PK::ECC';
croak "ecdh_key_unwrap: no private key" unless $kek_private->is_private;
my $encryption_key_size = 256;
if ($enc =~ /^A(128|192|256)CBC-HS/) {
$encryption_key_size = $1*2;
}
if ($enc =~ /^A(128|192|256)GCM/) {
$encryption_key_size = $1;
}
my $ephemeral = ref($epk) eq 'Crypt::PK::ECC' ? $epk : Crypt::PK::ECC->new(ref $epk ? $epk : \$epk);
my $shared_secret = $kek_private->shared_secret($ephemeral);
my $pt_data = _concat_kdf('SHA256', $encryption_key_size/8, $shared_secret, $enc, $apu, $apv);
return $pt_data;
}
sub ecdhaes_key_wrap {
my ($kek_public, $pt_data, $alg, $apu, $apv) = @_;
croak "ecdhaes_key_wrap: no Crypt::PK::(ECC|X25519)" unless ref($kek_public) =~ /^Crypt::PK::(ECC|X25519)$/;
my $encryption_key_size = 256;
if ($alg =~ /^ECDH-ES\+A(128|192|256)KW$/) {
$encryption_key_size = $1;
}
my $ephemeral;
if (ref($kek_public) eq 'Crypt::PK::ECC') {
$ephemeral = Crypt::PK::ECC->new->generate_key($kek_public->curve2hash);
}
else {
$ephemeral = Crypt::PK::X25519->new->generate_key();
}
my $shared_secret = $ephemeral->shared_secret($kek_public);
my $kek = _concat_kdf('SHA256', $encryption_key_size/8, $shared_secret, $alg, $apu, $apv);
# RFC 7518 sec 4.6 wraps via "AES Key Wrap algorithm specified in RFC 3394"
return (aes_key_wrap($kek, $pt_data, 'AES', 0), $ephemeral->export_key_jwk('public'));
}
sub ecdhaes_key_unwrap {
my ($kek_private, $ct_data, $alg, $epk, $apu, $apv) = @_;
croak "ecdhaes_key_unwrap: no Crypt::PK::(ECC|X25519)" unless ref($kek_private) =~ /^Crypt::PK::(ECC|X25519)$/;
croak "ecdhaes_key_unwrap: no private key" unless $kek_private->is_private;
my $encryption_key_size = 256;
if ($alg =~ /^ECDH-ES\+A(128|192|256)KW$/) {
$encryption_key_size = $1;
}
my $ephemeral;
if (ref($kek_private) eq 'Crypt::PK::ECC') {
$ephemeral = ref($epk) eq 'Crypt::PK::ECC' ? $epk : Crypt::PK::ECC->new(ref $epk ? $epk : \$epk);
}
else {
$ephemeral = ref($epk) eq 'Crypt::PK::X25519' ? $epk : Crypt::PK::X25519->new(ref $epk ? $epk : \$epk);
}
my $shared_secret = $kek_private->shared_secret($ephemeral);
my $kek = _concat_kdf('SHA256', $encryption_key_size/8, $shared_secret, $alg, $apu, $apv);
my $pt_data = aes_key_unwrap($kek, $ct_data, 'AES', 0);
return $pt_data;
}
1;
=pod
=head1 NAME
Crypt::KeyWrap - Key management/wrapping algorithms defined in RFC7518 (JWA)
( run in 0.558 second using v1.01-cache-2.11-cpan-e1769b4cff6 )