HomeGuíasAPI ReferenceChangelog
Log In
API Reference

Encriptación de la data

RSA 2048 y AES 256: cómo encriptar los datos de tarjeta antes de enviarlos.

Encriptar los datos de tarjeta antes de enviarlos es opcional, pero recomendado.

Los endpoints que reciben card_information aceptan tres formas de request:

  1. El body completo en claro.
  2. El body en claro con card_information encriptado.
  3. El body completo encriptado.

En los dos últimos casos debes enviar además el parámetro encryption_key: es el identificador con el que Toku guardó la llave privada al generarla.

🚧

Para el body completo, usa AES

RSA 2048 con PKCS1_OAEP cifra como máximo 214 bytes por operación. Un card_information cabe; un body completo —con id_customer, id_account, id_invoices y la tarjeta— no, y la librería falla con ValueError: Plaintext is too long. Encripta el body completo con AES, o limítate a encriptar card_information si vas a usar RSA.

Protocolo de encriptación

Soportamos dos algoritmos:

  • RSA 2048 con PKCS1_OAEP
  • AES 256 en modo CBC (Cipher-Block Chaining)

Para saber más:

Cómo encriptar la data

RSA

Usa el public_key que devuelve Generar una llave pública de encriptación.

import base64
import json

from Crypto.Cipher import PKCS1_OAEP
from Crypto.PublicKey import RSA


def encrypt_data(public_key: bytes, data: dict) -> str:
    """Encrypts the provided data in json format using the provided public key"""
    str_data = json.dumps(data)
    key = RSA.import_key(public_key)
    cipher = PKCS1_OAEP.new(key)
    encrypted_data = cipher.encrypt(str_data.encode("utf-8"))
    return base64.b64encode(encrypted_data).decode("utf-8")

Ejemplo de uso

Un cobro único con los datos de tarjeta encriptados con RSA, de principio a fin:

import base64
import json
import uuid

import requests
from Crypto.Cipher import PKCS1_OAEP
from Crypto.PublicKey import RSA

X_ORG_ID = "<X-ORG-ID>"
X_API_KEY = "<SECURE-API-KEY>"
ID_ACCOUNT = "<ID-ACCOUNT>"
ENCRYPTION_KEY = str(uuid.uuid4())  # este valor tiene que ser único
SECURE_API_URL = "https://secure.api.trytoku.com"


def encrypt_data(public_key: bytes, data: dict) -> str:
    """Encrypts the provided data in json format using the provided public key"""
    str_data = json.dumps(data)
    key = RSA.import_key(public_key)
    cipher = PKCS1_OAEP.new(key)
    encoded_data = str_data.encode("utf-8")
    encrypted_data = cipher.encrypt(encoded_data)
    return base64.b64encode(encrypted_data).decode("utf-8")


def create_public_key(encryption_key: str) -> str:
    """Create the public key using the encryption_key"""
    url = f"{SECURE_API_URL}/public-encryption-keys?encryption_key={encryption_key}"
    headers = {"x-org-id": X_ORG_ID, "x-api-key": X_API_KEY}

    response = requests.post(url, headers=headers)
    response.raise_for_status()

    return response.json()["public_key"]


def send_one_time_payment(payload: dict) -> dict:
    """Send request to make a one time payment"""
    url = f"{SECURE_API_URL}/cards/onetime/entry-point"
    headers = {
        "x-org-id": X_ORG_ID,
        "x-api-key": X_API_KEY,
        "Content-Type": "application/json",
    }

    response = requests.post(url, headers=headers, json=payload)
    response.raise_for_status()

    return response.json()


payload = {
    "id_customer": "<ID-CUSTOMER>",
    "toku_product": "payment_orchestration_onetime",
    "id_account": ID_ACCOUNT,
    "transaction_external_id": "<TRANSACTION-EXTERNAL-ID>",
    "engine": "SAAS",
    "id_invoices": ["<ID-INVOICE>"],
    "encryption_key": ENCRYPTION_KEY,
}

CARD_INFORMATION = {
    "number": "<CARD-NUMBER>",
    "verification_value": "<CVV>",
    "month": "<MONTH>",
    "year": "<YEAR>",
    "full_name": "<CARD-HOLDER-NAME>",
}

public_key = create_public_key(ENCRYPTION_KEY)
payload["card_information"] = encrypt_data(public_key.encode(), CARD_INFORMATION)

send_one_time_payment(payload)

AES

Usa la secret_key que devuelve Generar una llave pública de encriptación. En AES la misma llave encripta y desencripta.

La secret_key viaja en base64: decodifícala antes de pasarla al cifrador, o AES la recibe como 44 caracteres en vez de 32 bytes y falla con Incorrect AES key length.

import base64
import json

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad


def encrypt_data(secret_key_b64: str, data: dict) -> str:
    """Encrypts the provided data in JSON format using AES-256 (CBC mode)"""
    secret_key = base64.b64decode(secret_key_b64)
    str_data = json.dumps(data)
    cipher = AES.new(secret_key, AES.MODE_CBC)

    ct_bytes = cipher.encrypt(pad(str_data.encode("utf-8"), AES.block_size))
    iv_bytes = bytes(cipher.iv)
    combined = iv_bytes + ct_bytes

    return base64.b64encode(combined).decode("utf-8")
🚧

El IV va por delante

El ciphertext que envías es IV + ciphertext en base64, en ese orden. Toku separa los primeros 16 bytes como IV para desencriptar.