Crypt-ECDH_ES

 view release on metacpan or  search on metacpan

lib/Crypt/ECDH_ES.pm  view on Meta::CPAN

package Crypt::ECDH_ES;
$Crypt::ECDH_ES::VERSION = '0.005';
use strict;
use warnings;

use Carp;
use Crypt::Curve25519;
use Crypt::URandom qw/urandom/;
use Crypt::Rijndael 1.16;
use Digest::SHA qw/sha256 hmac_sha256/;

use Exporter 5.57 'import';
our @EXPORT_OK = qw/ecdhes_encrypt ecdhes_decrypt ecdhes_encrypt_authenticated ecdhes_decrypt_authenticated ecdhes_generate_key/;
our %EXPORT_TAGS = (all => \@EXPORT_OK);

my $format_unauthenticated = 'C/a C/a n/a N/a';

sub ecdhes_encrypt {
	my ($public_key, $data) = @_;

	my $private = curve25519_secret_key(urandom(32));
	my $public  = curve25519_public_key($private);
	my $shared  = curve25519_shared_secret($private, $public_key);

	my ($encrypt_key, $sign_key) = unpack 'a16 a16', sha256($shared);
	my $iv     = substr sha256($public), 0, 16;
	my $cipher = Crypt::Rijndael->new($encrypt_key, Crypt::Rijndael::MODE_CBC);

	my $pad_length = 16 - length($data) % 16;
	my $padding = chr($pad_length) x $pad_length;

	my $ciphertext = $cipher->encrypt($data . $padding, $iv);
	my $mac = hmac_sha256($iv . $ciphertext, $sign_key);
	return pack $format_unauthenticated, '', $public, $mac, $ciphertext;
}

sub ecdhes_decrypt {
	my ($private_key, $packed_data) = @_;

	my ($options, $public, $mac, $ciphertext) = unpack $format_unauthenticated, $packed_data;
	croak 'Unknown options' if $options ne '';

	my $shared = curve25519_shared_secret($private_key, $public);
	my ($encrypt_key, $sign_key) = unpack 'a16 a16', sha256($shared);
	my $iv     = substr sha256($public), 0, 16;
	croak 'MAC is incorrect' if hmac_sha256($iv . $ciphertext, $sign_key) ne $mac;
	my $cipher = Crypt::Rijndael->new($encrypt_key, Crypt::Rijndael::MODE_CBC);

	my $plaintext = $cipher->decrypt($ciphertext, $iv);
	my $pad_length = ord substr $plaintext, -1;
	substr($plaintext, -$pad_length, $pad_length, '') eq chr($pad_length) x $pad_length or croak 'Incorrectly padded';
	return $plaintext;
}

my $format_authenticated = 'C/a C/a C/a C/a N/a';

sub ecdhes_encrypt_authenticated {
	my ($public_key_other, $private_key_self, $data) = @_;

	my $public_key_self = curve25519_public_key($private_key_self);
	my $private_ephemeral = curve25519_secret_key(urandom(32));
	my $ephemeral_public  = curve25519_public_key($private_ephemeral);
	my $primary_shared  = curve25519_shared_secret($private_ephemeral, $public_key_other);

	my ($primary_encrypt_key, $primary_iv) = unpack 'a16 a16', sha256($primary_shared);
	my $primary_cipher = Crypt::Rijndael->new($primary_encrypt_key, Crypt::Rijndael::MODE_CBC);
	my $encrypted_public_key = $primary_cipher->encrypt($public_key_self, $primary_iv);

	my $secondary_shared = $primary_shared . curve25519_shared_secret($private_key_self, $public_key_other);
	my ($secondary_encrypt_key, $sign_key) = unpack 'a16 a16', sha256($secondary_shared);
	my $cipher = Crypt::Rijndael->new($secondary_encrypt_key, Crypt::Rijndael::MODE_CBC);
	my $iv     = substr sha256($ephemeral_public), 0, 16;

	my $pad_length = 16 - length($data) % 16;
	my $padding = chr($pad_length) x $pad_length;

	my $ciphertext = $cipher->encrypt($data . $padding, $iv);
	my $mac = hmac_sha256($iv . $ciphertext, $sign_key);
	return pack $format_authenticated, "\x{1}", $ephemeral_public, $encrypted_public_key, $mac, $ciphertext;
}

sub ecdhes_decrypt_authenticated {
	my ($private_key, $packed_data) = @_;

	my ($options, $ephemeral_public, $encrypted_public_key, $mac, $ciphertext) = unpack $format_authenticated, $packed_data;
	croak 'Unknown options' if $options ne "\x{1}";

	my $primary_shared = curve25519_shared_secret($private_key, $ephemeral_public);
	my ($primary_encrypt_key, $primary_iv) = unpack 'a16 a16', sha256($primary_shared);
	my $primary_cipher = Crypt::Rijndael->new($primary_encrypt_key, Crypt::Rijndael::MODE_CBC);
	my $public_key = $primary_cipher->decrypt($encrypted_public_key, $primary_iv);

	my $secondary_shared = $primary_shared . curve25519_shared_secret($private_key, $public_key);
	my ($secondary_encrypt_key, $sign_key) = unpack 'a16 a16', sha256($secondary_shared);
	my $cipher = Crypt::Rijndael->new($secondary_encrypt_key, Crypt::Rijndael::MODE_CBC);
	my $iv     = substr sha256($ephemeral_public), 0, 16;

	croak 'MAC is incorrect' if hmac_sha256($iv . $ciphertext, $sign_key) ne $mac;

	my $plaintext = $cipher->decrypt($ciphertext, $iv);
	my $pad_length = ord substr $plaintext, -1;
	substr($plaintext, -$pad_length, $pad_length, '') eq chr($pad_length) x $pad_length or croak 'Incorrectly padded';
	return ($plaintext, $public_key);
}

sub ecdhes_generate_key {
	my $buf = urandom(32);
	my $secret = curve25519_secret_key($buf);
	my $public = curve25519_public_key($secret);
	return ($public, $secret);
}

1;

#ABSTRACT: A fast and small hybrid crypto system

__END__

=pod

=encoding UTF-8

=head1 NAME

Crypt::ECDH_ES - A fast and small hybrid crypto system

=head1 VERSION

version 0.005

=head1 SYNOPSIS

 my $ciphertext = ecdhes_encrypt($data, $key);
 my $plaintext = ecdhes_decrypt($ciphertext, $key);

=head1 DESCRIPTION

This module uses elliptic curve cryptography in an ephemerical-static configuration combined with the AES cipher to achieve a hybrid cryptographical system. Both the public and the private key are simply 32 byte blobs.

=head2 Use-cases

You may want to use this module when storing sensive data in such a way that the encoding side can't read it afterwards, for example a website storing credit card data in a database that will be used by a separate back-end financial processor. When u...

=head2 DISCLAIMER

This distribution comes with no warranties whatsoever. While the author believes he's at least somewhat clueful in cryptography and it based on a well-understood model (ECIES), he is not a profesional cryptographer. Users of this distribution are enc...

=head2 TECHNICAL DETAILS

This modules uses Daniel J. Bernstein's curve25519 (also used by OpenSSH) to perform a Diffie-Hellman key agreement between an encoder and a decoder. The keys of the decoder should be known in advance (as this system works as a one-way communication ...

All cryptographic components are believed to provide at least 128-bits of security.

=head1 FUNCTIONS

=head2 ecdhes_encrypt($public_key, $plaintext)

This will encrypt C<$plaintext> using C<$public_key>. This is a non-deterministic encryption: the result will be different for every invocation.

=head2 ecdhes_decrypt($private_key, $ciphertext)

This will decrypt C<$ciphertext> (as encrypted using C<ecdhes_encrypt>) using C<$private_key> and return the plaintext.

=head2 ecdhes_encrypt_authenticated($public_key, $private_key, $plaintext)

This will encrypt C<$plaintext> using C<$public_key> (of the receiver) and C<$private_key> (of the sender). This is a non-deterministic encryption: the result will be different for every invocation.

=head2 ecdhes_decrypt_authenticated($private_key, $ciphertext)

This will decrypt C<$ciphertext> (as encrypted using C<ecdhes_encrypt_authenticated>) using C<$private_key> and return the plaintext and the public of the sender

=head2 ecdhes_generate_key()

This function generates a new random curve25519 keypair and returns it as C<($public_key, private_key)>

=head1 SEE ALSO

=over 4

=item * L<Crypt::OpenPGP|Crypt::OpenPGP>

This module can be used to achieve exactly the same effect in a more standardized way, but it requires much more infrastructure (such as a keychain), many more dependencies, larger messages and more thinking about various settings.

On the other hand, if your use-case has authenticity-checking needs that can not be solved using a MAC, you may want to use it instead of Crypt::ECDH_ES.

=item * L<Crypt::Ed25519|Crypt::Ed25519>

This is a public key signing/verification system based on an equivalent curve.

=back

=head1 AUTHOR

Leon Timmermans <fawaka@gmail.com>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2015 by Leon Timmermans.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut

 view all matches for this distribution
 view release on metacpan -  search on metacpan

( run in 1.083 second using v1.00-cache-2.02-grep-82fe00e-cpan-b63e86051f13 )