Authentication Without Email / Password
Obtaining Authentication Keys
Client Id and Client Secret are available over the merchant portal in the Settings menu.
Authentication
Encryption is done using AES algorithm with PKCS7 padding, CBC mode and a key size of 256. Encryption of the body payload is as follows:-
var CryptoJS = require('crypto-js');
var data = CryptoJS.AES.encrypt(JSON.stringify({
merchantId: 'MERCHANT_ID',
totp:''
}), 'CLIENT_SECRET').toString();
pm.variables.set("encryptedPayload", data);
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
import hashlib
import base64
def generate_salt():
return get_random_bytes(8)
def evpkdf(passphrase, salt, key_size=32, iv_size=16):
"""
Derives a key and IV from the passphrase and salt using OpenSSL compatible method.
"""
d = d_i = b'' # Initialize empty bytes for key and IV derivation
while len(d) < key_size + iv_size:
d_i = hashlib.md5(d_i + passphrase.encode('utf-8') + salt).digest()
d += d_i
return d[:key_size], d[key_size:key_size + iv_size]
def pad(data):
"""Pads data to a multiple of AES block size (16 bytes)."""
padding_len = 16 - len(data) % 16
return data + chr(padding_len) * padding_len
def encrypt_data(data, passphrase, salt):
key, iv = evpkdf(passphrase, salt)
cipher = AES.new(key, AES.MODE_CBC, iv)
encrypted_data = cipher.encrypt(pad(data).encode())
return encrypted_data
def create_encrypted_payload(data, passphrase):
salt = generate_salt()
encrypted_data = encrypt_data(data, passphrase, salt)
encrypted_data_with_salt = b"Salted__" + salt + encrypted_data
return base64.b64encode(encrypted_data_with_salt).decode('utf-8')
# Data to be encrypted
data = {
'merchantId': MERCHANT_ID,
'totp': ''
}
# Convert the data to a JSON string
data_str = json.dumps(data)
# Create the encrypted payload
encrypted_payload = create_encrypted_payload(data_str, CLIENT_SECRET)
function encrypt($toEncrypt, $secret){
$salt = openssl_random_pseudo_bytes(8);
$salted = $dx = '';
while (strlen($salted) < 48) {
$dx = md5($dx . $secret . $salt, true);
$salted .= $dx;
}
$key = substr($salted, 0, 32);
$iv = substr($salted, 32, 16);
// encrypt with PKCS7 padding
return base64_encode('Salted__' . $salt . openssl_encrypt($toEncrypt . '', 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv));
}
using System;
using System.Security.Cryptography;
using System.Text;
using System.Linq;
public class EncryptionAES
{
private static readonly Random SecureRandom = new Random();
public static byte[] EvpKDF(byte[] password, byte[] salt, int keySize = 48, string hasher = "MD5", int iterations = 1)
{
byte[] keyMaterial = new byte[keySize];
int generatedBytes = 0;
byte[] block = null;
using (var md = MD5.Create())
{
while (generatedBytes < keySize)
{
md.Initialize();
if (block != null)
md.TransformBlock(block, 0, block.Length, null, 0);
md.TransformBlock(password, 0, password.Length, null, 0);
md.TransformFinalBlock(salt, 0, salt.Length);
block = md.Hash;
for (int i = 1; i < iterations; i++)
{
block = md.ComputeHash(block);
}
int remaining = Math.Min(block.Length, keySize - generatedBytes);
Array.Copy(block, 0, keyMaterial, generatedBytes, remaining);
generatedBytes += remaining;
}
}
return keyMaterial;
}
public static string EncryptAES(string data, string key)
{
using (var aes = Aes.Create())
{
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
byte[] salt = new byte[8];
SecureRandom.NextBytes(salt);
byte[] derivedKey = EvpKDF(Encoding.UTF8.GetBytes(key), salt);
byte[] keyBytes = derivedKey.Take(32).ToArray();
byte[] ivBytes = derivedKey.Skip(32).Take(16).ToArray();
aes.Key = keyBytes;
aes.IV = ivBytes;
using (var encryptor = aes.CreateEncryptor())
using (var ms = new System.IO.MemoryStream())
{
ms.Write(Encoding.ASCII.GetBytes("Salted__"), 0, 8);
ms.Write(salt, 0, salt.Length);
using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
using (var sw = new System.IO.StreamWriter(cs))
{
sw.Write(data);
}
return Convert.ToBase64String(ms.ToArray());
}
}
}
}
//how to use
var data = new { merchantId = "861331d4-aa85-42c2-8a4f-a8e78043809a",totp=""};
string toEncrypt = JsonSerializer.Serialize(data);
string secret = "SECRET";
string encrypted = EncryptionAES.EncryptAES(toEncrypt, secret);
Console.WriteLine("Encrypted: " + encrypted);
If we provide the clientSecret as string it will not use it as a key but a passphrase to derive the key value using a KDF. The default KDF for this library is similar to the open SSL EVP function. (for reference: https://www.openssl.org/docs/manmaster/man3/EVP_BytesToKey.html) This function generates the actual AES keys using MD5 hashing and uses 1 iteration.
/**
* based on: www.openssl.org/docs/crypto/EVP_BytesToKey.html
*/
fun evpKDF(password: ByteArray, salt: ByteArray, keySize: Int = 48, hasher: String = "MD5", iterations: Int = 1): ByteArray {
val keyMaterial = ByteArray(keySize)
var generatedBytes = 0
var block: ByteArray? = null
val md = MessageDigest.getInstance(hasher)
while (generatedBytes < keySize) {
md.reset()
if (block != null)
md.update(block)
md.update(password)
block = md.digest(salt)
for (i in 1 until iterations)
block = md.digest(block)
val remaining = block!!.size.coerceAtMost(keySize - generatedBytes)
System.arraycopy(block, 0, keyMaterial, generatedBytes, remaining)
generatedBytes += remaining
}
return keyMaterial
}
fun encryptAES(data: String, key: String): String {
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
val salt = ByteArray(8)
secureRandom.nextBytes(salt)
val derivedKey = evpKDF(key.toByteArray(), salt)
val secretKey = SecretKeySpec(derivedKey.sliceArray(0..31), "AES")
val iv = IvParameterSpec(derivedKey.sliceArray(32..47))
cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv)
val encrypted = cipher.doFinal(data.toByteArray())
val encryptedWithIv = "Salted__".toByteArray() + salt + encrypted
return Base64.getEncoder().encodeToString(encryptedWithIv)
}
Generating access and refresh token using encrypted payload and clientId
POST
api/auth/generate-auth-token
This endpoint is used for generating access and refresh tokens without using merchant email and password. It requires clientId and encrypted string generated from the encryption logic discussed above.
Request Body
{
"ok": true,
"result": {
"access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImM1MzlkZWVjLTYwNDktNDZlMC1hZTJjLTUwZTJhNjQ2ZmMzMSIsImlhdCI6MTY2MjczOTczOSwiZXhwIjoxNjYyODI2MTM5fQ.AkvmP2oTAfj91w5w9arOAsiAAnfg1o-Ia-3b3PkZSaw",
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImM1MzlkZWVjLTYwNDktNDZlMC1hZTJjLTUwZTJhNjQ2ZmMzMSIsImlhdCI6MTY2MjczOTczOSwiZXhwIjoxNjY1MzMxNzM5fQ.CFjoO4XwZpuQNBpkVoQtpUbu1_yYVIFoMO5MqDWYGeg",
"status": 2
}
}
Last updated