Эта проблема связана с TOTP, как указано в RFC6238 здесь: https://tools.ietf.org/html/rfc6238#section-1.2.
Я должен реализовать RFC6238 для генерации 10-значного пароля TOTP, который позже будет использоваться в запросе POST. Пример ввода и вывода для TOTP должен быть таким:
Пример ввода:
- Общий ключ: [email protected] (без двойных кавычек)
- Используемая хеш-функция:
HMAC-SHA-512
- T0 = 0, временной шаг = 30 секунд (согласно RFC6238)
- Ожидаемый TOTP из 10 цифр
Пример вывода:
Успешно сгенерированный TOTP:
1773133250
, на время понедельника, 17 марта 2014 г., 15:20:51 GMT
Запрос имени пользователя / пароля авторизации POST в кодировке base64: bmluamFAZXhhbXBsZS5jb206MTc3MzEzMzI1MA ==
(Я декодировал образец авторизации POST как '[email protected]: 1773133250', поэтому я могу сказать, что образец вывода TOTP - 1773133250)
После попытки создать свой собственный сценарий в соответствии со спецификацией rfc6238 я не могу получить тот же результат для образца ввода, что и выше. Я попытался использовать другие доступные онлайн-модули TOTP, доступные онлайн (в основном на Python), чтобы обнаружить, что они генерируют тот же результат, что и созданный мной скрипт. Наконец, я попробовал код Java, приведенный в примере RFC6238, и получил тот же результат, что и мой скрипт, а именно:
Попытка ввода:
Hex-кодированное начальное число для HMAC512: 6E696E6A61406578616D706C652E636F6D4844454348414C4C454E4745303033 + 6E696E6A61406578616D706C652E636F6D4844454348414C30C30334 ;E4745
Введено время 1395069651L, что соответствует времени, полученному в виде выходных данных.
Результат попытки (такие же выходные данные пользовательского скрипта, других модулей Python и реализации Java, указанные в документации RFC6238):
Создан TOTP: 0490867067
Вот код, который я впервые использовал при попытке сгенерировать TOTP в 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
Вот второй код на Java, который по сути является модифицированной версией реализации Java, найденной в 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);
}
}
}
Обратите внимание, что для измененного кода Java RFC на выходе будет несколько дат / времени, перечисленных в массиве testTime [], однако целевое время по Гринвичу из образца ввода задачи также включено сюда. Тестирование в моем Ubuntu показало тот же результат, что и в моем скрипте Python.
Я считаю, что выполнил инструкции, данные в задании. Я использовал настоящий RFC-код Java, чтобы обнаружить, что он не генерирует такой же результат, как тот, который указан в задаче. Я связался с провайдером задачи, чтобы спросить, есть ли ошибка, но они говорят, что это правильно.
Возможно, мне здесь что-то не хватает, например, как поставщик задач на самом деле шифрует общий ключ?
1234567890
и проверьте, какое значение будет сгенерировано в данный момент. Затем сравните его с тем, что образец кода генерирует в данное время. Это покажет, есть ли разница между алгоритмом, описанным в RFC6238, и фактической реализацией, которую вы пытаетесь использовать. - person zloster   schedule 06.04.2017HDECHALLENGE003
, так как я также ищу ответы на вопросы этого теста разработчика. Могу я спросить, вы получили работу, работаете ли вы сейчас? Если да, можем ли мы поговорить по электронной почте, чтобы немного подробнее поговорить о роли и компании? Спасибо. - person JamesG   schedule 19.09.2019