Skip to content

Instantly share code, notes, and snippets.

@neil-sabol
Last active April 30, 2020 23:09
Show Gist options
  • Save neil-sabol/4796eb717c761284b6c4edc5e0e84f7d to your computer and use it in GitHub Desktop.
Save neil-sabol/4796eb717c761284b6c4edc5e0e84f7d to your computer and use it in GitHub Desktop.
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