Functions/Common/New-TimeBasedOneTimeSharedSecret.ps1

<#
    .SYNOPSIS
        Generate a shared secret for the Time-Base One-Time algorithm RFC 6238.
 
    .DESCRIPTION
        This command will create a new shared secret to be used for the
        Time-Based One-Time password algorithm (TOTP) specified in the RFC 6238.
        The command will return an object containing the following
        representations of the shared secret:
        - Shared secret encoded as Base32 (specified in RFC 4648)
        - Key uri format otpauth:// for apps like the Google Authenticator
        - As Duo Security compatible CSV line to import as hardware token
 
    .INPUTS
        None.
 
    .OUTPUTS
        SecurityFever.TOTPSharedSecret. Object with multiple representations of
        the shared secret.
 
    .EXAMPLE
        PS C:\> New-TimeBasedOneTimeSharedSecret -Account 'User1'
        Create a new TOTP shared secret for the user User1.
 
    .LINK
        https://github.com/claudiospizzi/SecurityFever
#>

function New-TimeBasedOneTimeSharedSecret
{
    [CmdletBinding()]
    [Alias('New-TOTPSharedSecret')]
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $Account,

        [Parameter(Mandatory = $false)]
        [System.String]
        $Issuer = "$Env:UserDomain\$Env:Username"
    )

    $ErrorActionPreference = 'Stop'

    [System.Byte[]] $bytes = @()
    for ($i = 0; $i -lt 16; $i++)
    {
        $bytes += [System.Byte] (Get-Random -Minimum 0 -Maximum 255)
    }

    # Convert the shared secret into usable formats
    $sharedSecretHex    = [System.BitConverter]::ToString($bytes) -replace '-', ''
    $sharedSecretBase32 = Convert-ByteToBase32 -Byte $bytes

    # Generate the QR code
    $qrCodePath = '{0}\{1:yyyyMMddHHmmss}_{2}.png' -f $Env:Temp, (Get-Date), $Account.Replace('\', '_')
    $qrOneTimeGenerator = [QRCoder.PayloadGenerator+OneTimePassword]::new()
    $qrOneTimeGenerator.Secret = $sharedSecretBase32
    $qrOneTimeGenerator.Issuer = $Issuer
    $qrOneTimeGenerator.Label  = $Account
    $qrGenerator = [QRCoder.QRCodeGenerator]::new()
    $qrData = $qrGenerator.CreateQrCode($qrOneTimeGenerator.ToString(), 'Q')
    $qrCode = [QRCoder.PngByteQRCode]::new($qrData)
    [System.IO.File]::WriteAllBytes($qrCodePath, $qrCode.GetGraphic(100))

    [PSCustomObject] @{
        PSTypeName          = 'SecurityFever.TOTPSharedSecret'
        SharedSecret        = $sharedSecretBase32
        AuthenticatorUri    = 'otpauth://totp/{0}?secret={1}&issuer={2}' -f $Account, $sharedSecretBase32, $Issuer
        AuthenticatorQRCode = $qrCodePath
        DuoHardwareToken    = 'SoftwareToken_{0},{1}' -f $Account, $sharedSecretHex
    }

    Invoke-Item -Path $qrCodePath
}