File Coverage

blib/lib/Crypt/ECDH_ES.pm
Criterion Covered Total %
statement 47 56 83.9
branch 3 12 25.0
condition n/a
subroutine 9 10 90.0
pod 3 3 100.0
total 62 81 76.5


line stmt bran cond sub pod time code
1             package Crypt::ECDH_ES;
2             $Crypt::ECDH_ES::VERSION = '0.002';
3 1     1   13498 use strict;
  1         2  
  1         23  
4 1     1   3 use warnings;
  1         1  
  1         25  
5              
6 1     1   4 use Carp;
  1         3  
  1         61  
7 1     1   401 use Crypt::Curve25519;
  1         660  
  1         50  
8 1     1   367 use Crypt::Rijndael;
  1         305  
  1         25  
9 1     1   412 use Digest::SHA qw/sha256 hmac_sha256/;
  1         2470  
  1         73  
10              
11 1     1   4 use Exporter 5.57 'import';
  1         14  
  1         526  
12             our @EXPORT_OK = qw/ecdhes_encrypt ecdhes_decrypt ecdhes_generate_key/;
13             our %EXPORT_TAGS = (all => \@EXPORT_OK);
14              
15             my $csprng = ($^O eq 'MSWin32') ?
16             do {
17             require Win32::API;
18             my $genrand = Win32::API->new('advapi32', 'INT SystemFunction036(PVOID RandomBuffer, ULONG RandomBufferLength)') or croak "Could not import SystemFunction036: $^E";
19             sub {
20             my $count = shift;
21             $genrand->Call(my $buffer, $count) or croak "Could not read from csprng: $^E";
22             return $buffer;
23             }
24             } :
25             do {
26             open my $urandom, '<:raw', '/dev/urandom' or croak 'Couldn\'t open /dev/urandom';
27             sub {
28             my $count = shift;
29             read $urandom, my $buffer, $count or croak "Couldn't read from csprng: $!";
30             return $buffer;
31             };
32             };
33              
34             my $format = 'C/a C/a n/a N/a';
35              
36             sub ecdhes_encrypt {
37 1     1 1 6 my ($public_key, $data) = @_;
38              
39 1         3 my $private = curve25519_secret_key($csprng->(32));
40 1         116 my $public = curve25519_public_key($private);
41 1         89 my $shared = curve25519_shared_secret($private, $public_key);
42              
43 1         21 my ($encrypt_key, $sign_key) = unpack 'A16 A16', sha256($shared);
44 1         5 my $iv = substr sha256($public), 0, 16;
45 1         12 my $cipher = Crypt::Rijndael->new($encrypt_key, Crypt::Rijndael::MODE_CBC);
46 1         4 $cipher->set_iv($iv);
47              
48 1         2 my $pad_length = 16 - length($data) % 16;
49 1         3 my $padding = chr($pad_length) x $pad_length;
50              
51 1         6 my $ciphertext = $cipher->encrypt($data . $padding);
52 1         8 my $mac = hmac_sha256($iv . $ciphertext, $sign_key);
53 1         11 return pack $format, '', $public, $mac, $ciphertext;
54             }
55              
56             sub ecdhes_decrypt {
57 1     1 1 424 my ($private_key, $packed_data) = @_;
58              
59 1         14 my ($options, $public, $mac, $ciphertext) = unpack $format, $packed_data;
60 1 50       5 croak 'Unknown options' if $options ne '';
61              
62 1         97 my $shared = curve25519_shared_secret($private_key, $public);
63 1         8 my ($encrypt_key, $sign_key) = unpack 'A16 A16', sha256($shared);
64 1         8 my $iv = substr sha256($public), 0, 16;
65 1 50       17 croak 'MAC is incorrect' if hmac_sha256($iv . $ciphertext, $sign_key) ne $mac;
66 1         7 my $cipher = Crypt::Rijndael->new($encrypt_key, Crypt::Rijndael::MODE_CBC);
67 1         3 $cipher->set_iv($iv);
68              
69 1         4 my $plaintext = $cipher->decrypt($ciphertext);
70 1         2 my $pad_length = ord substr $plaintext, -1;
71 1 50       6 substr($plaintext, -$pad_length, $pad_length, '') eq chr($pad_length) x $pad_length or croak 'Incorrectly padded';
72 1         5 return $plaintext;
73             }
74              
75             sub ecdhes_generate_key {
76 0     0 1   my $buf;
77 0 0         if ($^O eq 'MSWin32') {
78 0           $buf = $csprng->(32);
79             }
80             else {
81 0 0         open my $fh, '<:raw', '/dev/random' or croak "Couldn't open /dev/random: $!";
82 0 0         read $fh, $buf, 32 or croak "Can't read from /dev/random: $!";
83 0           close $fh;
84             }
85 0           my $secret = curve25519_secret_key($buf);
86 0           my $public = curve25519_public_key($secret);
87 0           return ($public, $secret);
88             }
89              
90             1;
91              
92             #ABSTRACT: A fast and small hybrid crypto system
93              
94             __END__