File Coverage

blib/lib/Crypt/CCM.pm
Criterion Covered Total %
statement 125 133 93.9
branch 13 24 54.1
condition 2 4 50.0
subroutine 17 17 100.0
pod 7 7 100.0
total 164 185 88.6


line stmt bran cond sub pod time code
1             package Crypt::CCM;
2              
3 4     4   140259 use 5.008006;
  4         15  
  4         143  
4 4     4   1252 use strict;
  4         6  
  4         117  
5 4     4   31 use warnings;
  4         9  
  4         104  
6 4     4   19 use Carp;
  4         9  
  4         367  
7 4     4   3800 use POSIX qw(ceil);
  4         30775  
  4         25  
8              
9             require Exporter;
10              
11             our @ISA = qw(Exporter);
12             our %EXPORT_TAGS = ( 'all' => [ qw() ] );
13             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
14             our @EXPORT = qw();
15              
16             our $VERSION = '0.03';
17              
18             require XSLoader;
19             XSLoader::load('Crypt::CCM', $VERSION);
20              
21              
22             sub new {
23 32     32 1 33385 my $class = shift;
24 32         98 my %args = @_;
25 32   50     333 my $self = bless {
      50        
26             cipher => undef,
27             aad => $args{'-aad'} || '',
28             nonce => $args{'-nonce'},
29             taglen => $args{'-tag_length'} || 128/8,
30             }, $class;
31              
32 32         73 $args{'-cipher'} |= 'Crypt::Rijndael';
33 32 50       80 if (exists $args{'-key'}) {
34 32         1847 eval "require $args{'-cipher'}";
35 32 50       109 if ($@) {
36 0         0 croak $@;
37             }
38 32         337 $self->set_cipher($args{'-cipher'}->new($args{'-key'}));
39             }
40              
41 32         107 return $self;
42             }
43              
44              
45             sub set_cipher {
46 32     32 1 43 my $self = shift;
47 32         80 $self->{cipher} = shift;
48             }
49              
50              
51             sub set_nonce {
52 32     32 1 4068 my $self = shift;
53 32         247 $self->{nonce} = shift;
54             }
55             *set_iv = \&set_nonce;
56              
57              
58             sub set_aad {
59 32     32 1 100 my $self = shift;
60 32         364 $self->{aad} = shift;
61             }
62              
63              
64             sub set_tag_length {
65 32     32 1 184 my $self = shift;
66 32         65 $self->{taglen} = shift;
67             }
68              
69              
70             sub encrypt {
71 28     28 1 136 my $self = shift;
72 28         211 my $P = shift;
73 28         41 my $cipher = $self->{cipher};
74 28         245 my $blocksize = $cipher->blocksize;
75              
76             # Step 1. Apply the formatting function to (N, A, P)
77 28         241 my $B = $self->_formatting_NAP($self->{nonce}, $self->{aad}, $P, $self->{taglen});
78             # Step 2. Set Y0 = CHIPk(B0)
79             # Step 3. For i = 1 to r, do Yi = CHIPk(Bi ^ Yi-1)
80 28         63 my $Y = pack 'C*', (0) x $blocksize;
81              
82 28         68 for (my $i = 0; $i < length $B; $i += $blocksize) {
83 4208         18055 $Y = $cipher->encrypt(substr($B, $i, $blocksize) ^ $Y);
84             }
85              
86             # Step 4. Set T = MSBtlen(Yr)
87 28         70 my $T = substr $Y, 0, $self->{taglen};
88             # Step 5. Apply the counter generation function
89 28         59 my $ctr = $self->_generate_counter_block();
90              
91             # Step 6. For j=0 to m, do Sj = CHIPk(CTRj)
92             # Step 7. Set S=Si|S2|...|Sm
93 28         112 my $S0 = $cipher->encrypt($ctr);
94 28         39 my $S = '';
95 28         165 for (my $i = 0; $i <= ceil(length($P) / $blocksize); $i++) {
96 82         170 for (my $j = 15; $j > 0; $j--) {
97 82         167 my $n = unpack 'C', substr $ctr, $j, 1;
98 82         161 substr($ctr, $j, 1) = $n = pack 'C', $n+1;
99 82 50       177 last if $n ne "\0";
100             }
101 82         497 $S .= $cipher->encrypt($ctr);
102             }
103              
104             # Step 8. Return C=(P^MSBplen(S)) | (T ^ MSBtlen(S0))
105 28         81 my $C = ($P ^ substr($S, 0, length $P)). ($T ^ substr($S0, 0, length $T));
106 28         90 return $C;
107             }
108              
109              
110             sub decrypt {
111 4     4 1 20 my $self = shift;
112 4         7 my $C = shift;
113 4         6 my $cipher = $self->{cipher};
114 4         9 my $blocksize = $cipher->blocksize;
115             # Step 1. If Clen <= Tlen, then return Invalid
116 4 50       14 if (length $C <= $self->{taglen}) {
117 0         0 carp 'cipher text is short';
118 0         0 return undef;
119             }
120             # Step 2. Apply the counter generation function
121 4         8 my $ctr = $self->_generate_counter_block();
122             # Step 3. For j=0 to m, do Sj = CIPHk(CTRj);
123             # Step 4. Set S=S1|S2|...|Sm
124 4         16 my $S0 = $cipher->encrypt($ctr);
125 4         6 my $S = '';
126 4         20 for (my $i = 0; $i <= ceil(length($C) / $blocksize); $i++) {
127 12         22 for (my $j = 15; $j > 0; $j--) {
128 12         23 my $n = unpack 'C', substr $ctr, $j, 1;
129 12         23 substr($ctr, $j, 1) = $n = pack 'C', $n+1;
130 12 50       26 last if $n ne "\0";
131             }
132 12         74 $S .= $cipher->encrypt($ctr);
133             }
134             # Step 5. Set P=MSBtlen(C) ^ MSBclen-tlen(S)
135 4         8 my $plen = length($C) - $self->{taglen};
136 4         10 my $P = substr($C, 0, $plen) ^ substr($S, 0, $plen);
137             # Step 6. Set T=LSBtlen(C) ^ MSBtlen(S0)
138 4         12 my $T = substr($C, $self->{taglen}*-1) ^ substr($S0, 0, $self->{taglen});
139             # Step 7.
140 4         13 my $B = $self->_formatting_NAP($self->{nonce}, $self->{aad}, $P, $self->{taglen});
141             # Step 8. Set Y0=CIPHk(B0)
142             # Step 9. For i = 1 to r, do Yj=CHIPk(Bi^Yi-1)
143 4         5 my $Y = pack 'H*', '00000000000000000000000000000000';
144 4         11 for (my $i = 0; $i < length $B; $i += $blocksize) {
145 4112         17373 $Y = $cipher->encrypt(substr($B, $i, $blocksize) ^ $Y);
146             }
147             # Step 10. If T != MSBtlen(Yr), then return INVALID, else return P.
148 4 50       15 if (substr($Y, 0, $self->{taglen}) ne $T) {
149 0         0 carp 'invalid TAG';
150 0         0 return undef;
151             }
152 4         15 return $P;
153             }
154              
155              
156              
157             sub _formatting_NAP {
158 32     32   35 my $self = shift;
159 32         38 my $nonce = shift; # N
160 32         203 my $assoc_data = shift; # A
161 32         38 my $plain_text = shift; # P
162 32         34 my $TAG_LENGTH = shift; # TAG length
163 32         209 my $payload = '';
164              
165             # if (!check_length($TAG_LENGTH, length $plain_text, length $nonce, length $assoc_data)) {
166             # return undef;
167             # }
168 32         257 my $counter_block = $self->_format_header($nonce, length $plain_text, $TAG_LENGTH, length $assoc_data);
169              
170 32         77 $payload = sprintf '%s%s%s',
171             $counter_block,
172             $self->_format_associated_data($assoc_data),
173             $self->_format_payload($plain_text);
174 32         76 return $payload;
175             }
176              
177              
178             sub _format_header {
179 32     32   37 my $self = shift;
180 32         39 my $nonce = shift;
181 32         449 my $payload_len = shift;
182 32         38 my $tag_len = shift;
183 32         30 my $a_len = shift;
184              
185 32         39 my $q_len = 15 - length $nonce;
186             # if (!check_length($tag_len, $q_len, length $nonce, $a_len)) {
187             # croak 'invalid length';
188             # }
189              
190             # Formatting of the Flags Octet in B0
191 32         40 my $flags = pack 'C', 0; # 7 RESERVED;
192 32 50       89 $flags |= pack 'C', ($a_len > 0) ? 0x40 : 0x00; # 6 Adata
193 32         78 $flags |= pack 'C', ((($tag_len - 2) / 2) & 0x07) << 3; # 5..3 [(t-2)/2]3
194 32         50 $flags |= pack 'C', ($q_len - 1) & 0x07; # 2..0 [q-1]3
195              
196 32         73 my $Q = '';
197 32         32 my $l = $payload_len;
198 32         81 for (my $i = 0; $i < $q_len; $i++) {
199 88         136 $Q = pack('C', $l). $Q;
200 88         193 $l >>= 8;
201             }
202              
203 32         86 return $flags. $nonce. $Q;
204             }
205              
206              
207             sub _format_associated_data {
208 32     32   36 my $self = shift;
209 32         31 my $A = shift;
210 32         34 my $payload = '';
211 32         72 my $blocksize = $self->{cipher}->blocksize;
212              
213 32         83 my $a_len = length $A;
214 32 50       62 if ($a_len == 0) {
215 0         0 $payload = '';
216             }
217 32 100       56 if ($a_len <= 0xFEFF) {
    50          
    0          
218 30         58 $payload = pack 'n', $a_len;
219             }
220             elsif ($a_len <= 0xFFFFFFFF) {
221 2         6 $payload = pack 'nN', 0xFFFE, $a_len;
222             }
223             elsif ($a_len < 2**64) {
224 0         0 $payload = pack 'nNN', 0xFFFF, ($a_len >> 32), $a_len;
225             }
226             else {
227 0         0 croak 'invalid a data length';
228             }
229 32         495 return $payload. $A. pack 'C*', (0) x (($blocksize - ($a_len) % $blocksize)-length $payload);
230             }
231              
232              
233             sub _format_payload {
234 32     32   38 my $self = shift;
235 32         42 my $P = shift;
236 32         64 my $blocksize = $self->{cipher}->blocksize;
237              
238 32         32 my $l = length $P;
239 32         35 my $pad = $l % $blocksize;
240 32 100       70 if ($pad > 0) {
241 28         75 $P .= pack 'C*', (0) x ($blocksize - $pad);
242             }
243 32         397 return $P;
244             }
245              
246              
247             sub _generate_counter_block {
248 32     32   42 my $self = shift;
249 32         47 my $nonce = $self->{nonce};
250              
251 32         43 my $q_len = 15 - length $nonce;
252 32         35 my $flags = pack 'C', 0; # 7, 6 RESERVED;
253             # 5, 4, 3 0
254 32         58 $flags |= pack 'C', ($q_len - 1) & 0x07; # 2..0 [q-1]3
255              
256 32         65 my $ctr = pack 'C*', (0) x $q_len;
257 32         83 return $flags. $nonce. $ctr;
258             }
259              
260              
261             1;
262             __END__