How 2FA Authenticators like Google Authenticator work offline
3 min readSep 9, 2023
Ever since I came across 2FA authenticators like Google Authenticator or Steam Guard Mobile Authenticator which I use to login in to steam to play CS:GO, I always wondered how the heck is this mechanism working without internet (i.e no communication between server and client).
- Shared Algorithm: If we think deep enough, it is sure that both the parties Authenticator and Aunthenticatee are sharing some common logic which will produce the OTP.
- Synchronisation Enabler: The produced OTPs are short lived like 15–30 seconds and both the parties are able to check the validity which signifies they are in sync. It means there is some component which is enabling them to sync with each other even with out communicating over internet.
- The Security Ingredients: The OTP produced should be unpredictable making the life of attackers difficult.
TOTP : Time-Based One Time Password
TOTP is a computer algorithm that generates a one-time password (OTP) that uses the current time as a source of uniqueness. It is an extension of the HMAC-based one-time password algorithm (HOTP).
- So the shared Algorithm is TOTP(essentially hashing).
- The synchronisation enabler is clock which is universally available thing and also a input parameter to TOTP algorithm. SO it is important that your device and verifying server’s clock are synced otherwise there will be issues due to time drift.
- But still there are chances that someone is able to intercept all OTPs and reverse engineer that OTP’s are being generated by an algorithm which uses time. So in TOTP we append secret to time and then take hash of the value. This secret is only known by your device and the verifying server. And you device know’s once you register initially for the 2FA.
Sample java code for the Algorithm from Official research paper
class TOTP {
private static final int[] DIGITS_POWER
// 0 1 2 3 4 5 6 7 8
= {1,10,100,1000,10000,100000,1000000,10000000,100000000 };
public static String generateTOTP(String key,
String time,
String returnDigits,
String crypto){
int codeDigits = Integer.decode(returnDigits).intValue();
String result = null;
// Using the counter
// First 8 bytes are for the movingFactor
// Compliant with base RFC 4226 (HOTP)
while (time.length() < 16 )
time = "0" + time;
// Get the HEX in a Byte[]
byte[] msg = hexStr2Bytes(time);
byte[] k = hexStr2Bytes(key);
byte[] hash = hmac_sha(crypto, k, msg);
// put selected bytes into result int
int offset = hash[hash.length - 1] & 0xf;
// bit masking to get unsigned integer
int binary =
((hash[offset] & 0x7f) << 24) |
((hash[offset + 1] & 0xff) << 16) |
((hash[offset + 2] & 0xff) << 8) |
(hash[offset + 3] & 0xff);
int otp = binary % DIGITS_POWER[codeDigits];
result = Integer.toString(otp);
while (result.length() < codeDigits) {
result = "0" + result;
}
return result;
}
public static void main(String[] args) {
// Secret for HMAC-SHA256 - 32 bytes
String seed32 = "313233343536373839303132333435363738"+
"3930313233343536373839303132";
long T0 = 0; // Start Time
long X = 30; // Time for which the OTP should be valid
long currentTime = 1234567890L; // 2009-02-13 23:31:30 (UTC)
long T = (currentTime - T0) / X;
String steps = Long.toHexString(T).toUpperCase();
final String otp = generateTOTP(seed32, steps, "6", "HmacSHA256");
System.out.println("OTP is "+otp + " using | SHA256 |");
}
}
Output: