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 ( 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.


  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


  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.

    • 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)

KEY in HEX = 3132333435363738393031323334353637383930313233343536373839303132
CARD_ID in hex = 414243442D454647482D313233
FINAL_KEY = 3132333435363738393031323334353637383930313233343536373839303132414243442D454647482D313233

Example with too long CARD_ID:
KEY in HEX = 3132333435363738393031323334353637383930313233343536373839303132
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. (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

  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.


#-- 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:

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]
res = hexVal
return res

def calcHmac512(keyBytes, dataBytes):
h = hmac.HMAC(keyBytes, hashes.SHA512(), backend=default_backend())
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)

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('SECRET =', SECRET, '( current -',, ')')
SECRET = "001#"+calculateTotp(keyBase32Str, digits=TOTP_LEN, interval=TOTP_WINDOW_SIZE, digest=TOTP_SHA, time=TIME)
print('SECRET =', SECRET, '(', TIME.astimezone(datetime.timezone.utc), ')')

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.


  • Java 1.8
repositories {
maven {
url '<repository>'

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

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


CardSecretKey sha512key = CardSecretKey

TimestepProvider timestepProvider = new TimestepProvider(Clock.systemUTC());
CardSecretGenerator generator = new CardSecretGenerator(timestepProvider);
CardSecretGenerationConfig config = CardSecretGenerationConfig

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


CardSecretKey sha512key = CardSecretKey

CardSecretVerificationConfig config = CardSecretVerificationConfig

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();