File Coverage

blib/lib/HEAT/Crypto.pm
Criterion Covered Total %
statement 81 108 75.0
branch 18 34 52.9
condition 1 2 50.0
subroutine 17 21 80.9
pod 9 13 69.2
total 126 178 70.7


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package HEAT::Crypto;
4              
5 1     1   58550 use strict;
  1         2  
  1         25  
6 1     1   5 use warnings;
  1         2  
  1         22  
7              
8 1     1   4 use Carp;
  1         2  
  1         85  
9 1     1   6 use XSLoader;
  1         2  
  1         30  
10 1     1   479 use Digest::SHA;
  1         2654  
  1         39  
11 1     1   421 use Crypt::Mode::CBC;
  1         8710  
  1         28  
12 1     1   409 use Crypt::PRNG qw(random_bytes);
  1         761  
  1         67  
13              
14             our $VERSION = '0.06';
15             XSLoader::load('HEAT::Crypto', $VERSION);
16              
17 1     1   6 use Exporter qw(import);
  1         1  
  1         1152  
18             our @EXPORT_OK = qw(hash keyspec keygen priv_to_pub_key
19             shared_key sign verify encrypt decrypt account_id tohex unhex);
20              
21             my $cbc = Crypt::Mode::CBC->new('AES');
22              
23             sub KEYSIZE()
24             {
25             32;
26             }
27              
28             sub KEYBUFF()
29             {
30             "\0" x KEYSIZE;
31             }
32              
33             sub tohex($)
34             {
35 0 0   0 0 0 return undef unless defined $_[0];
36 0         0 unpack('H*', $_[0]);
37             }
38              
39             sub unhex($)
40             {
41 0 0   0 0 0 return undef unless defined $_[0];
42 0         0 pack('H*', $_[0]);
43             }
44              
45             sub hash
46             {
47 9     9 0 27 my $sha = Digest::SHA->new(256);
48 9         116 $sha->add($_) for @_;
49 9         69 return $sha->digest;
50             }
51              
52             sub keyhash
53             {
54 0     0 0 0 my $k = hash(@_);
55 0         0 _clamp($k);
56 0         0 return $k;
57             }
58              
59             sub keygen(;$)
60             {
61 6 100   6 1 87 my $k = defined($_[0]) ? keyspec($_[0]) : random_bytes(KEYSIZE);
62 6         121 my $p = KEYBUFF;
63 6         10 my $s = KEYBUFF;
64              
65 6         14 _clamp($k);
66 6         2994 _core($p, $s, $k, undef);
67              
68             return {
69 6         31 p => $p,
70             s => $s,
71             k => $k,
72             };
73             }
74              
75             sub shared_key($$)
76             {
77 4     4 1 18 my $k = keyspec($_[0], 1);
78 4         12 my $p = keyspec($_[1]);
79 4         6 my $z = KEYBUFF;
80 4         1520 _core($z, undef, $k, $p);
81              
82 4         11 return $z;
83             }
84              
85             sub sign($$)
86             {
87 1     1 1 3 my $k = keyspec($_[0], 1);
88 1         2 my $msg = $_[1];
89              
90 1         3 my $m = hash($msg);
91 1         3 my $r = keygen($k);
92 1         3 my $x = hash($m, $r->{s});
93 1         2 my $y = keygen($x);
94 1         3 my $h = hash($m, $y->{p});
95              
96 1         3 my $v = KEYBUFF;
97 1 50       26 if (_sign($v, $h, $x, $r->{s})) {
98 1         7 return $v . $h;
99             }
100              
101 0         0 return undef;
102             }
103              
104             sub verify($$$)
105             {
106 1     1 1 3 my ($s, $m) = @_;
107 1         3 my $k = keyspec($_[2]);
108              
109 1 50 50     7 unless (defined $s) {
    50          
110 0         0 croak('undefined signature');
111 0         0 } elsif ($s =~ /^[[:xdigit:]]{128}$/) {
112 0         0 $s = unhex($s);
113             } elsif (length $s != 64) {
114             croak('invalid signature: %q', $s);
115             }
116              
117 1         3 my $v = substr($s, 0, 32);
118 1         2 my $h = substr($s, 32, 64);
119              
120 1         2 my $y = KEYBUFF;
121 1         635 _verify($y, $v, $h, $k);
122              
123 1         3 return hash(hash($m), $y) eq $h;
124             }
125              
126             sub keyspec($;$)
127             {
128 18     18 1 29 my ($spec, $is_private) = @_;
129              
130 18 0       72 unless (defined $spec) {
    50          
    50          
    50          
131 0         0 croak('undefined key spec');
132 0 100       0 } elsif ($is_private && $spec =~ /^([a-z]{3,12}( |\Z)){12}$/) {
133 0         0 return keyhash($spec);
134 0         0 } elsif (length $spec == KEYSIZE) {
135 18         31 return $spec;
136 0         0 } elsif ($spec =~ /^[[:xdigit:]]{64}$/) {
137 0         0 return unhex($spec);
138             } else {
139 0         0 croak('invalid key spec: %q', $spec);
140             }
141             }
142              
143             sub priv_to_pub_key($)
144             {
145 2     2 1 5 my $k = keyspec($_[0], 1);
146 2         3 my $r = keygen($k);
147 2         10 return $r->{p};
148             }
149              
150             sub account_id($)
151             {
152 0     0 1 0 my $k = keyspec($_[0]);
153 0         0 my $h = hash($k);
154              
155 0         0 my ($id, $t1, $t2) = (0);
156              
157 0         0 for (my $i = 7; $i >= 0; $i--) {
158 0         0 $t1 = $id * 256;
159 0         0 $t2 = $t1 + vec($h, $i, 8);
160 0         0 $id = $t2;
161             }
162              
163 0         0 return $id;
164             }
165              
166             sub encrypt($$;$)
167             {
168 2     2 1 4 my ($data, $k, $p) = @_;
169              
170 2 100       8 my $key = @_ == 3 ? shared_key($k, $p) : keyspec($k);
171              
172 2         7 my $iv = random_bytes(16);
173 2         23 my $nonce = random_bytes(32);
174              
175 2         26 for (my $i = 0; $i < 32; $i++) {
176 64         120 vec($key, $i, 8) = vec($key, $i, 8) ^ vec($nonce, $i, 8);
177             }
178              
179 2         3 my $encrypted = eval { $cbc->encrypt($data, hash($key), $iv) };
  2         4  
180 2 50       63 return undef if $@;
181              
182 2 50       14 return wantarray ? ($nonce, $iv, $encrypted) : $nonce . $iv . $encrypted;
183             }
184              
185             sub decrypt($$;$)
186             {
187 2     2 1 5 my ($data, $k, $p) = @_;
188              
189 2 100       8 my $key = @_ == 3 ? shared_key($k, $p) : keyspec($k);
190              
191             my ($nonce, $iv, $encrypted) = ref($data) eq 'ARRAY'
192 2 50       13 ? @{$data} : unpack('a32 a16 a*', $data);
  0         0  
193              
194 2         6 for (my $i = 0; $i < 32; $i++) {
195 64         121 vec($key, $i, 8) = vec($key, $i, 8) ^ vec($nonce, $i, 8);
196             }
197              
198 2         2 my $decrypted = eval { $cbc->decrypt($encrypted, hash($key), $iv) };
  2         5  
199 2 50       38 return undef if $@;
200              
201 2         7 return $decrypted;
202             }
203              
204             1;
205              
206             __END__