This problem is related to TOTP as specified in RFC6238 here: https://www.rfc-editor.org/rfc/rfc6238#section-1.2.
I am to implement the RFC6238 to generate a 10-digit TOTP password, which will be used in a POST request later on. The sample input and output for the TOTP is supposed to be like this:
Sample Input:
- Shared key: "[email protected]" (without double quotes)
- Hash function used:
HMAC-SHA-512
- T0 = 0, Timestep = 30 seconds (as per specified in RFC6238)
- Expected TOTP of 10 digits
Sample Output:
Successful TOTP generated:
1773133250
, for time of Mon, 17 Mar 2014 15:20:51 GMT
base64 encoded POST Authorization username/password request: bmluamFAZXhhbXBsZS5jb206MTc3MzEzMzI1MA==
(I have decoded the sample POST authorization to be '[email protected]:1773133250' hence why I can say that the sample TOTP output is 1773133250)
After attempting to make my own script according to the rfc6238 specification I cannot get the same output for the sample input as above. I tried using other available online TOTP modules that are available online (mostly in Python), to find that they generate the same output as the script that I created. Finally, I tried the Java code given in the example of RFC6238 and came up with the same result as my script, i.e.:
Attempted input:
Hex encoded seed for HMAC512: "6E696E6A61406578616D706C652E636F6D4844454348414C4C454E4745303033" + "6E696E6A61406578616D706C652E636F6D4844454348414C4C454E4745303033";
Time inputted is 1395069651L, representing the time received in sample output
Result of attempt (same output from custom script, other Python modules, and the Java implementation given in RFC6238 documentation):
Generated TOTP: 0490867067
Here is the code that I first used in attempt to generate the TOTP in Python:
# Mission/Task Description:
# * For the "password", provide an 10-digit time-based one time password conforming to RFC6238 TOTP.
#
# ** You have to read RFC6238 (and the errata too!) and get a correct one time password by yourself.
# ** TOTP's "Time Step X" is 30 seconds. "T0" is 0.
# ** Use HMAC-SHA-512 for the hash function, instead of the default HMAC-SHA-1.
# ** Token shared secret is the userid followed by ASCII string value "HDECHALLENGE003" (not including double quotations).
#
# *** For example, if the userid is "[email protected]", the token shared secret is "[email protected]".
# *** For example, if the userid is "[email protected]", the token shared secret is "[email protected]"
#
import hmac
import hashlib
import time
import sys
import struct
userid = "[email protected]"
secret_suffix = "HDECHALLENGE003"
shared_secret = userid+secret_suffix
timestep = 30
T0 = 0
def HOTP(K, C, digits=10):
"""HTOP:
K is the shared key
C is the counter value
digits control the response length
"""
K_bytes = K.encode()
C_bytes = struct.pack(">Q", C)
hmac_sha512 = hmac.new(key = K_bytes, msg=C_bytes, digestmod=hashlib.sha512).hexdigest()
return Truncate(hmac_sha512)[-digits:]
def Truncate(hmac_sha512):
"""truncate sha512 value"""
offset = int(hmac_sha512[-1], 16)
binary = int(hmac_sha512[(offset *2):((offset*2)+8)], 16) & 0x7FFFFFFF
return str(binary)
def TOTP(K, digits=10, timeref = 0, timestep = 30):
"""TOTP, time-based variant of HOTP
digits control the response length
the C in HOTP is replaced by ( (currentTime - timeref) / timestep )
"""
C = int ( 1395069651 - timeref ) // timestep
return HOTP(K, C, digits = digits)
passwd = TOTP("[email protected]@example.comHDECHALLENGE003", 10, T0, timestep).zfill(10)
print passwd
Here is the second code in Java which is essentially a modified version of the Java implementation found in RFC6238:
/**
Copyright (c) 2011 IETF Trust and the persons identified as
authors of the code. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, is permitted pursuant to, and subject to the license
terms contained in, the Simplified BSD License set forth in Section
4.c of the IETF Trust's Legal Provisions Relating to IETF Documents
(http://trustee.ietf.org/license-info).
*/
import java.lang.reflect.UndeclaredThrowableException;
import java.security.GeneralSecurityException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
import java.util.TimeZone;
import java.util.Calendar;
/**
* This is an example implementation of the OATH
* TOTP algorithm.
* Visit www.openauthentication.org for more information.
*
* @author Johan Rydell, PortWise, Inc.
*/
public class TOTP {
private TOTP() {}
/**
* This method uses the JCE to provide the crypto algorithm.
* HMAC computes a Hashed Message Authentication Code with the
* crypto hash algorithm as a parameter.
*
* @param crypto: the crypto algorithm (HmacSHA1, HmacSHA256,
* HmacSHA512)
* @param keyBytes: the bytes to use for the HMAC key
* @param text: the message or text to be authenticated
*/
private static byte[] hmac_sha(String crypto, byte[] keyBytes,
byte[] text){
try {
Mac hmac;
hmac = Mac.getInstance(crypto);
SecretKeySpec macKey =
new SecretKeySpec(keyBytes, "RAW");
hmac.init(macKey);
return hmac.doFinal(text);
} catch (GeneralSecurityException gse) {
throw new UndeclaredThrowableException(gse);
}
}
/**
* This method converts a HEX string to Byte[]
*
* @param hex: the HEX string
*
* @return: a byte array
*/
private static byte[] hexStr2Bytes(String hex){
// Adding one byte to get the right conversion
// Values starting with "0" can be converted
byte[] bArray = new BigInteger("10" + hex,16).toByteArray();
// Copy all the REAL bytes, not the "first"
byte[] ret = new byte[bArray.length - 1];
for (int i = 0; i < ret.length; i++)
ret[i] = bArray[i+1];
return ret;
}
private static final long[] DIGITS_POWER
// 0 1 2 3 4 5 6 7 8 9 10
= {1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000,10000000000L};
/**
* This method generates a TOTP value for the given
* set of parameters.
*
* @param key: the shared secret, HEX encoded
* @param time: a value that reflects a time
* @param returnDigits: number of digits to return
*
* @return: a numeric String in base 10 that includes
* {@link truncationDigits} digits
*/
public static String generateTOTP(String key,
String time,
String returnDigits){
return generateTOTP(key, time, returnDigits, "HmacSHA1");
}
/**
* This method generates a TOTP value for the given
* set of parameters.
*
* @param key: the shared secret, HEX encoded
* @param time: a value that reflects a time
* @param returnDigits: number of digits to return
*
* @return: a numeric String in base 10 that includes
* {@link truncationDigits} digits
*/
public static String generateTOTP256(String key,
String time,
String returnDigits){
return generateTOTP(key, time, returnDigits, "HmacSHA256");
}
/**
* This method generates a TOTP value for the given
* set of parameters.
*
* @param key: the shared secret, HEX encoded
* @param time: a value that reflects a time
* @param returnDigits: number of digits to return
*
* @return: a numeric String in base 10 that includes
* {@link truncationDigits} digits
*/
public static String generateTOTP512(String key,
String time,
String returnDigits){
return generateTOTP(key, time, returnDigits, "HmacSHA512");
}
/**
* This method generates a TOTP value for the given
* set of parameters.
*
* @param key: the shared secret, HEX encoded
* @param time: a value that reflects a time
* @param returnDigits: number of digits to return
* @param crypto: the crypto function to use
*
* @return: a numeric String in base 10 that includes
* {@link truncationDigits} digits
*/
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;
int binary =
((hash[offset] & 0x7f) << 24) |
((hash[offset + 1] & 0xff) << 16) |
((hash[offset + 2] & 0xff) << 8) |
(hash[offset + 3] & 0xff);
long otp = binary % DIGITS_POWER[codeDigits];
result = Long.toString(otp);
while (result.length() < codeDigits) {
result = "0" + result;
}
return result;
}
public static void main(String[] args) {
// Seed for HMAC-SHA1 - 20 bytes
String seed = "3132333435363738393031323334353637383930";
// Seed for HMAC-SHA256 - 32 bytes
String seed32 = "3132333435363738393031323334353637383930" +
"313233343536373839303132";
// Seed for HMAC-SHA512 - 64 bytes
String seed64 = "6E696E6A61406578616D706C652E636F6D4844454348414C4C454E4745303033";
//NOTE: this is the 16-bit/hex encoded representation of "[email protected]"
String seednew = "6E696E6A61406578616D706C652E636F6D4844454348414C4C454E4745303033" +
"6E696E6A61406578616D706C652E636F6D4844454348414C4C454E4745303033";
long T0 = 0;
long X = 30;
long current = System.currentTimeMillis()/1000;
System.out.println(current);
long testTime[] = {59L, 1234567890L,1395069651L};
String steps = "0";
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
df.setTimeZone(TimeZone.getTimeZone("UTC"));
try {
System.out.println(
"+---------------+-----------------------+" +
"------------------+--------+--------+");
System.out.println(
"| Time(sec) | Time (UTC format) " +
"| Value of T(Hex) | TOTP | Mode |");
System.out.println(
"+---------------+-----------------------+" +
"------------------+--------+--------+");
for (int i=0; i<testTime.length; i++) {
long T = (testTime[i] - T0)/X;
steps = Long.toHexString(T).toUpperCase();
while (steps.length() < 16) steps = "0" + steps;
String fmtTime = String.format("%1$-11s", testTime[i]);
String utcTime = df.format(new Date(testTime[i]*1000));
System.out.print("| " + fmtTime + " | " + utcTime +
" | " + steps + " |");
System.out.println(generateTOTP(seed, steps, "8",
"HmacSHA1") + "| SHA1 |");
System.out.print("| " + fmtTime + " | " + utcTime +
" | " + steps + " |");
System.out.println(generateTOTP(seed32, steps, "8",
"HmacSHA256") + "| SHA256 |");
System.out.print("| " + fmtTime + " | " + utcTime +
" | " + steps + " |");
System.out.println(generateTOTP(seed64, steps, "10",
"HmacSHA256") + "| SHA256 |");
System.out.print("| " + fmtTime + " | " + utcTime +
" | " + steps + " |");
System.out.println(generateTOTP(seednew, steps, "10",
"HmacSHA512") + "| SHA512 |");
System.out.println(
"+---------------+-----------------------+" +
"------------------+--------+--------+");
}
}catch (final Exception e){
System.out.println("Error : " + e);
}
}
}
Do note that for the modified RFC Java code, the output would be that of several date/time listed in the testTime[] array, however the targeted GMT from the sample input of the task is also included in here as well. Testing in my Ubuntu showed the same result as that from my Python script.
I believe that I have followed the instructions given by the task. I used the actual RFC given Java code to find it is not generating the same output as the one given in the task. I contacted the provider of the task to ask if there is a bug, but they say that it is correct.
Perhaps I am missing something here, such as the way the task provider actually encrypts the shared key?
1234567890
and check what value will be generated for a given moment. Than compare it with what the sample code is generating for the given time. That will highlight if there is a difference between the algorithm described in the RFC6238 and the actual implementation that you try to use. – MilkaHDECHALLENGE003
is as I am also looking to solve the questions on this dev test. Can I ask, did you get the job, do you currently work there? If so, could we have a chat over email to talk about the role and company a little more? Thanks. – Giovannagiovanni