NAME

Data::Tools::Crypto::Base - common encryption API shared by the Data::Tools::Crypto modules

SYNOPSIS

# Base is never used on its own, it is inherited by the crypto modules:

use Data::Tools::Crypto::Symmetric;
use Data::Tools::Crypto::RSA;

my $crypto = Data::Tools::Crypto::Symmetric->new( $key );

# --------------------------------------------------------------------------

# raw binary data in, raw binary data out

my $ctext = $crypto->encrypt( $ptext );
my $ptext = $crypto->decrypt( $ctext );

# --------------------------------------------------------------------------

# the same, in text-safe encodings

my $hex   = $crypto->encrypt_hex( $ptext );
my $ptext = $crypto->decrypt_hex( $hex   );

my $b64   = $crypto->encrypt_base64( $ptext );
my $ptext = $crypto->decrypt_base64( $b64   );

my $b64u  = $crypto->encrypt_base64url( $ptext );
my $ptext = $crypto->decrypt_base64url( $b64u  );

# --------------------------------------------------------------------------

# any perl data structure, JSON-serialised and encrypted in one step

my $data  = { name => 'test', list => [ 1, 2, 3 ] };

my $sealed = $crypto->freeze( $data   );
my $data   = $crypto->thaw(   $sealed );

my $sealed = $crypto->freeze_hex(       $data   );
my $sealed = $crypto->freeze_base64(    $data   );
my $sealed = $crypto->freeze_base64url( $data   );

# --------------------------------------------------------------------------

DESCRIPTION

Data::Tools::Crypto::Base implements no encryption of its own. It defines the API which every Data::Tools::Crypto module offers, and implements the encoding and serialisation wrappers on top of the encrypt() and decrypt() methods that the inheriting module must provide.

Errors behave exactly as in the inheriting module, since every method here goes through its encrypt() or decrypt(): caller errors raise an exception, and data which does not decrypt makes decrypt_*() and thaw*() return undef.

There is no Data::Tools::Crypto module, the base class is not usable by itself and has nothing to export. Use one of the implementations instead:

* Data::Tools::Crypto::Symmetric -- shared secret key, ChaCha20-Poly1305
* Data::Tools::Crypto::RSA       -- public/private key pair, hybrid

freeze/thaw use JSON as a safer option than Storable, so only plain data can be carried: hashes, arrays, scalars and numbers. Blessed objects, code references and circular structures cannot be serialised.

METHODS

encrypt( $plaintext ), decrypt( $cryptotext )

Implemented by the inheriting module, not by Base. All the methods below are built on top of these two.

encrypt_hex( $plaintext ), decrypt_hex( $cryptotext )

As encrypt()/decrypt() but the cryptotext is HEX encoded, so it is plain text and safe to store or pass anywhere a binary string would not survive.

encrypt_base64( $plaintext ), decrypt_base64( $cryptotext )

As encrypt()/decrypt() but the cryptotext is BASE64 encoded, on a single line with no newlines added.

encrypt_base64url( $plaintext ), decrypt_base64url( $cryptotext )

As encrypt_base64() but using the URL and filename safe BASE64 alphabet, so the result can be used in a URL or a file name without further escaping.

freeze( $data_ref ), thaw( $cryptotext )

freeze() serialises any plain perl data structure to JSON and encrypts it. thaw() reverses this and returns the data structure back, or undef if the cryptotext cannot be decrypted, exactly as decrypt() does.

freeze_hex(), freeze_base64(), freeze_base64url() and their thaw pairs

As freeze()/thaw() but with the cryptotext encoded as described above:

my $sealed = $crypto->freeze_hex( $data_ref );
my $data   = $crypto->thaw_hex(   $sealed   );

REQUIRED MODULES

Data::Tools::Crypto::Base uses:

* Data::Tools
* JSON
* MIME::Base64

GITHUB REPOSITORY

https://github.com/cade-vs/perl-data-tools-crypto

git clone https://github.com/cade-vs/perl-data-tools-crypto.git

AUTHOR

Vladi Belperchinov-Shabanski "Cade"
      <cade@noxrun.com> <cade@bis.bg> <cade@cpan.org>
http://cade.noxrun.com/

NAME

Data::Tools::Crypto::Symmetric - authenticated symmetric encryption with a shared secret key

SYNOPSIS

use Data::Tools::Crypto::Symmetric;
use Crypt::PRNG;
use Exception::Sink;

# the key is 32 raw bytes, not a passphrase

my $key    = Crypt::PRNG::random_bytes( 32 );
my $crypto = Data::Tools::Crypto::Symmetric->new( $key );

# --------------------------------------------------------------------------

my $ctext = $crypto->encrypt( $ptext );
my $ptext = $crypto->decrypt( $ctext );

boom( "data cannot be decrypted" ) unless defined $ptext;

# --------------------------------------------------------------------------

# the whole encoding and freeze/thaw API comes from
# Data::Tools::Crypto::Base, see there for the full list

my $sealed = $crypto->freeze_base64url( { name => 'test' } );
my $data   = $crypto->thaw_base64url( $sealed );

# --------------------------------------------------------------------------

# a new key can be set on an existing object

$crypto->reinit( $another_key );

# --------------------------------------------------------------------------

DESCRIPTION

Data::Tools::Crypto::Symmetric encrypts with ChaCha20-Poly1305, which is authenticated: cryptotext which has been modified in any way will not decrypt at all, rather than decrypting to wrong data. A fresh random nonce is used for every message, so encrypting the same plaintext twice never gives the same cryptotext.

Both sides need the same secret key. If there is no way to share one, use Data::Tools::Crypto::RSA instead.

METHODS

new( $key )

Returns a new object. $key must be exactly 32 raw bytes, undef or an empty key is refused. A key which perl holds as characters is accepted if it can be represented as bytes, and is converted, otherwise it is rejected -- 32 characters are not always 32 bytes.

There are no options yet. Unknown options are refused rather than silently ignored.

reinit( $key )

Sets a new key on an existing object. All previous state is cleared first, so if the key or an option is rejected the object is left without a key and is not usable until a later reinit() succeeds. Catching the exception and handling the unusable object is left to the caller.

encrypt( $plaintext )

Returns the cryptotext, which is always binary. There is no limit on the size of the plaintext, the cryptotext is 29 bytes longer than the plaintext.

decrypt( $cryptotext )

Returns the plaintext, or undef if the cryptotext cannot be decrypted, which covers a wrong key, modified data, truncated data and random input alike. There is deliberately no more detail than that: telling these cases apart would help an attacker more than a caller.

UTF8 HANDLING

encrypt()/decrypt() are transparent. Whatever goes in comes back out, with the same value, the same length and in the same form. A byte string returns as a byte string and a character string returns as a character string, which are not the same thing in perl even when they print identically:

my $chars = "caf\x{e9}";             # 4 characters
my $bytes = "caf\xc3\xa9";           # 5 bytes, the utf8 encoding of it

$crypto->decrypt( $crypto->encrypt( $chars ) );  # 4 characters back
$crypto->decrypt( $crypto->encrypt( $bytes ) );  # 5 bytes back

Cryptotext is always binary. Passing a character string to decrypt() is a mistake and raises an exception, rather than being silently mangled.

ERRORS

The two kinds of failure are reported differently and never confused:

* a caller or configuration error raises an exception with boom(), i.e. a
  missing, empty or wrong sized key, an unknown option, an undefined
  argument, a character string where binary data is required, or any use
  of an object whose reinit() failed

* data which simply does not decrypt returns undef, which is a normal
  result the caller is expected to check

DATA FORMAT

nonce (12 bytes) . cryptotext . authentication tag (16 bytes)

The plaintext carries one leading marker byte, 'b' for binary or 'u' for a character string, which is what makes the utf8 handling above transparent. This accounts for the 29 bytes of overhead.

REQUIRED MODULES

Data::Tools::Crypto::Symmetric uses:

* Data::Tools::Crypto::Base
* Crypt::AuthEnc::ChaCha20Poly1305 (CryptX)
* Crypt::PRNG (CryptX)
* Encode
* Exception::Sink

GITHUB REPOSITORY

https://github.com/cade-vs/perl-data-tools-crypto

git clone https://github.com/cade-vs/perl-data-tools-crypto.git

AUTHOR

Vladi Belperchinov-Shabanski "Cade"
      <cade@noxrun.com> <cade@bis.bg> <cade@cpan.org>
http://cade.noxrun.com/

NAME

Data::Tools::Crypto::RSA - hybrid public key encryption and digital signatures with an RSA key pair

SYNOPSIS

use Data::Tools qw( file_text_load );
use Data::Tools::Crypto::RSA;
use Exception::Sink;

# the key is always the PEM text itself, never a file name

my $pem    = file_text_load( 'private.pem' );
my $crypto = Data::Tools::Crypto::RSA->new( $pem );

# --------------------------------------------------------------------------

# anyone with the public key can encrypt, only the private key can decrypt

my $ctext = $crypto->encrypt( $ptext );
my $ptext = $crypto->decrypt( $ctext );

boom( "data cannot be decrypted" ) unless defined $ptext;

# --------------------------------------------------------------------------

# only the private key can sign, anyone with the public key can verify

my $sig = $crypto->sign( $message );

boom( "message is not authentic" ) unless $crypto->verify( $message, $sig );

# the same, with the signature in text-safe encodings

my $sig_hex  = $crypto->sign_hex( $message );
my $sig_b64  = $crypto->sign_base64( $message );
my $sig_b64u = $crypto->sign_base64url( $message );

boom( "message is not authentic" ) unless $crypto->verify_base64url( $message, $sig_b64u );

# --------------------------------------------------------------------------

# a different hash can be selected, it applies to both wrapping and signing

my $crypto = Data::Tools::Crypto::RSA->new( $pem, HASH => 'SHA512' );

# --------------------------------------------------------------------------

# the whole encoding and freeze/thaw API comes from
# Data::Tools::Crypto::Base, see there for the full list

my $sealed = $crypto->freeze_base64url( { name => 'test' } );
my $data   = $crypto->thaw_base64url( $sealed );

# --------------------------------------------------------------------------

DESCRIPTION

Data::Tools::Crypto::RSA encrypts with RSA-OAEP and signs with RSA-PSS.

Encryption is hybrid. RSA by itself can only encrypt a few hundred bytes, so it is not used on the data at all: a fresh symmetric key is generated for each message, the message is encrypted with Data::Tools::Crypto::Symmetric and only that key is wrapped with RSA. There is no limit on the size of the data, and the payload is authenticated exactly as it is there.

PUBLIC AND PRIVATE KEYS

A private key contains the public key, so an object holding a private key can do everything. A public key can only do the two public operations:

                  private key    public key
encrypt()             yes           yes
verify()              yes           yes
decrypt()             yes           no
sign()                yes           no

This is why a service which only sends encrypted data, or only verifies signatures, should be given the public key alone -- it works exactly the same and the private key never reaches that host.

Asking a public key object to decrypt() or sign() is a configuration error, not a data error, and raises an exception saying so.

SIGNATURES

Encryption does not prove who sent anything. Anyone holding the public key can produce a well formed message, because that is precisely what the public key is for. Only sign()/verify() establish origin, and they are independent of the encrypt/decrypt pair -- use both when both properties are needed.

METHODS

new( $pem_text, %options )

Returns a new object. $pem_text is the PEM text of a public or a private key. It is never taken for a file name, reading a key from a file is left to the calling code. Options:

HASH => 'SHA256'   hash for OAEP wrapping and PSS signatures, default SHA256

Unknown options are refused rather than silently ignored, as is a key too small to be usable -- see KEY SIZE below.

reinit( $pem_text, %options )

Sets a new key on an existing object. All previous state is cleared first, so if the key or an option is rejected the object is left without a key and is not usable until a later reinit() succeeds. Catching the exception and handling the unusable object is left to the caller.

encrypt( $plaintext )

Returns the cryptotext, which is always binary. There is no limit on the size of the plaintext, the cryptotext is the key modulus size plus 29 bytes longer than the plaintext.

decrypt( $cryptotext )

Returns the plaintext, or undef if the cryptotext cannot be decrypted, which covers a wrong key, modified data, truncated data and random input alike. Raises an exception if the object holds a public key.

sign( $message )

Returns an RSA-PSS signature, which is always binary and is the key modulus size. Raises an exception if the object holds a public key.

verify( $message, $signature )

Returns true if the signature was made over this exact message by the private key matching this object's key, false otherwise. An unusable or malformed signature is simply false, it does not raise an exception.

sign_hex(), sign_base64(), sign_base64url() and their verify pairs

As sign()/verify() but with the signature HEX, BASE64 or URL-safe BASE64 encoded, so it can be stored or passed as plain text. BASE64 is on a single line with no newlines added. Only the signature is encoded, the message is signed and verified exactly as with sign()/verify():

my $sig = $crypto->sign_hex( $message );
my $ok  = $crypto->verify_hex( $message, $sig );

An encoded signature which does not decode to a valid signature is simply false, as in verify().

is_private()

True if the object holds a private key, so the caller can tell in advance whether decrypt() and sign() are available to it. Like every other method it raises an exception on an object whose reinit() failed, rather than answering false.

KEY SIZE

OAEP can carry keysize minus twice the hash size minus two bytes, and that must fit the 32 byte symmetric key, so the key modulus must be at least

32 + 2 * hash size + 2 bytes

PSS signatures need less than that, so the OAEP limit is the one which decides. The minimum key size for each HASH option:

HASH               hash size   min modulus            smallest usual key
SHA1                20 bytes    74 bytes ( 592 bits)   1024 bits
SHA224              28 bytes    90 bytes ( 720 bits)   1024 bits
SHA256 (default)    32 bytes    98 bytes ( 784 bits)   1024 bits
SHA3_256            32 bytes    98 bytes ( 784 bits)   1024 bits
SHA384              48 bytes   130 bytes (1040 bits)   1536 bits
SHA512              64 bytes   162 bytes (1296 bits)   1536 bits
SHA3_512            64 bytes   162 bytes (1296 bits)   1536 bits

So a 1024 bit key works with SHA256 but is refused with SHA384 or SHA512. A key too small for the chosen hash is refused when it is loaded, rather than failing on every encrypt() later.

These are the smallest keys which work at all, not a recommendation: 1024 bit RSA is no longer considered secure, use 2048 bits or more.

UTF8 HANDLING

encrypt()/decrypt() are transparent, exactly as described in Data::Tools::Crypto::Symmetric, which does the work.

sign()/verify() cover the same distinction, so a character string and its utf8 byte form do not share a signature. Signatures are always binary, passing a character string to verify() raises an exception.

ERRORS

The two kinds of failure are reported differently and never confused:

* a caller or configuration error raises an exception with boom(), i.e. a
  key given as a reference instead of PEM text, malformed or unsupported
  key material, a key too small, an invalid
  or unknown hash, an unknown option, an undefined argument, a private
  operation on a public key, or any use of an object whose reinit() failed

* data which simply does not decrypt or verify returns undef from decrypt()
  and false from verify(), which are normal results the caller must check

DATA FORMAT

RSA wrapped symmetric key (key modulus size) . Crypto::Symmetric cryptotext

REQUIRED MODULES

Data::Tools::Crypto::RSA uses:

* Data::Tools::Crypto::Base
* Data::Tools::Crypto::Symmetric
* Crypt::PK::RSA (CryptX)
* Crypt::Digest (CryptX)
* Crypt::PRNG (CryptX)
* Data::Tools
* Encode
* Exception::Sink
* MIME::Base64

GITHUB REPOSITORY

https://github.com/cade-vs/perl-data-tools-crypto

git clone https://github.com/cade-vs/perl-data-tools-crypto.git

AUTHOR

Vladi Belperchinov-Shabanski "Cade"
      <cade@noxrun.com> <cade@bis.bg> <cade@cpan.org>
http://cade.noxrun.com/