File Coverage

blib/lib/Crypt/ECDH_ES.pm
Criterion Covered Total %
statement 85 85 100.0
branch 6 12 50.0
condition n/a
subroutine 13 13 100.0
pod 5 5 100.0
total 109 115 94.7


line stmt bran cond sub pod time code
1             package Crypt::ECDH_ES;
2             $Crypt::ECDH_ES::VERSION = '0.006';
3 2     2   283982 use strict;
  2         5  
  2         87  
4 2     2   18 use warnings;
  2         5  
  2         143  
5              
6 2     2   10 use Carp;
  2         3  
  2         158  
7 2     2   1178 use Crypt::Curve25519;
  2         2331  
  2         163  
8 2     2   1079 use Crypt::SysRandom qw/random_bytes/;
  2         6871  
  2         151  
9 2     2   856 use Crypt::Rijndael 1.16;
  2         1230  
  2         77  
10 2     2   1144 use Digest::SHA qw/sha256 hmac_sha256/;
  2         7720  
  2         247  
11              
12 2     2   30 use Exporter 5.57 'import';
  2         54  
  2         2573  
13             our @EXPORT_OK = qw/ecdhes_encrypt ecdhes_decrypt ecdhes_encrypt_authenticated ecdhes_decrypt_authenticated ecdhes_generate_key/;
14             our %EXPORT_TAGS = (all => \@EXPORT_OK);
15              
16             my $format_unauthenticated = 'C/a C/a n/a N/a';
17              
18             sub ecdhes_encrypt {
19 1     1 1 238969 my ($public_key, $data) = @_;
20              
21 1         18 my $private = curve25519_secret_key(random_bytes(32));
22 1         520 my $public = curve25519_public_key($private);
23 1         487 my $shared = curve25519_shared_secret($private, $public_key);
24              
25 1         22 my ($encrypt_key, $sign_key) = unpack 'a16 a16', sha256($shared);
26 1         9 my $iv = substr sha256($public), 0, 16;
27 1         19 my $cipher = Crypt::Rijndael->new($encrypt_key, Crypt::Rijndael::MODE_CBC);
28              
29 1         4 my $pad_length = 16 - length($data) % 16;
30 1         5 my $padding = chr($pad_length) x $pad_length;
31              
32 1         9 my $ciphertext = $cipher->encrypt($data . $padding, $iv);
33 1         13 my $mac = hmac_sha256($iv . $ciphertext, $sign_key);
34 1         17 return pack $format_unauthenticated, '', $public, $mac, $ciphertext;
35             }
36              
37             sub ecdhes_decrypt {
38 1     1 1 1226 my ($private_key, $packed_data) = @_;
39              
40 1         29 my ($options, $public, $mac, $ciphertext) = unpack $format_unauthenticated, $packed_data;
41 1 50       7 croak 'Unknown options' if $options ne '';
42              
43 1         453 my $shared = curve25519_shared_secret($private_key, $public);
44 1         13 my ($encrypt_key, $sign_key) = unpack 'a16 a16', sha256($shared);
45 1         16 my $iv = substr sha256($public), 0, 16;
46 1 50       17 croak 'MAC is incorrect' if hmac_sha256($iv . $ciphertext, $sign_key) ne $mac;
47 1         11 my $cipher = Crypt::Rijndael->new($encrypt_key, Crypt::Rijndael::MODE_CBC);
48              
49 1         7 my $plaintext = $cipher->decrypt($ciphertext, $iv);
50 1         4 my $pad_length = ord substr $plaintext, -1;
51 1 50       8 substr($plaintext, -$pad_length, $pad_length, '') eq chr($pad_length) x $pad_length or croak 'Incorrectly padded';
52 1         8 return $plaintext;
53             }
54              
55             my $format_authenticated = 'C/a C/a C/a C/a N/a';
56              
57             sub ecdhes_encrypt_authenticated {
58 1     1 1 28 my ($public_key_other, $private_key_self, $data) = @_;
59              
60 1         442 my $public_key_self = curve25519_public_key($private_key_self);
61 1         16 my $private_ephemeral = curve25519_secret_key(random_bytes(32));
62 1         531 my $ephemeral_public = curve25519_public_key($private_ephemeral);
63 1         465 my $primary_shared = curve25519_shared_secret($private_ephemeral, $public_key_other);
64              
65 1         12 my ($primary_encrypt_key, $primary_iv) = unpack 'a16 a16', sha256($primary_shared);
66 1         13 my $primary_cipher = Crypt::Rijndael->new($primary_encrypt_key, Crypt::Rijndael::MODE_CBC);
67 1         6 my $encrypted_public_key = $primary_cipher->encrypt($public_key_self, $primary_iv);
68              
69 1         515 my $secondary_shared = $primary_shared . curve25519_shared_secret($private_key_self, $public_key_other);
70 1         16 my ($secondary_encrypt_key, $sign_key) = unpack 'a16 a16', sha256($secondary_shared);
71 1         12 my $cipher = Crypt::Rijndael->new($secondary_encrypt_key, Crypt::Rijndael::MODE_CBC);
72 1         6 my $iv = substr sha256($ephemeral_public), 0, 16;
73              
74 1         4 my $pad_length = 16 - length($data) % 16;
75 1         6 my $padding = chr($pad_length) x $pad_length;
76              
77 1         7 my $ciphertext = $cipher->encrypt($data . $padding, $iv);
78 1         11 my $mac = hmac_sha256($iv . $ciphertext, $sign_key);
79 1         16 return pack $format_authenticated, "\x{1}", $ephemeral_public, $encrypted_public_key, $mac, $ciphertext;
80             }
81              
82             sub ecdhes_decrypt_authenticated {
83 1     1 1 1089 my ($private_key, $packed_data) = @_;
84              
85 1         8 my ($options, $ephemeral_public, $encrypted_public_key, $mac, $ciphertext) = unpack $format_authenticated, $packed_data;
86 1 50       6 croak 'Unknown options' if $options ne "\x{1}";
87              
88 1         467 my $primary_shared = curve25519_shared_secret($private_key, $ephemeral_public);
89 1         13 my ($primary_encrypt_key, $primary_iv) = unpack 'a16 a16', sha256($primary_shared);
90 1         12 my $primary_cipher = Crypt::Rijndael->new($primary_encrypt_key, Crypt::Rijndael::MODE_CBC);
91 1         7 my $public_key = $primary_cipher->decrypt($encrypted_public_key, $primary_iv);
92              
93 1         513 my $secondary_shared = $primary_shared . curve25519_shared_secret($private_key, $public_key);
94 1         14 my ($secondary_encrypt_key, $sign_key) = unpack 'a16 a16', sha256($secondary_shared);
95 1         11 my $cipher = Crypt::Rijndael->new($secondary_encrypt_key, Crypt::Rijndael::MODE_CBC);
96 1         7 my $iv = substr sha256($ephemeral_public), 0, 16;
97              
98 1 50       37 croak 'MAC is incorrect' if hmac_sha256($iv . $ciphertext, $sign_key) ne $mac;
99              
100 1         8 my $plaintext = $cipher->decrypt($ciphertext, $iv);
101 1         3 my $pad_length = ord substr $plaintext, -1;
102 1 50       10 substr($plaintext, -$pad_length, $pad_length, '') eq chr($pad_length) x $pad_length or croak 'Incorrectly padded';
103 1         10 return ($plaintext, $public_key);
104             }
105              
106             sub ecdhes_generate_key {
107 2     2 1 1175 my $buf = random_bytes(32);
108 2         9 my $secret = curve25519_secret_key($buf);
109 2         1101 my $public = curve25519_public_key($secret);
110 2         17 return ($public, $secret);
111             }
112              
113             1;
114              
115             #ABSTRACT: A fast and small hybrid crypto system
116              
117             __END__