File Coverage

blib/lib/Algorithm/IRCSRP2.pm
Criterion Covered Total %
statement 2 4 50.0
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 4 6 66.6


line stmt bran cond sub pod time code
1             package Algorithm::IRCSRP2;
2              
3             BEGIN {
4 1     1   23700 $Algorithm::IRCSRP2::VERSION = '0.501';
5             }
6              
7             # ABSTRACT: IRC channel encryption algorithm
8              
9 1     1   492 use Moose;
  0            
  0            
10              
11             # core
12             use Data::Dumper;
13             use Digest::SHA;
14             use MIME::Base64;
15             use Math::BigInt only => 'GMP,Pari';
16              
17             # CPAN
18             use Crypt::OpenSSL::AES;
19              
20             # local
21             use Algorithm::IRCSRP2::Utils qw(:all);
22              
23             has 'error' => (
24             'isa' => 'Str',
25             'is' => 'rw',
26             );
27              
28             has 'nickname' => (
29             'isa' => 'Str',
30             'is' => 'rw',
31             'default' => 'unknown'
32             );
33              
34             has 'debug_cb' => (
35             'isa' => 'CodeRef',
36             'is' => 'rw',
37             'default' => sub {
38             sub {
39             my @args = @_;
40             @args = grep { defined($_) } @args;
41             print(@args);
42             }
43             }
44             );
45              
46             has '_orig_debug_cb' => (
47             'isa' => 'CodeRef',
48             'is' => 'rw',
49             'default' => sub {
50             sub {
51             }
52             }
53             );
54              
55             has 'am_i_dave' => (
56             'isa' => 'Bool',
57             'is' => 'ro',
58             );
59              
60             has 'cbc_blocksize' => (
61             'isa' => 'Int',
62             'is' => 'ro',
63             'default' => 16
64             );
65              
66             # -------- methods --------
67             sub BUILD {
68             my ($self) = @_;
69              
70             my $orig_cb = $self->debug_cb;
71              
72             $self->_orig_debug_cb($orig_cb);
73              
74             my $new_cb = sub {
75             my $str = join('', @_);
76             $str = (($self->am_i_dave) ? 'Dave: ' : 'Alice: ') . $self->nickname . ' ' . $str;
77             return $orig_cb->($str);
78             };
79              
80             $self->debug_cb($new_cb);
81              
82             return;
83             }
84              
85             sub init {
86             my ($self) = @_;
87              
88             my $s = urandom(32);
89             my $x = bytes2int(H($s . $self->I() . $self->P()));
90              
91             $self->s($s);
92             $self->v(Math::BigInt->new(g())->copy->bmodpow($x->bstr, N()));
93              
94             return $self->state('init');
95             }
96              
97             sub cbc_decrypt {
98             my ($self, $data) = @_;
99              
100             my $blocksize = $self->cbc_blocksize();
101              
102             die('length($data) % $blocksize != 0') unless (length($data) % $blocksize == 0);
103              
104             my $IV = substr($data, 0, $blocksize);
105             $data = substr($data, $blocksize);
106              
107             my $plaintext = '';
108              
109             foreach (@{[ 0 .. (length($data) / $blocksize) - 1 ]}) {
110             my $temp = $self->cipher->decrypt(substr($data, 0, $blocksize));
111             my $temp2 = xorstring($temp, $IV, $blocksize);
112             $plaintext .= $temp2;
113             $IV = substr($data, 0, $blocksize);
114             $data = substr($data, $blocksize);
115             }
116              
117             return $plaintext;
118             }
119              
120             sub cbc_encrypt {
121             my ($self, $data) = @_;
122              
123             my $blocksize = $self->cbc_blocksize();
124              
125             die('length($data) % $blocksize != 0') unless (length($data) % $blocksize == 0);
126              
127             my $IV = urandom($blocksize);
128             die('len(IV) == blocksize') unless (length($IV) == $blocksize);
129              
130             my $ciphertext = $IV;
131              
132             foreach (@{[ 0 .. (length($data) / $blocksize) - 1 ]}) {
133             my $xored = xorstring($data, $IV, $blocksize);
134             my $enc = $self->cipher->encrypt($xored);
135              
136             $ciphertext .= $enc;
137             $IV = $enc;
138             $data = substr($data, $blocksize);
139             }
140              
141             die('len(ciphertext) % blocksize == 0') unless (length($ciphertext) % $blocksize == 0);
142              
143             return $ciphertext;
144             }
145              
146             sub decrypt_message {
147             my ($self, $msg) = @_;
148              
149             substr($msg, 0, 1, '');
150              
151             my $raw = MIME::Base64::decode_base64($msg);
152              
153             my $cmac = substr($raw, 0, 16);
154             my $ctext = substr($raw, 16);
155              
156             if ($cmac ne hmac_sha256_128($self->mac_key, $ctext)) {
157             die("decrypt_message: wrong mac!\n");
158             }
159              
160             my $padded = $self->cbc_decrypt($ctext);
161              
162             my $plain = $padded;
163             $plain =~ s/^\x00*//;
164             $plain =~ s/\x00*$//;
165              
166             unless (substr($plain, 0, 1) eq 'M') {
167             die("decrypt_message: not M\n");
168             }
169              
170             my $usernamelen = ord(substr($plain, 1, 2));
171             my $username = substr($plain, 2, $usernamelen);
172              
173             $msg = substr($plain, 4 + 2 + $usernamelen);
174              
175             if ($msg =~ /^\xffKEY/) {
176              
177             my $new = substr($msg, 4);
178              
179             if (length($new) != (32 + 32)) {
180             die('decrypt_message: length($new) != 32 + 32 ; length is ' . length($new));
181             }
182              
183             $self->debug_cb->('decrypt_message: rekeying');
184              
185             $self->session_key(substr($new, 0, 32));
186             $self->mac_key(substr($new, 32, 32));
187             $self->cipher(Crypt::OpenSSL::AES->new($self->session_key));
188              
189             return;
190             }
191              
192             $self->debug_cb->("decrypt_message: from $username ; msg $msg");
193              
194             return $msg;
195             }
196              
197             sub encrypt_message {
198             my ($self, $who, $msg) = @_;
199              
200             my $times = pack('L>', int(time()));
201              
202             # info = len(username) || username || timestamp
203             my $infos = chr(length($who)) . $who . $times;
204              
205             # ctext = IV || AES-CBC(sessionkey, IV, "M" || info || plaintext)
206             my $ctext = $self->cbc_encrypt(padto('M' . $infos . $msg, 16));
207              
208             # cmac = HM(mackey, ctext)
209             my $cmac = hmac_sha256_128($self->mac_key, $ctext);
210              
211             # ircmessage = "*" || Base64(cmac || ctext)
212             return '*' . MIME::Base64::encode_base64($cmac . $ctext, '');
213             }
214              
215             no Moose;
216              
217             __PACKAGE__->meta->make_immutable;
218              
219             1;
220              
221             __END__
222              
223             =pod
224              
225             =head1 NAME
226              
227             Algorithm::IRCSRP2 - IRC channel encryption algorithm
228              
229             =head1 VERSION
230              
231             version 0.501
232              
233             =head1 DESCRIPTION
234              
235             L<Algorithm::IRCSRP2> implements the IRCSRP version 2 algorithm as specified in
236             L<http://www.bjrn.se/ircsrp/ircsrp.2.0.txt>.
237              
238             From the specification:
239              
240             IRCSRP is based on the SRP-6 protocol [3] for password-authenticated key
241             agreement. While SRP was originally designed for establishing a secure,
242             authenticated channel between a user and a host, it can be adapted for group
243             communcations, as described in this document.
244              
245             See L<https://gitorious.org/ircsrp/ircsrp> for a working version used in Pidgin.
246              
247             =head1 CURRENT CAVEATS
248              
249             =over
250              
251             =item * Only Alice is implemented (initial Dave started)
252              
253             =back
254              
255             =head1 ATTRIBUTES
256              
257             =head2 Optional Attributes
258              
259             =over
260              
261             =item * B<am_i_dave> (ro, Bool) - Child class will set this.
262              
263             =item * B<cbc_blocksize> (ro, Int) - CBC blocksize. Defaults to '16'.
264              
265             =item * B<debug_cb> (rw, CodeRef) - Debug callback. Defaults to C<print()>
266              
267             =item * B<error> (rw, Str) - If set, there was an error.
268              
269             =item * B<nickname> (rw, Str) - Child class will set this. Defaults to 'unknown'.
270              
271             =back
272              
273             =head1 PUBLIC API METHODS
274              
275             =over
276              
277             =item * B<init()> - Setup object for key exchange.
278              
279             =item * B<encrypt_message($msg, $who)> - Returns encrypted message with
280             plaintext C<$msg> from nickname C<$who>.
281              
282             =item * B<decrypt_message($msg)> - Returns decrypted text from encrypted
283             C<$msg>. C<die()>s on errors.
284              
285             =back
286              
287             =head1 SEE ALSO
288              
289             =over
290              
291             =item * L<http://www.bjrn.se/ircsrp/>
292              
293             =item * See L<https://gitorious.org/ircsrp/ircsrp> for a working version used in
294             Pidgin.
295              
296             =back
297              
298             =head1 AUTHOR
299              
300             Adam Flott <adam@npjh.com>
301              
302             =head1 COPYRIGHT AND LICENSE
303              
304             This software is copyright (c) 2011 by Adam Flott.
305              
306             This is free software; you can redistribute it and/or modify it under
307             the same terms as the Perl 5 programming language system itself.
308              
309             =cut