TOTP secret generation

How to generate time-based secret

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

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.

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

  • In case of Activation Code generation for VISA push-provisioning (like TAV in MDES):

    The Bank retrieves KEY in HEX and concatenates with LAST 4 digits from PAN in HEX.

    FINAL_KEY (hex) = KEY (hex) + PAN_LAST_4 (hex)

  • In case of CARD_ID and SECRET:

    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

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.
  • Format SECRET value. (This step is only applicable for CARD_ID and SECRET generation, and is not applicable for VISA Activation Code generation)

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

Mobile app sends CARD_ID and SECRET to MeaWallet.

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

To keep in mind

Keep in mind

Samples

MeaWallet secret

				
					#-- 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), ')')				
			

TOTP

Test vectors

MeaWallet Time-based Secret library for usage in the 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-developer.meawallet.com/content/groups/mtp-<user>-group'

        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();				
			
On this page