See https://blog.neilsabol.site/post/effect-of-clock-skew-on-oath-totp-passcodes/. This snippet generates and compares OATH-TOTP passcodes with varying degrees of clock skew (resets the computer's time) and dumps the results to CSV.
############################################################################################################ | |
# NOTE: This must be run as administrator since w32tm and time are used to manipulate the computer's time. # | |
############################################################################################################ | |
# Import ecspresso's TOTPPowerShellModule (based on jonfriesen's TOTP Client for PowerShell). | |
# Assumes the module is downloaded to C:\Temp\TOTP. | |
# https://github.com/ecspresso/TOTPPowerShellModule | |
# https://gist.github.com/jonfriesen/234c7471c3e3199f97d5 | |
Import-Module C:\Temp\TOTP\totp.psd1 | |
# Add CSV headers. | |
"time,skew,correct otp" > C:\Temp\csvresults.csv | |
# Start the "Windows Time" service - this is required by w32tm. | |
Start-Service -Name "W32Time" | |
sleep 5 | |
# Loop until cancelled | |
while($true) { | |
# Sync the clock at the beginning of each "run." | |
# See http://adriank.org/windows-server-2008-r2-sp1-sync-time-external-time-source/ | |
w32tm /config /manualpeerlist:"time.windows.com" /syncfromflags:manual /reliable:yes /update | |
w32tm /resync | |
# Write the current clock skew (drift) for information - should be 0 since we just synced. | |
# This uses the free World Time API and assumes your public IP is geolocated in your timezone. | |
$actualTime=Get-Date((Invoke-RestMethod -Uri http://worldtimeapi.org/api/ip).datetime);$computerTime=Get-Date | |
write-host "Time drift is currently $(($computerTime-$actualTime).seconds) seconds" | |
write-host "" | |
# Generate a random 40 character hex TOTP secret. | |
# See https://codegolf.stackexchange.com/questions/58442/generate-random-uuid | |
# The length of the hex secret must be divisible by 5 to leverage HumanEquivalentUnit's | |
# byte to base32 conversion code below - I used a 40 digit secret. | |
$hexSecret = (((40)|%{((1..$_)|%{('{0:X}' -f (random(16)))})}) -Join "").ToLower() | |
# Convert the hex secret key to base32 (with byte array as an intermediary). | |
# This seemed like the easier path vs. generating a base32 secret and converting back to hex. | |
# First, from hex to bytes. | |
# See https://gist.github.com/jonfriesen/234c7471c3e3199f97d5 ( function Convert-HexToByteArray ) | |
$byteSecret = $hexSecret -replace '^0x', '' -split "(?<=\G\w{2})(?=\w{2})" | %{ [Convert]::ToByte( $_, 16 ) } | |
# Then, from bytes to base32. | |
# See https://humanequivalentunit.github.io/Base32-coding-the-scripting-way/ | |
$byteArrayAsBinaryString = -join $byteSecret.ForEach{ | |
[Convert]::ToString($_, 2).PadLeft(8, '0') | |
} | |
$base32Secret = [regex]::Replace($byteArrayAsBinaryString, '.{5}', { | |
param($Match) | |
'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'[[Convert]::ToInt32($Match.Value, 2)] | |
}).ToLower() | |
# Get the current time for this run. | |
$realTime = Get-Date -format "hh:mm:ss tt" | |
# Generate the "correct" OTP (no skew) using the random secret. | |
$correctOTP = otp $base32Secret | |
# Iterate through clock skew/drift scenarios from -30 seconds to 30 seconds. | |
for($i=-30;$i -le 30;$i++) { | |
# Use the good old "time" command to reset the computer's time to the current time (known good). | |
# This fixes the intentional drift introduced by previous iterations. | |
cmd /c "time $realTime" | |
# Create a TimeSpan to represent the offset in the current iteration. | |
$timeToAdd = New-TimeSpan -Seconds $i | |
# Skew the computer's time for this iteration. | |
set-date -adjust $timeToAdd | out-null | |
# Call ecspresso's module to generate an OTP based on the random secret. | |
# By default, this is a 6 digit code with a 30-second window. | |
# This OTP will be based on the computer's current time, which we skewed above. | |
$skewedOTP = otp $base32Secret | |
# Check if the OTP for this iteration matches the expected/correct OTP. | |
if($skewedOTP -eq $correctOTP) { $result="true" } else { $result="false" } | |
# Write the result of this iteration to the CSV. | |
"$(get-date -format "hh:mm:ss tt"),$i,$result" >> C:\Temp\csvresults.csv | |
} | |
# Write the current clock skew (drift) for information - should be off now since we forced time changes on the computer. | |
$actualTime=Get-Date((Invoke-RestMethod -Uri http://worldtimeapi.org/api/ip).datetime);$computerTime=Get-Date | |
write-host "Time drift is currently $(($computerTime-$actualTime).seconds) seconds" | |
} | |
######################################################################################################## | |
# The resulting CSV (csvresults.csv) will require further analysis in Excel to produce the data I did. # | |
######################################################################################################## |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment