Crypto.psm1

function ConvertTo-Base64Url {
    param (
        [byte[]]$bytes
    )
    $base64 = [System.Convert]::ToBase64String($bytes)
    $base64Url = $base64 -replace '\+', '-' -replace '/', '_' -replace '='
    return $base64Url
}

function ConvertFrom-Base64Url {
    param (
        [string]$base64Url
    )
    $base64 = $base64Url -replace '-', '+' -replace '_', '/' 
    $padding = 4 - ($base64.Length % 4)
    if ($padding -lt 4) {
        $base64 += '=' * $padding
    }

    $bytes = [System.Convert]::FromBase64String($base64)
    return $bytes
}


function New-EncryptionKey {
    $aes = New-Object System.Security.Cryptography.AesManaged
    $aes.GenerateKey()

    $aes.Dispose()

    return (ConvertTo-Base64Url $aes.Key)
}

function Protect-String {
    param (
        [string]$textToEncrypt,
        [byte[]]$encryptionKey
    )

    # Convert the plaintext and key to byte arrays
    $plaintextBytes = [System.Text.Encoding]::UTF8.GetBytes($textToEncrypt)

    # Create an AesGcm object
    $aesGcm = [Security.Cryptography.AesGcm]::new($encryptionKey)

    # Create a nonce (IV) - A nonce should never be repeated with the same key
    $nonce = New-RandomIV

    # Create a buffer to hold the ciphertext and the authentication tag
    $ciphertext = New-Object byte[] $plaintextBytes.Length  # Same length as plaintext
    $tag = [byte[]]::new(16)

    # Encrypt the plaintext and obtain the authentication tag
    $aesGcm.Encrypt($nonce, $plaintextBytes, $ciphertext, $tag, $null)

    # Concatenate the nonce, the ciphertext, and the authentication tag
    $ivAndCiphertext = $nonce + $ciphertext + $tag

    # Convert the result to Base64 for easy storage and transmission
    $encryptedBase64 = [Convert]::ToBase64String($ivAndCiphertext)

    $aesGcm.Dispose()

    $result = "data:application/octet-stream;base64," + $encryptedBase64
    return $result
}

# Function to generate a random initialization vector (IV)
function New-RandomIV {
    $randomIV = New-Object byte[] 12
    [System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($randomIV)
    return $randomIV
}

function Unprotect-String {
    param (
        [string]$encryptedData,
        [byte[]]$encryptionKey
    )

    # Remove the base64 encoding prefix
    $base64Data = $encryptedData -replace "data:application/octet-stream;base64,", ""

    # Convert the base64-encoded string to a byte array
    $ivAndCiphertextAndTag = [System.Convert]::FromBase64String($base64Data)

    # Split the combined array into the nonce (IV) and ciphertext
    $nonceSize = 12  # The size of the nonce in bytes
    $nonce = $ivAndCiphertextAndTag[0..($nonceSize - 1)]
    $ciphertext = $ivAndCiphertextAndTag[$nonceSize..($ivAndCiphertextAndTag.Length - 17)]
    $tag = $ivAndCiphertextAndTag[($ivAndCiphertextAndTag.Length - 16)..($ivAndCiphertextAndTag.Length - 1)]

    # Create an AesGcm object
    $aesGcm = [Security.Cryptography.AesGcm]::new($encryptionKey)

    # Create a buffer to hold the plaintext
    $plaintext = [byte[]]::new($ciphertext.Length)

    # Decrypt the ciphertext
    $aesGcm.Decrypt($nonce, $ciphertext, $tag, $plaintext, $null)
    $aesGcm.Dispose()


    # Convert the resulting plaintext byte array to a string
    $decryptedText = [System.Text.Encoding]::UTF8.GetString($plaintext)

    return $decryptedText
}

# Helper function to convert Base64 to byte array
function ConvertFrom-Base64 {
    param (
        [string]$base64EncodedString
    )
    $bytes = [System.Convert]::FromBase64String($base64EncodedString)
    return $bytes
}