# NAME

Vigil::Crypt

# SYNOPSIS

## Encryption/Decryption

        use Vigil::Crypt;

        my $crypt = Vigil::Crypt->new( ENCRYPTION_KEY );

        my $encrypted = $crypt->encrypt($plaintext_to_encrypt, $secret1, $secret2);

        my $decrypted = $crypt->decrypt($encrypted, $secret1, $secret2);

## Hashing

        use Vigil::Crypt;

        my $crypt = Vigil::Crypt->new( ENCRYPTION_KEY );

        my $stored_hash = $crypt->hash($password, $PEPPER);

        if( $crypt->verify_password($entered_password, $stored_hash, $PEPPER) ) {
                ... passwords match ...
        } else {
                ... passwords did not match. Do not pass go, do not collect $200 ...
        }

# DESCRIPTION

## Encryption/Decryption

The encrypt\_data and decrypt\_data methods handle sensitive information - like emails
or personal IDs - in a way that makes it extremely difficult for anyone without the
right keys to access. They use ChaCha20-Poly1305, a modern authenticated encryption
algorithm trusted in security-critical applications worldwide. Not only does this
algorithm scramble your data (encryption), it also includes a tag to ensure the data
hasn't been tampered with (authentication). The system also derives an additional
authenticated data (AAD) from user-specific information, which means even if someone
guesses part of the input, they still cannot decrypt the data without the full context.
For a newcomer, think of it as a strong lock on your secrets that refuses to open if
anything looks suspicious - all handled automatically for you.

`ChaCha20-Poly1305` is used for encryption/decryption because:

> \* Provides `authenticated encryption`, ensuring data confidentiality `and` integrity.
>
> \* Resistant to tampering: modifying the ciphertext or tag causes decryption to fail completely.
>
> \* Fast and efficient in software, especially on CPUs without dedicated AES hardware.
>
> \* Uses a 256-bit key, providing strong security against brute-force attacks.
>
> \* Includes a `nonce` to ensure the same plaintext encrypts differently each time.
>
> \* Supports `Additional Authenticated Data (AAD)`, allowing context-specific data to be protected without including it in the ciphertext.
>
> \* Widely standardized and recommended for modern secure communications.
>
> \* Constantly reviewed and trusted by the cryptography community.

## Hashing Passwords/Markers

Hashing is used for one-way protection, typically for passwords, but also for any
verification markers or tokens that need to be validated without storing the original
value. With hash and verify\_password, sensitive data is never stored directly - only
the hashed version. The module uses Argon2id, a state-of-the-art password hashing
function designed to resist brute-force attacks while being memory- and CPU-intensive
enough to slow down attackers. We also pre-hash the input with a pepper to add an
extra layer of security. verify\_password (or verify\_hash) lets you safely check
credentials or other verification markers: it confirms the input matches the stored
hash without ever exposing the original value. In short, it's like a magic 
fingerprint - you can confirm it, but nobody can reverse-engineer it.

`Argon2` is used for password hashing because:

> \* It requires a large amount of RAM to compute.
>
> \* GPU/ASIC attackers can't easily parallelize attacks because they run out of memory bandwidth.
>
> \* This makes brute-force attacks extremely expensive on specialized hardware.
>
> \* Resistant to Time-Memory Trade-Off (TMTO) attacks: attackers can't just reduce memory drastically without paying a huge time penalty.
>
> \* Built with side-channel resistance in mind.
>
> \* Future-proof against most foreseeable optimizations in hardware cracking.
>
> \* Included in RFC 9106 as the recommended password hashing scheme.
>
> \* Supported in libraries across most languages.
>
> \* Constantly reviewed by cryptography experts.

# CLASS METHODS

- $obj->new(encryption\_key => ENCRYPTION\_KEY);

    The constructor takes one argument and it is mandatory.

            use Vigil::Crypt;
            my $crypt = Vigil::Crypt->new( ENCRYPTION_KEY );
            

    The encryption key must be a 64-character string of hexadecimal digits, which corresponds
    to 32 bytes of binary data. This value must be stored somewhere permanent and should never
    change; if it does, all previously encrypted items would need to be re-encrypted. The
    recommended approach is to store the key in a configuration file above web-root level,
    which your program can then `require` to access the key.

    Here is a small script that you can run one time to generate a cryptographically secure encryption key:

            use strict;
            use warnings;
            use Bytes::Random::Secure qw(random_bytes);
            use MIME::Base16 qw(encode_base16);

            # Generate 32 random bytes
            my $key_bytes = random_bytes(32);

            # Convert to hex string (64 characters)
            my $key_hex = encode_base16($key_bytes);

            print "Random 32-byte key (hex): $key_hex\n";
            exit;
            

# OBJECT METHODS

## Encryption/Decryption

- $obj->last\_error;

            print $obj->last_error;
            

    If your attempts to encrypt or decrypt a value fail, then you can print out the contents of this method to see why.

- $obj->encrypt($value\_to\_encrypt, $user\_specific\_value\_1, $user\_specific\_value2);

            my $encrypted_data = $obj->encrypt($plaintext, $userid, $user_account_date);
            

    In my time developing things for the web (25+ years now), I've almost always used encryption with sensitive
    user information. This means that there has always been a user profile associated to the encrypted data. For
    this reason, I designed the encryption/decryption that could come directly from a user profile.

    In my case, every profile has an ID number (userid), and every profile will include a date that it was created (user\_account\_date).
    Those two pieces of information will never (should never) change for an account profile. So if I encrypt something today, those two
    pieces of information should still be identical ten years from now.

    The upside to this is that if the database table gets stolen, the bad-hat would only have the userid,
    they would not have the account creation date.

    The second upside to this is that if the bad-hat did decrypt the data for one user, that decryption would
    not work with any other user, as all user's profile ids and dates would be different.

    - userid

        Generally a sequence of digits, but it does not need to be. It just has to be unique to a user.

    - user\_account\_date

        This can be in any format: ymd, timestamp, seconds since epoch, etc.

    _In fact, you can use any kind of data you want for these two values, so long as those pieces of data are unique to the user._

            my $encrypted_data = $obj->encrypt($plaintext, $aad);
            

    If you have your own AAD, then you can pass that as a single argument.

    AAD (Additional Authenticated Data) - Think of it like a label on a locked box. The box (your encrypted data)
    can't be opened unless the label matches exactly what it was when the box was locked. It's extra info used
    to verify the data hasn't been tampered with, without being hidden inside the box itself. If you don't really
    know what AAD is or how to use it, stick with the two pieces of user information as arguments.

            my $encrypted_data = $obj->encrypt($plaintext);

    You CAN do this but you SHOULD NOT do this. When you pass the data to encrypt with no further arguments, the
    module will generate it's own AAD for that encryption. It will be the same AAD generated for anyone else, though,
    so you really do compromise the security you are trying to enable with encryption.

- $obj->decrypt($encrypted\_data, $user\_specific\_value\_1, $user\_specific\_value2);

            my $plaintext = $obj->decrypt($encrypted_data, $userid, $user_account_date);

    In this method, you are returning the encrypted text to plain text. The rules on arguments are the same as well.
    Remember that how you encrypt data (arguments supplied) must be the exact same way you decrypt data (arguments supplied).

## Hashing

- $obj->hash($password, $pepper);

    **IMPORTANT:** The hash method produces a 32-byte binary value. When creating your database
    column to store this value, define it as: `BINARY(32)`.

    The pepper is optional but strongly recommended. It is an additional secret mixed into the
    password before hashing, which makes brute-force attacks significantly harder. Because
    verification requires the same pepper used during hashing, it must be stored securely, 
    separate from the script - ideally in a configuration file above your web root or in a secure
    environment variable. You can generate a pepper in the same way you generate your encryption key.

    In short, the difference between an encryption key and a pepper is: the encryption key is 
    used to encrypt and decrypt data, while the pepper is used to strengthen password hashes.

- $obj->verify\_password($input\_pwd, $stored\_hashed\_pwd, $pepper);

            if($obj->verify_password($input_pwd, $stored_hashed_pwd, $pepper)) {
                ...password challenge was A-OK, do your stuff...
            } else {
                ...password challenge failed, go away!...
            }
            

    You need to pass the password being valided, then the password that was hashed and stored
    previously, and the pepper. Remember that the pepper for the original password and the
    validation method must be identical.

    _NOTE: This method can also be accessed as `$obj->verify_hash($compare_hash, $stored_hash, $pepper);`_

    Don't limit hashing to passwords. It can be used for one-way tokens or other verification
    markers. Let your imagination run rampant!

# ENCRYPTION LENGTHS

Knowing how long the final encryption will be is important when designing your
database tables. The formula to calculate the length of encrypted values is:

        my $Base64_length = 4 * ceil(($plaintext_bytes + 28) / 3);

Since Perl does not have a ceil() function, we would actually calculate it this way:

        my $Base64_length = 4 * int((($plaintext_bytes + 28) + 2) / 3);

Here are some prepresentative values to get you going:

        Plaintext   Encrypted Base64
         (bytes)      (Characters)
        16          4 * ceil((16 + 28)/3)   = 60
        32          4 * ceil((32 + 28)/3)   = 80
        64          4 * ceil((64 + 28)/3)   = 123
        100         4 * ceil((100 + 28)/3)  = 172
        256         4 * ceil((256 + 28)/3)  = 372
        512         4 * ceil((512 + 28)/3)  = 688
        1000        4 * ceil((1000 + 28)/3) = 1376
        2500        4 * ceil((2500 + 28)/3) = 3352
        5000        4 * ceil((5000 + 28)/3) = 6712

## Local Installation

If your host does not allow you to install from CPAN, then you can install this module locally two ways:

- Same Directory

    In the same directory as your script, create a subdirectory called "Vigil". Then add these two lines, in this order, to your script:

            use lib '.';           # Add current directory to @INC
            use Vigil::Crypt;      # Now Perl can find the module in the same dir
            
            #Then call it as normal:
            my $crypt = Vigil::Crypt->new( ENCRYPTION_KEY );

- In a different directory

    First, create a subdirectory called "Vigil" then add it to `@INC` array through a `BEGIN{}` block in your script:

            #!/usr/bin/perl
            BEGIN {
                push(@INC, '/path/on/server/to/Vigil');
            }
            
            use Vigil::Crypt;
            
            #Then call it as normal:
            my $crypt = Vigil::Crypt->new( ENCRYPTION_KEY );

# AUTHOR

Jim Melanson (jmelanson1965@gmail.com).

Created: October, 2018.

Last Update: August 2025.

License: Use it as you will, and don't pretend you wrote it - be a mensch.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
