DBIx-Squirrel
view release on metacpan or search on metacpan
lib/DBIx/Squirrel/Crypt/Fernet.pm view on Meta::CPAN
require Crypt::CBC;
require Crypt::Rijndael;
require Exporter;
use Const::Fast 'const';
use Digest::SHA 'hmac_sha256';
use MIME::Base64::URLSafe qw(
urlsafe_b64decode
urlsafe_b64encode
);
use namespace::clean;
use overload '""' => \&to_string; # overload after namespace::clean for stringification to work
const my $TOKEN_VERSION => pack( "H*", '80' );
const my $LEN_HDR => 25;
const my $LEN_DIGEST => 32;
const my $LEN_HDR_DIGEST => $LEN_HDR + $LEN_DIGEST;
# Calculate the age (seconds) of a token from its timestamp field.
sub _age_sec {
use bytes;
my($token) = @_;
return time - unpack( 'V', reverse( substr( $token, 1, 8 ) ) );
}
# Generate a timestamp field that can be embedded in a token.
sub _timestamp {
use bytes;
local $_;
my $t = time();
my @p = map( substr( pack( 'I', ( $t >> $_ * 8 ) & 0xFF ), 0, 1 ), 0 .. 7 );
return join( '', reverse(@p) );
}
# Generate a random 32-byte Fernet key.
sub _rand_key {
return Crypt::CBC->random_bytes(32);
}
# Encode a binary string as Base64 with padding.
sub _pad_b64encode {
my $b64 = urlsafe_b64encode(shift);
return $b64 . '=' x ( 4 - length($b64) % 4 );
}
=head2 METHODS
=head3 C<new>
$obj = DBIx::Squirrel::Crypt::Fernet->new();
$obj = DBIx::Squirrel::Crypt::Fernet->new($key);
A constructor (also see L<Fernet>).
If no arguments are passed then a random 32-byte Fernet key is generated. If
a Base64-encoded key is passed then it will be decoded and its signing and
encryption key fields extracted.
Take care never to display the binary signing and extraction keys, but to use
the C<to_string> method (or stringification) to recombine them into a Base64-
encoded Fernet key.
=cut
sub new {
my( $class, $b64key ) = @_;
my $fernet_key = $b64key ? urlsafe_b64decode($b64key) : _rand_key();
my $self = {
signing_key => substr( $fernet_key, 0, 16 ),
encrypt_key => substr( $fernet_key, 16, 16 ),
};
return bless $self, ref($class) || $class;
}
=head3 C<generatekey>
$key = $obj->generatekey();
$key = DBIx::Squirrel::Crypt::Fernet->generatekey();
Returns a Base64-encoded randomly-generated key.
=cut
sub generatekey {
return _pad_b64encode( _rand_key() );
}
=head3 C<encrypt>
$token = $obj->encrypt($message);
Encrypts a message, returning a Base64-encode token.
=cut
sub encrypt {
my( $self_or_b64key, $data ) = @_;
my( $signing_key, $encrypt_key ) = do {
if ( UNIVERSAL::isa( $self_or_b64key, __PACKAGE__ ) ) {
@{$self_or_b64key}{qw(signing_key encrypt_key)};
}
else {
my $key = urlsafe_b64decode($self_or_b64key);
substr( $key, 0, 16 ), substr( $key, 16, 16 );
}
};
my $iv = Crypt::CBC->random_bytes(16);
my $t = $TOKEN_VERSION . _timestamp() . $iv . Crypt::CBC->new(
-cipher => 'Rijndael',
-header => 'none',
-iv => $iv,
-key => $encrypt_key,
-keysize => 16,
-literal_key => 1,
lib/DBIx/Squirrel/Crypt/Fernet.pm view on Meta::CPAN
=head3 C<verify>
$bool = $obj->verify($token);
$bool = $obj->verify($token, $ttl);
Returns true if the token was signed using the same signing key as that
embedded in the Fernet key. If a time-to-live (seconds) is specified (C<$ttl>)
then a further check is made to ensure that the token has not expired.
=cut
sub verify {
my( $self_or_b64key, $b64token, $ttl ) = @_;
my $signing_key = do {
if ( UNIVERSAL::isa( $self_or_b64key, __PACKAGE__ ) ) {
$self_or_b64key->{signing_key};
}
else {
substr( urlsafe_b64decode($self_or_b64key), 0, 16 );
}
};
my $t = urlsafe_b64decode($b64token);
return !!0
if $TOKEN_VERSION ne substr( $t, 0, 1 ) || $ttl && _age_sec($t) > $ttl;
my $digest = substr( $t, length($t) - $LEN_DIGEST, $LEN_DIGEST, '' ); # 4-arg substr removes $digest from $token
return $digest eq hmac_sha256( $t, $signing_key );
}
=head3 C<to_string>
$key = $obj->to_string();
$key = "$obj";
Returns the Base64-encoded key.
=cut
sub to_string {
my($self) = @_;
return _pad_b64encode( join( '', @{$self}{qw(signing_key encrypt_key)} ) );
}
=head2 EXPORTS
This package exports nothing by default.
=head3 C<Fernet>
$obj = Fernet();
$obj = Fernet($key);
Alternative constructor (also see L<new>).
Returns a new C<DBIx::Squirrel::Crypt::Fernet> object.
If no arguments are passed then a random 32-byte Fernet key is generated. If
a Base64-encoded key is passed then it will be decoded and its signing and
encryption key fields extracted.
Take care never to display the binary signing and extraction keys, but to use
the C<to_string> method (or stringification) to recombine them into a Base64-
encoded Fernet key.
=cut
sub Fernet {
return __PACKAGE__->new(@_);
}
=head3 C<generatekey>
$key = generatekey();
Returns a Base64-encoded randomly-generated key.
=head3 C<encrypt>
$token = encrypt($key, $message);
Encrypts a message, returning a Base64-encode token.
While a Base64-encoded key may be passed as the first argument, it would be
more efficient to call the "two-faced" C<encrypt> as a method on a Fernet
object to avoid the repeated overhead of decoding and parsing-out the signing
and encryption keys.
=head3 C<decrypt>
$message = decrypt($key, $token);
$message = decrypt($key, $token, $ttl);
Returns the decrypted message, or C<undef> if the token could not be
decrypted. If a time-to-live (seconds) is specified (C<$ttl>) then a
further check is made to ensure that the token has not expired.
While a Base64-encoded key may be passed as the first argument, it would be
more efficient to call the "two-faced" C<decrypt> as a method on a Fernet
object to avoid the repeated overhead of decoding and parsing-out the signing
and encryption keys.
=head3 C<verify>
$bool = verify($key, $token);
$bool = verify($key, $token, $ttl);
Returns true if the token was signed using the same signing key as that
embedded in the Fernet key. If a time-to-live (seconds) is specified (C<$ttl>)
then a further check is made to ensure that the token has not expired.
While a Base64-encoded key may be passed as the first argument, it would be
more efficient to call the "two-faced" C<verify> as a method on a Fernet
object to avoid the repeated overhead of decoding and parsing-out the signing
and encryption keys.
=cut
( run in 0.736 second using v1.01-cache-2.11-cpan-600a1bdf6e4 )