Last active
September 12, 2024 23:07
-
-
Save jonfriesen/234c7471c3e3199f97d5 to your computer and use it in GitHub Desktop.
TOTP Client for PowerShell
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#requires -version 2 | |
<# | |
.SYNOPSIS | |
Time-base One-Time Password Algorithm (RFC 6238) | |
.DESCRIPTION | |
This is an implementation of the RFC 6238 Time-Based One-Time Password Algorithm draft based upon the HMAC-based One-Time Password (HOTP) algorithm (RFC 4226). This is a time based variant of the HOTP algorithm providing short-lived OTP values. | |
.NOTES | |
Version: 1.0 | |
Author: Jon Friesen | |
Creation Date: May 7, 2015 | |
Purpose/Change: Provide an easy way of generating OTPs | |
#> | |
function Get-Otp($SECRET, $LENGTH, $WINDOW){ | |
$enc = [System.Text.Encoding]::UTF8 | |
$hmac = New-Object -TypeName System.Security.Cryptography.HMACSHA1 | |
$hmac.key = Convert-HexToByteArray(Convert-Base32ToHex(($SECRET.ToUpper()))) | |
$timeBytes = Get-TimeByteArray $WINDOW | |
$randHash = $hmac.ComputeHash($timeBytes) | |
$offset = $randhash[($randHash.Length-1)] -band 0xf | |
$fullOTP = ($randhash[$offset] -band 0x7f) * [math]::pow(2, 24) | |
$fullOTP += ($randHash[$offset + 1] -band 0xff) * [math]::pow(2, 16) | |
$fullOTP += ($randHash[$offset + 2] -band 0xff) * [math]::pow(2, 8) | |
$fullOTP += ($randHash[$offset + 3] -band 0xff) | |
$modNumber = [math]::pow(10, $LENGTH) | |
$otp = $fullOTP % $modNumber | |
$otp = $otp.ToString("0" * $LENGTH) | |
return $otp | |
} | |
function Get-TimeByteArray($WINDOW) { | |
$span = (New-TimeSpan -Start (Get-Date -Year 1970 -Month 1 -Day 1 -Hour 0 -Minute 0 -Second 0) -End (Get-Date).ToUniversalTime()).TotalSeconds | |
$unixTime = [Convert]::ToInt64([Math]::Floor($span/$WINDOW)) | |
$byteArray = [BitConverter]::GetBytes($unixTime) | |
[array]::Reverse($byteArray) | |
return $byteArray | |
} | |
function Convert-HexToByteArray($hexString) { | |
$byteArray = $hexString -replace '^0x', '' -split "(?<=\G\w{2})(?=\w{2})" | %{ [Convert]::ToByte( $_, 16 ) } | |
return $byteArray | |
} | |
function Convert-Base32ToHex($base32) { | |
$base32chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; | |
$bits = ""; | |
$hex = ""; | |
for ($i = 0; $i -lt $base32.Length; $i++) { | |
$val = $base32chars.IndexOf($base32.Chars($i)); | |
$binary = [Convert]::ToString($val, 2) | |
$staticLen = 5 | |
$padder = '0' | |
# Write-Host $binary | |
$bits += Add-LeftPad $binary.ToString() $staticLen $padder | |
} | |
for ($i = 0; $i+4 -le $bits.Length; $i+=4) { | |
$chunk = $bits.Substring($i, 4) | |
# Write-Host $chunk | |
$intChunk = [Convert]::ToInt32($chunk, 2) | |
$hexChunk = Convert-IntToHex($intChunk) | |
# Write-Host $hexChunk | |
$hex = $hex + $hexChunk | |
} | |
return $hex; | |
} | |
function Convert-IntToHex([int]$num) { | |
return ('{0:x}' -f $num) | |
} | |
function Add-LeftPad($str, $len, $pad) { | |
if(($len + 1) -ge $str.Length) { | |
while (($len - 1) -ge $str.Length) { | |
$str = ($pad + $str) | |
} | |
} | |
return $str; | |
} |
Magnificient mind @jonfriesen, you've blown me away with this writing.
Would you allow me if i follow your logic and will implement it in C#? Credits still be yours. ❤️
@rey021 of course, glad it helps. Though, I'd expect there to be some C# TOTP libraries out in the world already :P
doesn't the secret need to be in base 32? Hoping to generate a QR code to use with authenticator, but that requires a base 32 secret
NVM, Yes if you use base 32 string, and use it to make a QR code it will match the script output <3
Some minor improvements:
diff --git a/tmp/totp-old.ps1 b/tmp/totp.ps1
index 0a50e76..dbc31de 100644
--- a/tmp/totp-old.ps1
+++ b/tmp/totp.ps1
@@ -11,16 +11,20 @@
Author: Jon Friesen
Creation Date: May 7, 2015
Purpose/Change: Provide an easy way of generating OTPs
-
#>
-
-function Get-Otp($SECRET, $LENGTH, $WINDOW){
- $enc = [System.Text.Encoding]::UTF8
+
+function Get-Otp(){
+ param(
+ [Parameter(Mandatory=$true)]$SECRET,
+ $LENGTH = 6,
+ $WINDOW = 30
+ )
+ $SECRET = $SECRET -replace '\s',''
$hmac = New-Object -TypeName System.Security.Cryptography.HMACSHA1
$hmac.key = Convert-HexToByteArray(Convert-Base32ToHex(($SECRET.ToUpper())))
$timeBytes = Get-TimeByteArray $WINDOW
$randHash = $hmac.ComputeHash($timeBytes)
-
+
$offset = $randhash[($randHash.Length-1)] -band 0xf
$fullOTP = ($randhash[$offset] -band 0x7f) * [math]::pow(2, 24)
$fullOTP += ($randHash[$offset + 1] -band 0xff) * [math]::pow(2, 16)
@@ -42,7 +46,7 @@ function Get-TimeByteArray($WINDOW) {
}
function Convert-HexToByteArray($hexString) {
- $byteArray = $hexString -replace '^0x', '' -split "(?<=\G\w{2})(?=\w{2})" | %{ [Convert]::ToByte( $_, 16 ) }
+ $byteArray = $hexString -replace '^0x', '' -split "(?<=\G\w{2})(?=\w{1,2})" | %{ [Convert]::ToByte( $_, 16 ) }
return $byteArray
}
- enc was unused
- removed trailing whitespace
- set default for length/window like @ecspresso
- automatically remove whitespace from secret
- accept uneven length hexString in Convert-HexToByteArray
- This was an issue with an amazon totp for me. It would throw an error otherwise as there would be a 3 character string into
[Convert]::ToByte
- This was an issue with an amazon totp for me. It would throw an error otherwise as there would be a 3 character string into
Enjoy!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@jamez2128 👍