File Coverage

blib/lib/DTOne/Crypt.pm
Criterion Covered Total %
statement 65 65 100.0
branch 15 16 93.7
condition n/a
subroutine 15 15 100.0
pod 2 2 100.0
total 97 98 98.9


line stmt bran cond sub pod time code
1             package DTOne::Crypt;
2              
3 1     1   60645 use strict;
  1         2  
  1         22  
4 1     1   17 use 5.008_005;
  1         3  
5             our $VERSION = '0.06';
6              
7             require Exporter;
8             our @ISA = qw(Exporter);
9             our @EXPORT_OK = qw(encrypt_aes256gcm decrypt_aes256gcm);
10              
11 1     1   408 use Crypt::AuthEnc::GCM qw(gcm_encrypt_authenticate gcm_decrypt_verify);
  1         8103  
  1         55  
12 1     1   384 use Crypt::ScryptKDF qw(scrypt_raw);
  1         2357  
  1         50  
13 1     1   486 use Bytes::Random::Secure qw(random_bytes);
  1         7387  
  1         48  
14 1     1   5 use MIME::Base64;
  1         2  
  1         32  
15 1     1   4 use Carp;
  1         2  
  1         37  
16              
17 1     1   5 use constant PROTOCOL_VERSION => 2;
  1         2  
  1         54  
18              
19             # v1 constants
20 1     1   6 use constant SCRYPT_ITERATIONS => 32768; # 2**15
  1         4  
  1         38  
21 1     1   10 use constant SCRYPT_BLOCK_SIZE => 8;
  1         1  
  1         33  
22 1     1   5 use constant SCRYPT_PARALLELISM => 1;
  1         1  
  1         29  
23 1     1   4 use constant SCRYPT_DERIVED_KEY_LEN => 32;
  1         1  
  1         369  
24              
25             sub encrypt_aes256gcm {
26 8     8 1 6519 my $plaintext = shift;
27 8 100       32 my $master_key = shift or croak "master key required";
28              
29 6 100       16 unless (defined $plaintext) {
30 1         25 croak "plaintext data required";
31             }
32              
33 5         17 $master_key = decode_base64($master_key);
34 5 100       12 unless (length($master_key) == 32) {
35 2         14 croak "invalid master key length";
36             }
37              
38 3         20 my $iv = random_bytes(12);
39 3         1242 my ($ciphertext, $tag) = gcm_encrypt_authenticate(
40             'AES',
41             $master_key,
42             $iv,
43             undef,
44             $plaintext
45             );
46              
47 3         18 my $composite = encode_base64(join('', $iv, $tag, $ciphertext), '');
48              
49 3         15 return '$'.join('$', PROTOCOL_VERSION, $composite);
50             }
51              
52             sub decrypt_aes256gcm {
53 10 100   10 1 7707 my $encrypted = shift or croak "encrypted data required";
54 7 100       40 my $master_key = shift or croak "master key required";
55              
56 5         16 $master_key = decode_base64($master_key);
57 5 100       13 unless (length($master_key) == 32) {
58 2         13 croak "invalid master key length";
59             }
60              
61 3         12 my $key;
62             my $iv;
63 3         0 my $tag;
64 3         0 my $ciphertext;
65 3 100       7 if (_is_v2_format($encrypted)) {
66 1         4 (undef, my $version, $encrypted) = split(/\$/, $encrypted);
67 1         4 $encrypted = decode_base64($encrypted);
68              
69 1         5 ($iv, $tag, $ciphertext) = unpack('a12 a16 a*', $encrypted);
70 1         3 $key = $master_key;
71             } else {
72 2         6 $encrypted = decode_base64($encrypted);
73 2         14 (my $salt, $iv, $tag, $ciphertext) = unpack('a16 a12 a16 a*', $encrypted);
74              
75 2         11 $key = scrypt_raw(
76             $master_key,
77             $salt,
78             SCRYPT_ITERATIONS,
79             SCRYPT_BLOCK_SIZE,
80             SCRYPT_PARALLELISM,
81             SCRYPT_DERIVED_KEY_LEN
82             );
83             }
84              
85 3         372332 return gcm_decrypt_verify(
86             'AES',
87             $key,
88             $iv,
89             undef,
90             $ciphertext,
91             $tag
92             );
93             }
94              
95             sub _is_v2_format {
96 3 50   3   8 my $encrypted = shift or croak "encrypted data required";
97 3         11 return substr($encrypted, 0, 1) eq '$';
98             }
99              
100             1;
101             __END__