Skip to main content

Time-based Secret (TOTP) Generation

In some cases the Bank might not have access to Card Number, therefore unable to provide it in requests to MeaWallet. At the same time, MeaWallet as PCI-DSS approved authority can integrate and fetch card numbers from the bank’s 3rd party vendor (or another instance within the bank). In all such scenarios the bank will use CARD_ID and SECRET digitization type, where the bank provides virtual CARD_ID and SECRET. At the same time, MeaWallet will verify the SECRET and make a request to the 3rd party vendor to receive full card data.

This page explains how the bank can generate a SECRET and how MeaWallet can validate it.

Generation & Verification

SECRET is filled with One Time Password (OTP), which expires in time - TOTP (Time-Based One Time Password). MeaWallet is using algorithm defined in Internet Engineering Task Force (https://ietf.org) standard RFC 6238.

When the MeaWallet’s back-end receives an TOTP from a client, it computes the TOTP on its own, using the shared secret key, CARD_ID and its current Unix timestamp (not the Unix timestamp used by client) and compare the TOTPs: if they are generated within the same time-step they match and the validation succeeds.

If the client sends an TOTP close to the end of a time step, due to network latency or performance issues, the MeaWallet’s back-end may start processing the request in the following time-step, resulting in a validation failure. For this scenario, the MeaWallet’s back-end allow compare TOTPs not only in the current time step but also with the next and previous time-steps.

Setup

  1. MeaWallet and the Bank to exchange 256-bit shared secret key (KEY) in HEX encoding format, and 3-digit KEY ID.

    Sample (also key for TEST environment):
    KEY = 3132333435363738393031323334353637383930313233343536373839303132
    KEY_ID = 001

  2. MeaWallet and the Bank to agree on SECRET/OTP length (1-8).

    Default: 8 (recommended)

  3. Agree on hashing algorithm. Available options are SHA-256 and SHA-512.

    Default: SHA-512

  4. MeaWallet and the Bank to agree on a TIME interval the OTP is valid.

    Default: 60 seconds

Implementation

  1. Whenever a user shows intention to digitize (or push-provision) a card from a mobile app, mobile app calls the Bank's back-end to get TOTP.

  2. The Bank’s back-end prepares FINAL_KEY for TOTP calculation.

    The Bank retrieves KEY in HEX and concatenates with CARD_ID in HEX.
    FINAL_KEY (hex) = KEY (hex) + CARD_ID (hex)
    FINAL_KEY must not exceed 64 bytes (128 characters in HEX)

Example:
KEY in HEX = 3132333435363738393031323334353637383930313233343536373839303132
CARD_ID = ABCD-EFGH-123
CARD_ID in hex = 414243442D454647482D313233
FINAL_KEY = 3132333435363738393031323334353637383930313233343536373839303132414243442D454647482D313233

Example with too long CARD_ID:
KEY in HEX = 3132333435363738393031323334353637383930313233343536373839303132
CARD_ID = THIS_IS_TOO_LONG_KEY_AND_WE_NEED_TO_TRIM_IT_FROM_THE_LEFT_TO_BE_32_BYTES
CARD_ID in hex = 544849535f49535f544f4f5f4c4f4e475f4b45595f414e445f57455f4e4545445f544f5f5452494d5f49545f46524f4d5f5448455f4c4546545f544f5f42455f33325f4259544553
FINAL_KEY = 3132333435363738393031323334353637383930313233343536373839303132544849535f49535f544f4f5f4c4f4e475f4b45595f414e445f57455f4e4545445f544f5f5452494d5f49545f46524f4d5f5448455f4c4546545f544f5f42455f33325f4259544553

the FINAL_KEY is 104 bytes (208 characters in HEX), this is too long and must be trimmed to 64 bytes (128 characters in HEX) FINAL_KEY (trimmed) = 3132333435363738393031323334353637383930313233343536373839303132544849535f49535f544f4f5f4c4f4e475f4b45595f414e445f57455f4e454544

  1. Generate TOTP using FINAL_KEY. For that purpose any TOTP/HOTP library can be used. See useful scripts below this description.

    • Selected library should use Unix (epoch) time to generate the TOTP. Algorithm can be verified using Test Vectors provided down below. An incorrect time is common reason of failure.
  2. Format SECRET value.

    SECRET consists of following parts:

    • Key ID (3 digits) - used by MeaWallet to select the correct shared secret key from its database used to validate the TOTP. This allows us to rotate keys without any downtime and optionally to segregate keys between the card ranges or in any other way.

    • Separator (1 symbol) - separator of key ID and TOTP. Constant value # (ASCII 0x2D).

    • TOTP (8 digits) - Time-based One Time Password. Calculated as described in this document (Remember: the TOTP is calculated combining shared secret key with CARD_ID).

Sample: 001#12345678

  1. Mobile app sends CARD_ID and SECRET to MeaWallet (might be a method from MeaWallet’s mobile SDK).
  2. MeaWallet computes the TOTP on its own, using the shared secret key, CARD_ID and its current Unix timestamp (not the Unix timestamp used by client) and compare the TOTPs.
info

Samples

#-- 1. Import necessary libraries

import binascii
import base64
import datetime
#from datetime import timezone
import calendar
# pip install pyotp
import pyotp
import time

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, hmac

#-- 2. Define utility functions

def calculateTotp(base32SecretStr, digits=8, interval=60, digest='sha512',time=None):
totp = pyotp.TOTP(base32SecretStr, digits=digits, interval=interval, digest=digest)
if time==None:
return totp.now()
else:
return totp.at(time)

def hex2bytes(hexData):
return bytes(bytearray.fromhex(hexData.replace(" ","")))

def bytes2hex(byteArray, addSpace=False):
hexVal = binascii.hexlify(byteArray).upper().decode("utf-8")
res = ''
if addSpace:
for i in range(int(len(hexVal)/2)):
res += hexVal[i*2:i*2+2]+' '
res = res[:-1]
else:
res = hexVal
return res

def calcHmac512(keyBytes, dataBytes):
h = hmac.HMAC(keyBytes, hashes.SHA512(), backend=default_backend())
h.update(dataBytes)
h512 = h.finalize()
return bytes2hex(h512)

#-- 3. Configure the keys and input data.

TOTP_KEY_HEX = '3132333435363738393031323334353637383930313233343536373839303132'
TOTP_SHA = 'sha512' # Possible values: sha256, sha512
TOTP_LEN = 8 # Digits
TOTP_WINDOW_SIZE = 60 # Seconds
CARD_ID = "335688998"
TIME = datetime.datetime.fromtimestamp(59)
#-- 4. Prepare the inputs.

print('Unix time:', TIME.astimezone(datetime.timezone.utc))
epochSeconds = int(time.mktime(TIME.timetuple()))
print('Epoch seconds:', epochSeconds)
timeStep = int(epochSeconds / TOTP_WINDOW_SIZE)
print('Unix Time /', str(TOTP_WINDOW_SIZE) + ':', timeStep)

keyInHex = TOTP_KEY_HEX
print('Key 128-bit in HEX:', keyInHex)
cardIdInHex = bytes2hex(CARD_ID.encode())
print('CARD_ID:', CARD_ID)
print('CARD_ID in HEX:', cardIdInHex)
cardIdInHex = cardIdInHex[:64]
print("CARD_ID in HEX (trimmed to 64): "+cardIdInHex)
secretKeyHex = keyInHex + cardIdInHex
print('FINAL Key in HEX:', secretKeyHex)
secretKeyInBytes = hex2bytes(secretKeyHex)
keyBytes = base64.b32encode(secretKeyInBytes)
keyBase32Str = keyBytes.decode()
print('FINAL Key in Base32:', keyBase32Str)

#-- 5. Calculate TOTP
timeInBytes = hex2bytes('0000000003f940aa')
h512Base32 = calcHmac512(keyBytes, timeInBytes)
print('HMAC (Base32):', h512Base32)
SECRET = "001#" + calculateTotp(keyBase32Str, digits=TOTP_LEN, interval=TOTP_WINDOW_SIZE, digest=TOTP_SHA, time=None)


print('---------')
print('SECRET =', SECRET, '( current -', datetime.datetime.now(), ')')
SECRET = "001#"+calculateTotp(keyBase32Str, digits=TOTP_LEN, interval=TOTP_WINDOW_SIZE, digest=TOTP_SHA, time=TIME)
print('SECRET =', SECRET, '(', TIME.astimezone(datetime.timezone.utc), ')')

Test Vectors

1) SHA256
Timestep window size = 30
Card secret length = 8
CARD_ID (plain text) = 335688998
CARD_ID (hex) = 333335363838393938
Key (hex) = 3132333435363738393031323334353637383930313233343536373839303132
Total Key (hex) = 3132333435363738393031323334353637383930313233343536373839303132333335363838393938
Mode = SHA256

+-------------+----------+----------+
| Time (sec) | Timestep | TOTP |
+-------------+----------+----------+
| 59 | 1 | 66549790 |
| 1111111109 | 37037036 | 52828544 |
| 1234567890 | 41152263 | 88543363 |
| 2000000000 | 66666666 | 58932909 |
+-------------+----------+----------+

2) SHA512
Timestep window size = 60
Card secret length = 8
CARD_ID (plain text) = 516906811
CARD_ID (hex) = 333335363838393938
Key (hex) = 31323334353637383930313233343536373839303132333435363738393031323334353637383930313233343536373839303132333435363738393031323334
Total Key (hex) = 31323334353637383930313233343536373839303132333435363738393031323334353637383930313233343536373839303132333435363738393031323334

Mode = SHA512
Card ID: 115225348
CardId in HEX: 313135323235333438
CardId in HEX (trimmed to 64): 313135323235333438
Key in HEX: 3132333435363738393031323334353637383930313233343536373839303132
FINAL Key in HEX: 3132333435363738393031323334353637383930313233343536373839303132313135323235333438
TOTP Key in Base32: GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDCMJVGIZDKMZUHA======

+-------------+----------+----------+
| Time (sec) | Timestep | TOTP |
+-------------+----------+----------+
| 1163214254 | 19386904 | 19304652 |
| 1111111109 | 18518638 | 85949906 |
| 1234567890 | 20576251 | 05376914 |
| 2000000000 | 33333513 | 81567743 |
+-------------+----------+----------+

MeaWallet Time-based Secret Library for Backend

MeaWallet provides Java library for Time-based Secret calculations, in case this library is not fit for you, you are free to implement Time-based Secret calculations your self as documented above.

Requirements:

  • Java 1.8
repositories {
maven {
url 'https://nexus.ext.meawallet.com/repository/<repository>'

credentials {
username '<user>'
password '<password>'
}
}
}

dependencies {
...
compile 'com.meawallet.commons:mw-totp:<version>'
...
}

Generation

CardSecretKey sha512key = CardSecretKey
.builder()
.sharedKey("shared-key-value")
.cardId("card-id-value")
.algorithm(com.meawallet.commons.totp.HmacHashAlgorithm.SHA_512);

TimestepProvider timestepProvider = new TimestepProvider(Clock.systemUTC());
CardSecretGenerator generator = new CardSecretGenerator(timestepProvider);
CardSecretGenerationConfig config = CardSecretGenerationConfig
.builder(sha512key)
.timestepWindowSize(Duration.ofSeconds(60))
.cardSecretLength(8);

GeneratedCardSecret cardSecret = generator.generate(config);
String result = cardSecret.getSecret();

Verification

CardSecretKey sha512key = CardSecretKey
.builder()
.sharedKey("shared-key-value")
.cardId("card-id-value")
.algorithm(com.meawallet.commons.totp.HmacHashAlgorithm.SHA_512);

CardSecretVerificationConfig config = CardSecretVerificationConfig
.builder(sha512key)
.allowedFutureValidationWindows(futureSteps)
.allowedPastValidationWindows(pastSteps)
.timestepWindowSize(Duration.ofSeconds(60))
.cardSecretLength(8);

TimestepProvider timestepProvider = new TimestepProvider(Clock.systemUTC());
CardSecretGenerator generator = new CardSecretGenerator(timestepProvider);
CardSecretVerifier verifier = new CardSecretVerifier(generator);

VerificationResult result = verifier.verify("card-secret-value", config);
boolean isSuccess = result.isSuccess();