Last active
April 30, 2020 23:09
-
-
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.
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
############################################################################################################ | |
# 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