-
-
Save jonfriesen/234c7471c3e3199f97d5 to your computer and use it in GitHub Desktop.
#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; | |
} |
Thanks for this code! I wrapped it in a module for easier use: https://github.com/ecspresso/TOTPPowerShellModule
Awesome @ecpresso! Great job :) [
$Window is a time-based Window. It adds n seconds to the current time in which you have to validate the generated OTP.
$Window is the window size. It is used to calculate the number of windows that has elapsed since the epoch.Nice script @jonfriesen
Hi,
I have a token setup in Authy and I would like to use that one with this script. Is this possible?
Many Thanks
POC
I have a token setup in Authy and I would like to use that one with this script. Is this possible?
@DaDDyICEPOC If you can get the token as a string, yes.
OMG thank you for this! Mind if I... you know 👉👈?
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!
@nduval,
$Window is a time-based Window. It adds n seconds to the current time in which you have to validate the generated OTP.
Nice script @jonfriesen