File Coverage

blib/lib/Crypt/RNCryptor/V3.pm
Criterion Covered Total %
statement 91 97 93.8
branch 10 16 62.5
condition 21 48 43.7
subroutine 18 19 94.7
pod 3 10 30.0
total 143 190 75.2


line stmt bran cond sub pod time code
1             package Crypt::RNCryptor::V3;
2 2     2   1060 use strict;
  2         3  
  2         69  
3 2     2   6 use warnings;
  2         3  
  2         48  
4 2     2   861 use parent 'Crypt::RNCryptor';
  2         503  
  2         7  
5 2     2   94 use Carp;
  2         2  
  2         115  
6 2     2   958 use Crypt::PBKDF2;
  2         880843  
  2         107  
7 2     2   1568 use Crypt::CBC;
  2         8320  
  2         75  
8 2     2   1323 use Digest::SHA qw(hmac_sha256);
  2         6798  
  2         228  
9              
10             use constant {
11 2         298 VERSION => 3,
12             # option
13             OPTION_USE_PASSWORD => 1,
14             OPTION_NOT_USE_PASSWORD => 0,
15             # size
16             VERSION_SIZE => 1,
17             OPTIONS_SIZE => 1,
18             ENCRYPTION_SALT_SIZE => 8,
19             HMAC_SALT_SIZE => 8,
20             IV_SIZE => 16,
21             HMAC_SIZE => 32,
22             # PBKDF2
23             DEFAULT_PBKDF2_ITERATIONS => 10000,
24             PBKDF2_OUTPUT_SIZE => 32,
25 2     2   16 };
  2         3  
26              
27             use Class::Accessor::Lite (
28 2         18 ro => [qw(
29             password pbkdf2_iterations
30             encryption_key hmac_key
31             )],
32 2     2   683 );
  2         1357  
33              
34             sub new {
35 11     11 1 29 my ($class, %opts) = @_;
36 11 50 33     97 if ($opts{password} && ($opts{encryption_key} || $opts{hmac_key})) {
      66        
37 0         0 confess 'Cannot set the "password" option with "encryption_key" or "hmac_key" option.';
38             }
39 11 50       25 if ($opts{pbkdf2_iterations}) {
40 0         0 confess 'v3.1 is not supported still yet.';
41             }
42             bless {
43 11         240 password => $opts{password},
44             encryption_key => $opts{encryption_key},
45             hmac_key => $opts{hmac_key},
46             pbkdf2_iterations => DEFAULT_PBKDF2_ITERATIONS,
47             }, $class;
48             }
49              
50             sub pbkdf2 {
51 29     29 0 552924 my ($self, $password, $salt, $iterations) = @_;
52 29   33     192 $iterations ||= $self->pbkdf2_iterations;
53 29         1069 Crypt::PBKDF2->new(
54             hash_class => 'HMACSHA1',
55             iterations => $iterations,
56             output_len => PBKDF2_OUTPUT_SIZE,
57             )->PBKDF2($salt, $password);
58             }
59              
60             sub aes256cbc {
61 20     20 0 312 my ($self, $encryption_key, $iv) = @_;
62 20         205 Crypt::CBC->new(
63             -literal_key => 1,
64             -key => $encryption_key,
65             -iv => $iv,
66             -header => 'none',
67             -cipher => 'Crypt::OpenSSL::AES',
68             );
69             }
70              
71             sub make_options {
72 0     0 0 0 my ($self, $use_password, $pbkdf2_iterations) = @_;
73 0         0 confess 'TODO';
74             }
75              
76             sub encrypt {
77 10     10 1 431 my $self = shift;
78 10 100       27 return $self->encrypt_with_password(@_) if $self->password;
79 4 50 33     29 return $self->encrypt_with_keys(@_) if $self->encryption_key && $self->hmac_key;
80 0         0 confess 'Cannot encrypt.';
81             }
82              
83             sub encrypt_with_password {
84 6     6 0 80 my ($self, $plaintext, %opts) = @_;
85 6   66     25 my $iv = $opts{iv} || Crypt::CBC->random_bytes(IV_SIZE);
86 6   66     780 my $encryption_salt = $opts{encryption_salt} || Crypt::CBC->random_bytes(ENCRYPTION_SALT_SIZE);
87 6   66     776 my $hmac_salt = $opts{hmac_salt} || Crypt::CBC->random_bytes(HMAC_SALT_SIZE);
88 6   33     788 my $password = $opts{password} || $self->password;
89 6   33     54 my $pbkdf2_iterations = $opts{pbkdf2_iterations} || $self->pbkdf2_iterations;
90              
91 6         38 my $encryption_key = $self->pbkdf2($password, $encryption_salt);
92 6         764488 my $hmac_key = $self->pbkdf2($password, $hmac_salt);
93              
94             # Header = 3 || 1 || EncryptionSalt || HMACSalt || IV
95 6         737946 my $header = pack('CCa*a*a*', VERSION, OPTION_USE_PASSWORD, $encryption_salt, $hmac_salt, $iv);
96             # Ciphertext = AES256(plaintext, ModeCBC, IV, EncryptionKey)
97 6         370 my $ciphertext = $self->aes256cbc($encryption_key, $iv)->encrypt($plaintext);
98 6         3367 my $cipherdata = pack('a*a*', $header, $ciphertext);
99             # HMAC = HMAC(Header || Ciphertext, HMACKey, SHA-256)
100 6         74 my $hmac = hmac_sha256($cipherdata, $hmac_key);
101             # Message = Header || Ciphertext || HMAC
102 6         60 pack('a*a*', $cipherdata, $hmac);
103             }
104              
105             sub encrypt_with_keys {
106 4     4 0 49 my ($self, $plaintext, %opts) = @_;
107 4   66     17 my $iv = $opts{iv} || Crypt::CBC->random_bytes(IV_SIZE);
108 4   33     847 my $encryption_key = $opts{encryption_key} || $self->encryption_key;
109 4   33     28 my $hmac_key = $opts{hmac_key} || $self->hmac_key;
110             # Header = 3 || 0 || IV
111 4         24 my $header = pack('CCa*', VERSION, OPTION_NOT_USE_PASSWORD, $iv);
112             # Ciphertext = AES256(plaintext, ModeCBC, IV, EncryptionKey)
113 4         9 my $ciphertext = $self->aes256cbc($encryption_key, $iv)->encrypt($plaintext);
114 4         645 my $cipherdata = pack('a*a*', $header, $ciphertext);
115             # HMAC = HMAC(Header || Ciphertext, HMACKey, SHA-256)
116 4         46 my $hmac = hmac_sha256($cipherdata, $hmac_key);
117             # Message = Header || Ciphertext || HMAC
118 4         23 pack('a*a*', $cipherdata, $hmac);
119             }
120              
121             sub decrypt {
122 10     10 1 18 my $self = shift;
123 10 100       36 return $self->decrypt_with_password(@_) if $self->password;
124 4 50 33     28 return $self->decrypt_with_keys(@_) if $self->encryption_key && $self->hmac_key;
125 0         0 confess 'Cannot decrypt.';
126             }
127              
128             sub decrypt_with_password {
129 6     6 0 75 my ($self, $message, %opts) = @_;
130 6   33     29 my $password = $opts{password} || $self->password;
131 6         40 my ($header, $ciphertext, $hmac) = do {
132 6         7 my $header_size = VERSION_SIZE + OPTIONS_SIZE + ENCRYPTION_SALT_SIZE + HMAC_SALT_SIZE + IV_SIZE;
133 6         33 my $fmt = sprintf('a%da%da%d',
134             $header_size,
135             length($message) - $header_size - HMAC_SIZE,
136             HMAC_SIZE);
137 6         38 unpack($fmt, $message);
138             };
139 6         11 my ($version, $options, $encryption_salt, $hmac_salt, $iv) = do {
140 6         8 my $fmt = sprintf('CCa%da%da%d', ENCRYPTION_SALT_SIZE, HMAC_SALT_SIZE, IV_SIZE);
141 6         24 unpack($fmt, $header);
142             };
143              
144             # compare HMAC
145 6         19 my $hmac_key = $self->pbkdf2($password, $hmac_salt);
146 6         792184 my $computed_hmac = hmac_sha256(pack('a*a*', $header, $ciphertext), $hmac_key);
147 6 50       382 die "HMAC is not matched.\n" unless $computed_hmac eq $hmac;
148              
149             # decrypt
150 6         21 my $encryption_key = $self->pbkdf2($password, $encryption_salt);
151 6         767122 $self->aes256cbc($encryption_key, $iv)->decrypt($ciphertext);
152             }
153              
154             sub decrypt_with_keys {
155 4     4 0 47 my ($self, $message, %opts) = @_;
156 4   33     17 my $encryption_key = $opts{encryption_key} || $self->encryption_key;
157 4   33     29 my $hmac_key = $opts{hmac_key} || $self->hmac_key;
158              
159 4         22 my ($header, $ciphertext, $hmac) = do {
160 4         6 my $header_size = VERSION_SIZE + OPTIONS_SIZE + IV_SIZE;
161 4         18 my $fmt = sprintf('a%da%da%d',
162             $header_size,
163             length($message) - $header_size - HMAC_SIZE,
164             HMAC_SIZE);
165 4         20 unpack($fmt, $message);
166             };
167 4         4 my ($version, $options, $iv) = do {
168 4         4 my $fmt = sprintf('CCa%d', IV_SIZE);
169 4         11 unpack($fmt, $header);
170             };
171              
172             # compare HMAC
173 4         45 my $computed_hmac = hmac_sha256(pack('a*a*', $header, $ciphertext), $hmac_key);
174 4 50       12 die "HMAC is not matched.\n" unless $computed_hmac eq $hmac;
175              
176             # Plaintext = AES256Decrypt(Ciphertext, ModeCBC, IV, EncryptionKey)
177 4         9 $self->aes256cbc($encryption_key, $iv)->decrypt($ciphertext);
178             }
179              
180             1;
181              
182             __END__
183              
184             =encoding utf-8
185              
186             =head1 NAME
187              
188             Crypt::RNCryptor::V3 - Implementation of RNCyrptor v3.
189              
190             =head1 SYNOPSIS
191              
192             use Crypt::RNCryptor::V3;
193              
194             # generate password-based encryptor
195             $cryptor = Crypt::RNCryptor::V3->new(
196             password => 'secret password',
197             );
198              
199             # generate key-based encryptor
200             $cryptor = Crypt::RNCryptor::V3->new(
201             encryption_key => '',
202             hmac_key => '',
203             );
204              
205             # encrypt
206             $ciphertext = $cryptor->encrypt('plaintext');
207              
208             # decrypt
209             $plaintext = $cryptor->decrypt($ciphertext);
210              
211             =head1 METHODS
212              
213             =head2 CLASS METHODS
214              
215             =over 4
216              
217             =item my $cryptor = Crypt::RNCryptor->new(%opts);
218              
219             Create a cryptor instance.
220              
221             %opts = (
222             password => 'any length password',
223             pbkdf2_iterations => DEFAULT_PBKDF2_ITERATIONS,
224             # or
225             encryption_key => '32 length key',
226             hmac_key => '32 length key',
227             );
228              
229             =back
230              
231             =head2 INSTANCE METHODS
232              
233             =over 4
234              
235             =item $ciphertext = $cryptor->encrypt($plaintext)
236              
237             Encrypt plaintext with options.
238              
239             =item $plaintext = $cryptor->decrypt($ciphertext)
240              
241             Decrypt ciphertext with options.
242              
243             =back
244              
245             =head1 LICENSE
246              
247             Copyright (C) Shintaro Seki.
248              
249             This library is free software; you can redistribute it and/or modify
250             it under the same terms as Perl itself.
251              
252             =head1 AUTHOR
253              
254             Shintaro Seki E<lt>s2pch.luck@gmail.comE<gt>
255              
256             =head1 SEE ALSO
257              
258             L<RNCryptor-Spec-v3|https://github.com/RNCryptor/RNCryptor-Spec/blob/master/RNCryptor-Spec-v3.md>,
259             L<RNCryptor-Spec-v3.1 Draft|https://github.com/RNCryptor/RNCryptor-Spec/blob/master/draft-RNCryptor-Spec-v3.1.md>
260              
261             =cut