File Coverage

blib/lib/Crypt/Fernet.pm
Criterion Covered Total %
statement 10 16 62.5
branch n/a
condition n/a
subroutine 4 8 50.0
pod n/a
total 14 24 58.3


line stmt bran cond sub pod time code
1             package Crypt::Fernet;
2              
3 1     1   45270 use 5.018002;
  1         4  
  1         32  
4 1     1   6 use strict;
  1         2  
  1         39  
5 1     1   5 use warnings;
  1         7  
  1         273  
6              
7             require Exporter;
8              
9             our @ISA = qw(Exporter);
10              
11             # Items to export into callers namespace by default. Note: do not export
12             # names by default without a very good reason. Use EXPORT_OK instead.
13             # Do not simply export all your public functions/methods/constants.
14              
15             # This allows declaration use Crypt::Fernet ':all';
16             # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
17             # will save memory.
18             our %EXPORT_TAGS = ( 'all' => [ qw(
19             fernet_genkey fernet_encrypt fernet_verify fernet_decrypt
20             ) ] );
21              
22             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
23              
24             our @EXPORT = qw(
25            
26             );
27              
28             our $VERSION = '0.02';
29              
30             our $FERNET_TOKEN_VERSION = pack("H*", '80');
31              
32              
33             # Preloaded methods go here.
34              
35 0     0     sub fernet_genkey { Crypt::Fernet::generate_key() }
36 0     0     sub fernet_encrypt { Crypt::Fernet::encrypt(@_) }
37 0     0     sub fernet_verify { Crypt::Fernet::verify(@_) }
38 0     0     sub fernet_decrypt { Crypt::Fernet::decrypt(@_) }
39              
40              
41 1     1   760 use Crypt::CBC;
  0            
  0            
42             use Digest::SHA qw(hmac_sha256);
43             use MIME::Base64::URLSafe;
44              
45             sub generate_key {
46             return _urlsafe_pading_base64_encode(Crypt::CBC->random_bytes(32));
47             }
48              
49             sub encrypt {
50             my ($key, $data) = @_;
51             my $b64decode_key = urlsafe_b64decode($key);
52             my $signkey = substr $b64decode_key, 0, 16;
53             my $encryptkey = substr $b64decode_key, 16, 16;
54             my $iv = Crypt::CBC->random_bytes(16);
55             my $cipher = Crypt::CBC->new(-literal_key => 1,
56             -key => $encryptkey,
57             -iv => $iv,
58             -keysize => 16,
59             -blocksize => 16,
60             -padding => 'standard',
61             -cipher => 'Rijndael',
62             -header => 'none',
63             );
64             my $ciphertext = $cipher->encrypt($data);
65             my $pre_token = $FERNET_TOKEN_VERSION . _timestamp() . $iv . $ciphertext;
66             my $digest=hmac_sha256($pre_token, $signkey);
67             my $token = $pre_token . $digest;
68             return _urlsafe_pading_base64_encode($token);
69             }
70              
71             sub decrypt {
72             my ($key, $token, $ttl) = @_;
73             verify($key, $token, $ttl) or return;
74             my $b64decode_key = urlsafe_b64decode($key);
75             my $token_data = urlsafe_b64decode($token);
76              
77             my $encryptkey = substr $b64decode_key, 16, 16;
78             my $iv = substr $token_data, 9, 16;
79              
80             my $ciphertextlen = (length $token_data) - 25 - 32;
81             my $ciphertext = substr $token_data, 25, $ciphertextlen;
82            
83             my $cipher = Crypt::CBC->new(-literal_key => 1,
84             -key => $encryptkey,
85             -iv => $iv,
86             -keysize => 16,
87             -blocksize => 16,
88             -padding => 'standard',
89             -cipher => 'Rijndael',
90             -header => 'none',
91             );
92             my $plaintext = $cipher->decrypt($ciphertext);
93             return $plaintext;
94             }
95              
96             sub verify {
97             my ($key, $token, $ttl) = @_;
98             $ttl ||= 0;
99             my $b64decode_key = urlsafe_b64decode($key);
100             my $msg = urlsafe_b64decode($token);
101             my $token_version = substr $msg, 0, 1;
102             ($token_version eq $FERNET_TOKEN_VERSION) or return 0;
103              
104             if ($ttl > 0) {
105             my $timestamp_bytes = substr $msg, 1, 8;
106             my $timestamp = _byte_to_time($timestamp_bytes);
107             return 0 if (time - $timestamp > $ttl);
108             }
109              
110             my $token_sign = substr $msg, (length $msg) - 32, 32;
111             my $signkey = substr $b64decode_key, 0, 16;
112             my $pre_token = substr $msg, 0, (length $msg) - 32;
113             my $verify_digest = hmac_sha256($pre_token , $signkey);
114             ($token_sign eq $verify_digest) and return 1;
115             return 0;
116             }
117              
118             sub _timestamp {
119             use bytes;
120             my $time = time;
121             my $time64bit;
122             for my $index (0..7) {
123             $time64bit .= substr pack("I", ($time >> $index * 8) & 0xFF), 0, 1;
124             }
125             my $result = reverse $time64bit;
126             no bytes;
127             return $result;
128             }
129              
130             sub _urlsafe_pading_base64_encode {
131             my ($msg) = @_;
132             my $s = urlsafe_b64encode($msg);
133             return $s.("=" x (4 - length($s) % 4));
134             }
135              
136             sub _byte_to_time {
137             my ($bytes) = @_;
138             use bytes;
139             my $rb = reverse $bytes;
140             my $time = unpack 'V', $rb;
141             return $time;
142             }
143              
144             1;
145             __END__