File Coverage

blib/lib/Crypt/EECDH.pm
Criterion Covered Total %
statement 84 87 96.5
branch 19 28 67.8
condition 3 7 42.8
subroutine 15 16 93.7
pod 5 5 100.0
total 126 143 88.1


line stmt bran cond sub pod time code
1             # -*-cperl-*-
2             #
3             # Crypt::EECDH - Simple ephemeral ECDH + AES hybrid cryptosystem
4             # Copyright (c) 2017 Ashish Gulhati
5             #
6             # $Id: lib/Crypt/EECDH.pm v1.005 Sat Jun 10 10:46:49 PDT 2017 $
7              
8             package Crypt::EECDH;
9              
10 2     2   28505 use warnings;
  2         5  
  2         59  
11 2     2   8 use strict;
  2         4  
  2         34  
12              
13 2     2   808 use Crypt::Curve25519;
  2         1421  
  2         98  
14 2     2   832 use Crypt::Ed25519;
  2         507  
  2         48  
15 2     2   717 use Crypt::EC_DSA;
  2         113594  
  2         59  
16 2     2   16 use Bytes::Random::Secure;
  2         4  
  2         78  
17 2     2   741 use Crypt::Rijndael;
  2         607  
  2         69  
18 2     2   15 use Digest::SHA qw/sha256 hmac_sha256/;
  2         5  
  2         108  
19 2     2   17 use vars qw( $VERSION $AUTOLOAD );
  2         3  
  2         1397  
20              
21             our ( $VERSION ) = '$Revision: 1.005 $' =~ /\s+([\d\.]+)/;
22              
23             sub new {
24 1     1 1 11 my ($class, %arg) = @_;
25             bless { debug => $arg{Debug} || 0,
26 1   50     13 sigscheme => $arg{SigScheme} || 'ECDSA'
      50        
27             }, $class;
28             }
29              
30             my $format = 'C/a C/a n/a N/a';
31              
32             sub encrypt {
33 2     2 1 1523 my ($self, %arg) = @_;
34              
35 2 50 33     23 if (defined $arg{SigningKey} && defined $arg{Signature}) {
36             # Check signature on $public_key
37 2 100       20 if ($self->sigscheme eq 'Ed25519') {
38 1 50       259 return unless Crypt::Ed25519::verify($arg{PublicKey}, $arg{SigningKey}, $arg{Signature});
39             }
40             else {
41 1         9 my $ecdsa = new Crypt::EC_DSA;
42             return unless $ecdsa->verify( Message => $arg{PublicKey}, Signature => $arg{Signature},
43 1 50       4556 Key => $arg{SigningKey} );
44             }
45             }
46              
47 2         24731074 my $random = Bytes::Random::Secure->new( Bits => 128 );
48 2         207 my $private = curve25519_secret_key($random->bytes(32));
49 2         657327997 my $public = curve25519_public_key($private);
50 2         309 my $shared = curve25519_shared_secret($private, $arg{PublicKey});
51              
52 2         42 my ($encrypt_key, $sign_key) = unpack 'a16 a16', sha256($shared);
53 2         20 my $iv = substr sha256($public), 0, 16;
54 2         47 my $cipher = Crypt::Rijndael->new($encrypt_key, Crypt::Rijndael::MODE_CBC);
55 2         15 $cipher->set_iv($iv);
56              
57 2         12 my $pad_length = 16 - length($arg{Message}) % 16;
58 2         14 my $padding = chr($pad_length) x $pad_length;
59              
60 2         19 my $ciphertext = $cipher->encrypt($arg{Message} . $padding);
61 2         28 my $mac = hmac_sha256($iv . $ciphertext, $sign_key);
62 2         82 return (pack ($format, '', $public, $mac, $ciphertext), $private);
63             }
64              
65             sub decrypt {
66 2     2 1 14 my ($self, %arg) = @_;
67              
68 2         21 my ($options, $public, $mac, $ciphertext) = unpack $format, $arg{Ciphertext};
69 2 50       11 die 'Unknown options' if $options ne '';
70              
71 2         248 my $shared = curve25519_shared_secret($arg{Key}, $public);
72 2         22 my ($encrypt_key, $sign_key) = unpack 'a16 a16', sha256($shared);
73 2         16 my $iv = substr sha256($public), 0, 16;
74 2 50       30 die 'MAC is incorrect' if hmac_sha256($iv . $ciphertext, $sign_key) ne $mac;
75 2         24 my $cipher = Crypt::Rijndael->new($encrypt_key, Crypt::Rijndael::MODE_CBC);
76 2         11 $cipher->set_iv($iv);
77              
78 2         14 my $plaintext = $cipher->decrypt($ciphertext);
79 2         10 my $pad_length = ord substr $plaintext, -1;
80 2 50       17 substr($plaintext, -$pad_length, $pad_length, '') eq chr($pad_length) x $pad_length or die 'Incorrectly padded';
81 2         21 return ($plaintext, $public);
82             }
83              
84             sub keygen {
85 4     4 1 1552 my ($self, %arg) = @_;
86 4         38 my $random = Bytes::Random::Secure->new( Bits => 128 );
87 4         539 my $secret = curve25519_secret_key($random->bytes(32));
88 4         1074012810 my $public = curve25519_public_key($secret);
89 4 100       31 if ($arg{PrivateKey}) {
90             # Sign public key with the signing key
91 2         50 my $signature;
92 2 100       23 if ($self->sigscheme eq 'Ed25519') {
93 1         92 $signature = Crypt::Ed25519::sign( $public, $arg{PublicKey}, $arg{PrivateKey} );
94             }
95             else {
96 1         20 my $ecdsa = new Crypt::EC_DSA;
97 1         4586 $signature = $ecdsa->sign( Message => $public, Key => $arg{PrivateKey} );
98             }
99 2         232172344 return ($public, $secret, $signature);
100             }
101             else {
102 2         42 return ($public, $secret);
103             }
104             }
105              
106             sub signkeygen {
107 2     2 1 19 my $self = shift;
108 2 100       17 if ($self->sigscheme eq 'Ed25519') {
109 1         1879 return Crypt::Ed25519::generate_keypair;
110             }
111             else {
112 1         8 my $ecdsa = new Crypt::EC_DSA;
113 1         4420 $ecdsa->keygen;
114             }
115             }
116              
117             sub AUTOLOAD {
118 11     11   26217313 my $self = shift; (my $auto = $AUTOLOAD) =~ s/.*:://;
  11         98  
119 11 100       81 return if $auto eq 'DESTROY';
120 10 50       72 if ($auto =~ /^(debug|sigscheme)$/x) {
121 10 100       42 $self->{$auto} = shift if (defined $_[0]);
122 10         105 return $self->{$auto};
123             }
124             else {
125 0           die "Could not AUTOLOAD method $auto.";
126             }
127             }
128              
129             sub _diag {
130 0     0     my $self = shift;
131 0 0         print STDERR @_ if $self->debug;
132             }
133              
134             1; # End of Crypt::EECDH
135              
136             __END__