Codebase list libcryptx-perl / f1e9116
added CCM OO interface: new-add-add-done Karel Miko 6 years ago
5 changed file(s) with 296 addition(s) and 14 deletion(s). Raw diff Collapse all Expand all
00 Changes for CryptX
1
2 TODO:
3 - XS croaks should report the "real caller" (Crypt::Mac::*, Crypt::Mode::*, ...)
4 - CCM interface new-add-add-done
51
62 0.054_* 2017-10-XX
73 - new Crypt::Cipher::IDEA
84 - new Crypt::Cipher::Serpent
95 - new Crypt::Stream::Salsa20
6 - added CCM OO interface: new-add-add-done
107
118 0.054 2017-10-12
129 - fix Crypt::PK::DSA verify
3232
3333 typedef struct ccm_struct { /* used by Crypt::AuthEnc::CCM */
3434 ccm_state state;
35 int direction;
36 int tag_len;
37 unsigned long pt_len;
3538 int id;
3639 } *Crypt__AuthEnc__CCM;
3740
9191 XPUSHs(sv_2mortal(pt));
9292 }
9393 }
94
95 Crypt::AuthEnc::CCM
96 _new(char * cipher_name, SV * key, SV * nonce, SV * adata, int tag_len, int pt_len)
97 CODE:
98 {
99 unsigned char *k=NULL;
100 STRLEN k_len=0;
101 unsigned char *n=NULL;
102 STRLEN n_len=0;
103 unsigned char *h=NULL;
104 STRLEN h_len=0;
105 int rv, id;
106
107 if (tag_len < 1 || tag_len > MAXBLOCKSIZE) croak("FATAL: invalid tag_len %d", tag_len);
108 if (pt_len < 0) croak("FATAL: invalid pt_len");
109 if (!SvPOK(key)) croak("FATAL: key must be string/buffer scalar");
110 k = (unsigned char *) SvPVbyte(key, k_len);
111 if (!SvPOK(nonce)) croak("FATAL: nonce must be string/buffer scalar");
112 n = (unsigned char *) SvPVbyte(nonce, n_len);
113 if (!SvPOK(adata)) croak("FATAL: adata must be string/buffer scalar");
114 h = (unsigned char *) SvPVbyte(adata, h_len);
115
116 id = find_cipher(cipher_name);
117 if (id == -1) croak("FATAL: find_cipfer failed for '%s'", cipher_name);
118
119 Newz(0, RETVAL, 1, struct ccm_struct);
120 if (!RETVAL) croak("FATAL: Newz failed");
121
122 rv = ccm_init(&RETVAL->state, id, k, (unsigned long)k_len, pt_len, tag_len, h_len);
123 if (rv != CRYPT_OK) {
124 Safefree(RETVAL);
125 croak("FATAL: ccm_init failed: %s", error_to_string(rv));
126 }
127 rv = ccm_add_nonce(&RETVAL->state, n, (unsigned long)n_len);
128 if (rv != CRYPT_OK) {
129 Safefree(RETVAL);
130 croak("FATAL: ccm_add_nonce failed: %s", error_to_string(rv));
131 }
132 rv = ccm_add_aad(&RETVAL->state, h, (unsigned long)h_len);
133 if (rv != CRYPT_OK) {
134 Safefree(RETVAL);
135 croak("FATAL: ccm_add_aad failed: %s", error_to_string(rv));
136 }
137 RETVAL->direction = -1;
138 RETVAL->tag_len = tag_len;
139 RETVAL->pt_len = pt_len;
140 }
141 OUTPUT:
142 RETVAL
143
144 void
145 DESTROY(Crypt::AuthEnc::CCM self)
146 CODE:
147 Safefree(self);
148
149 Crypt::AuthEnc::CCM
150 clone(Crypt::AuthEnc::CCM self)
151 CODE:
152 Newz(0, RETVAL, 1, struct ccm_struct);
153 if (!RETVAL) croak("FATAL: Newz failed");
154 Copy(&self->state, &RETVAL->state, 1, struct ccm_struct);
155 OUTPUT:
156 RETVAL
157
158 SV *
159 encrypt_add(Crypt::AuthEnc::CCM self, SV * data)
160 CODE:
161 {
162 int rv;
163 STRLEN in_data_len;
164 unsigned char *in_data, *out_data;
165
166 in_data = (unsigned char *)SvPVbyte(data, in_data_len);
167 if (in_data_len == 0) {
168 RETVAL = newSVpvn("", 0);
169 }
170 else {
171 if (self->direction == -1) self->direction = CCM_ENCRYPT;
172 if (self->direction != CCM_ENCRYPT) {
173 croak("FATAL: encrypt_add failed: wrong direction");
174 }
175 if (self->pt_len < in_data_len) croak("FATAL: encrypt_add failed: pt_len mismatch");
176 RETVAL = NEWSV(0, in_data_len);
177 SvPOK_only(RETVAL);
178 SvCUR_set(RETVAL, in_data_len);
179 out_data = (unsigned char *)SvPVX(RETVAL);
180 rv = ccm_process(&self->state, in_data, (unsigned long)in_data_len, out_data, self->direction);
181 if (rv != CRYPT_OK) {
182 SvREFCNT_dec(RETVAL);
183 croak("FATAL: ccm_process failed: %s", error_to_string(rv));
184 }
185 self->pt_len -= in_data_len;
186 }
187 }
188 OUTPUT:
189 RETVAL
190
191 SV *
192 decrypt_add(Crypt::AuthEnc::CCM self, SV * data)
193 CODE:
194 {
195 int rv, i;
196 STRLEN in_data_len;
197 unsigned char *in_data, *out_data;
198
199 in_data = (unsigned char *)SvPVbyte(data, in_data_len);
200 if (in_data_len == 0) {
201 RETVAL = newSVpvn("", 0);
202 }
203 else {
204 if (self->direction == -1) self->direction = CCM_DECRYPT;
205 if (self->direction != CCM_DECRYPT) {
206 croak("FATAL: decrypt_add failed: wrong direction");
207 }
208 if (self->pt_len < in_data_len) croak("FATAL: decrypt_add failed: pt_len mismatch");
209 RETVAL = NEWSV(0, in_data_len);
210 SvPOK_only(RETVAL);
211 SvCUR_set(RETVAL, in_data_len);
212 out_data = (unsigned char *)SvPVX(RETVAL);
213 rv = ccm_process(&self->state, out_data, (unsigned long)in_data_len, in_data, CCM_DECRYPT);
214 if (rv != CRYPT_OK) {
215 SvREFCNT_dec(RETVAL);
216 croak("FATAL: ccm_process failed: %s", error_to_string(rv));
217 }
218 self->pt_len -= in_data_len;
219 }
220 }
221 OUTPUT:
222 RETVAL
223
224 void
225 encrypt_done(Crypt::AuthEnc::CCM self)
226 PPCODE:
227 {
228 int rv;
229 unsigned char tag[MAXBLOCKSIZE];
230 unsigned long tag_len = self->tag_len;
231
232 if (self->direction != CCM_ENCRYPT) {
233 croak("FATAL: encrypt_done failed: wrong direction");
234 }
235 if (self->pt_len != 0) croak("FATAL: encrypt_done failed: pt_len mismatch");
236 rv = ccm_done(&self->state, tag, &tag_len);
237 if (rv != CRYPT_OK) croak("FATAL: ccm_done failed: %s", error_to_string(rv));
238 XPUSHs(sv_2mortal(newSVpvn((char*)tag, tag_len)));
239 }
240
241 void
242 decrypt_done(Crypt::AuthEnc::CCM self, ...)
243 PPCODE:
244 {
245 int rv;
246 unsigned char tag[MAXBLOCKSIZE];
247 unsigned long tag_len = self->tag_len;
248 STRLEN expected_tag_len;
249 unsigned char *expected_tag;
250
251 if (self->direction != CCM_DECRYPT) {
252 croak("FATAL: decrypt_done failed: wrong direction");
253 }
254 if (self->pt_len != 0) croak("FATAL: decrypt_done failed: pt_len mismatch");
255 rv = ccm_done(&self->state, tag, &tag_len);
256 if (rv != CRYPT_OK) croak("FATAL: ccm_done failed: %s", error_to_string(rv));
257 if (items == 1) {
258 XPUSHs(sv_2mortal(newSVpvn((char*)tag, tag_len)));
259 }
260 else {
261 if (!SvPOK(ST(1))) croak("FATAL: expected_tag must be string/buffer scalar");
262 expected_tag = (unsigned char *) SvPVbyte(ST(1), expected_tag_len);
263 if (expected_tag_len!=tag_len) {
264 XPUSHs(sv_2mortal(newSViv(0))); /* false */
265 }
266 else if (memNE(expected_tag, tag, tag_len)) {
267 XPUSHs(sv_2mortal(newSViv(0))); /* false */
268 }
269 else {
270 XPUSHs(sv_2mortal(newSViv(1))); /* true */
271 }
272 }
273 }
1111 use CryptX;
1212 use Crypt::Cipher;
1313
14 ### the following functions are implemented in XS:
15 # - _memory_encrypt
16 # - _memory_decrypt
14 sub new {
15 my ($class, $cipher, $key, $iv, $adata, $tag_len, $pt_len) = @_;
16 return _new(Crypt::Cipher::_trans_cipher_name($cipher), $key, $iv, $adata, $tag_len, $pt_len);
17 }
1718
1819 sub ccm_encrypt_authenticate {
1920 my $cipher_name = shift;
2021 my $key = shift;
21 my $nonce = shift;
22 my $iv = shift;
2223 my $adata = shift;
2324 my $tag_len = shift;
2425 my $plaintext = shift;
25 return _memory_encrypt(Crypt::Cipher::_trans_cipher_name($cipher_name), $key, $nonce, $adata, $tag_len, $plaintext);
26
27 $iv = "" if !defined $iv;
28 $adata = "" if !defined $adata;
29 $plaintext = "" if !defined $plaintext;
30
31 return _memory_encrypt(Crypt::Cipher::_trans_cipher_name($cipher_name), $key, $iv, $adata, $tag_len, $plaintext);
32 #my $m = Crypt::AuthEnc::CCM->new($cipher_name, $key, $iv, $adata, $tag_len, length($plaintext));
33 #my $ct = $m->encrypt_add($plaintext);
34 #my $tag = $m->encrypt_done();
35 #return ($ct, $tag);
2636 }
2737
2838 sub ccm_decrypt_verify {
2939 my $cipher_name = shift;
3040 my $key = shift;
31 my $nonce = shift;
41 my $iv = shift;
3242 my $adata = shift;
3343 my $ciphertext = shift;
3444 my $tag = shift;
35 return _memory_decrypt(Crypt::Cipher::_trans_cipher_name($cipher_name), $key, $nonce, $adata, $ciphertext, $tag);
45
46 $iv = "" if !defined $iv;
47 $adata = "" if !defined $adata;
48 $ciphertext = "" if !defined $ciphertext;
49
50 return _memory_decrypt(Crypt::Cipher::_trans_cipher_name($cipher_name), $key, $iv, $adata, $ciphertext, $tag);
51 #my $m = Crypt::AuthEnc::CCM->new($cipher_name, $key, $iv, $adata, length($tag), length($ciphertext));
52 #my $pt = $m->decrypt_add($ciphertext);
53 #return $m->decrypt_done($tag) ? $pt : undef;
3654 }
3755
3856 1;
4563
4664 =head1 SYNOPSIS
4765
66 ### OO interface
67 use Crypt::AuthEnc::CCM;
68
69 # encrypt and authenticate
70 $ae = Crypt::AuthEnc::CCM->new("AES", $key, $iv, $adata, $tag_len, $pt_len);
71 $ct = $ae->encrypt_add('data1');
72 $ct .= $ae->encrypt_add('data2');
73 $ct .= $ae->encrypt_add('data3');
74 $tag = $ae->encrypt_done();
75
76 # decrypt and verify
77 $ae = Crypt::AuthEnc::CCM->new("AES", $key, $iv, $adata, $tag_len, $pt_len);
78 $pt = $ae->decrypt_add('ciphertext1');
79 $pt .= $ae->decrypt_add('ciphertext2');
80 $pt .= $ae->decrypt_add('ciphertext3');
81 $tag = $ae->decrypt_done();
82 die "decrypt failed" unless $tag eq $expected_tag;
83
84 #or
85 $result = $ae->decrypt_done($expected_tag); # 0 or 1
86
4887 ### functional interface
4988 use Crypt::AuthEnc::CCM qw(ccm_encrypt_authenticate ccm_decrypt_verify);
5089
51 my ($ciphertext, $tag) = ccm_encrypt_authenticate('AES', $key, $nonce, $adata, $tag_len, $plaintext);
52 my $plaintext = ccm_decrypt_verify('AES', $key, $nonce, $adata, $ciphertext, $tag);
90 ($ciphertext, $tag) = ccm_encrypt_authenticate('AES', $key, $nonce, $adata, $tag_len, $plaintext);
91 $plaintext = ccm_decrypt_verify('AES', $key, $nonce, $adata, $ciphertext, $tag);
5392
5493 =head1 DESCRIPTION
5594
88127
89128 # on error returns undef
90129
130 =head1 METHODS
131
132 =head2 new
133
134 my $ae = Crypt::AuthEnc::CCM->new($cipher, $key, $nonce, $adata, $tag_len, $pt_len);
135
136 # $cipher .. 'AES' or name of any other cipher with 16-byte block len
137 # $key ..... key of proper length (e.g. 128/192/256bits for AES)
138 # $nonce ... unique nonce/salt (no need to keep it secret)
139 # $adata ... additional authenticated data
140 # $tag_len . required length of output tag
141 # $pt_len .. expected length of plaintext/ciphertext to encrypt/decrypt
142
143 =head2 encrypt_add
144
145 $ciphertext = $ae->encrypt_add($data); #can be called multiple times
146
147 =head2 encrypt_done
148
149 $tag = $ae->encrypt_done();
150
151 =head2 decrypt_add
152
153 $plaintext = $ae->decrypt_add($ciphertext); #can be called multiple times
154
155 =head2 decrypt_done
156
157 my $result = $ae->decrypt_done($tag); # returns 1 (success) or 0 (failure)
158 #or
159 my $tag = $ae->decrypt_done; # returns $tag value
160
161 =head2 clone
162
163 my $ae_new = $ae->clone;
164
91165 =head1 SEE ALSO
92166
93167 =over
00 use strict;
11 use warnings;
22
3 use Test::More tests => 6;
3 use Test::More tests => 12;
44
55 use Crypt::AuthEnc::CCM qw( ccm_encrypt_authenticate ccm_decrypt_verify );
66
77 my $nonce = "random-nonce";
88 my $key = "12345678901234561234567890123456";
9
10 {
11 my $pt = "plain_half";
12 my $ct;
13
14 my $m1 = Crypt::AuthEnc::CCM->new("AES", $key, $nonce, "abc", 16, 20);
15 $ct = $m1->encrypt_add($pt);
16 $ct .= $m1->encrypt_add($pt);
17 my $tag = $m1->encrypt_done;
18
19 is(unpack('H*', $ct), "96b0114ff47da72e92631aadce84f203a8168b20", "enc: ciphertext");
20 is(unpack('H*', $tag), "fdc41ec07673ec132f1910ba771b9530", "enc: tag");
21
22 my $d1 = Crypt::AuthEnc::CCM->new("AES", $key, $nonce, "abc", 16, 20);
23 my $pt2 = $d1->decrypt_add($ct);
24 my $tag2 = $d1->decrypt_done();
25
26 is($pt2, "plain_halfplain_half", "dec1: plaintext");
27 is(unpack('H*', $tag2), "fdc41ec07673ec132f1910ba771b9530", "dec1: tag");
28
29 my $d2 = Crypt::AuthEnc::CCM->new("AES", $key, $nonce, "abc", 16, 20);
30 my $pt3;
31 $pt3 .= $d2->decrypt_add(substr($ct,$_-1,1)) for (1..length($ct));
32 my $tag3 = $d2->decrypt_done();
33
34 is($pt3, "plain_halfplain_half", "dec2: plaintext");
35 is(unpack('H*', $tag3), "fdc41ec07673ec132f1910ba771b9530", "dec2: tag");
36 }
937
1038 {
1139 my ($ct, $tag) = ccm_encrypt_authenticate('AES', $key, $nonce, "header-abc", 16, "plain_halfplain_half");