> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fintoc.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Genera llaves JWS

> Genera un par de llaves JWS y firma tus solicitudes a la API de Transfers de Fintoc para mover dinero de forma segura entre cuentas en México y Chile.

Para hacer solicitudes a nuestros endpoints de Transferencias que mueven dinero, necesitarás incluir una JSON Web Signature (JWS). JWS es un estándar para firmar digitalmente datos a fin de garantizar su integridad y autenticidad. Las acciones protegidas incluyen [crear transferencias salientes](/es/api/transfers-api/transfers/transfers-create) y devolver transferencias entrantes.

# Generar llaves JWS

Para firmar una llamada a la API, debes seguir estos pasos:

## Generar un par de llaves JWS pública y privada

Ejecuta este comando en tu terminal:

```bash theme={null}
openssl genrsa -out private_key.pem 2048
openssl rsa -in private_key.pem -outform PEM -pubout -out public_key.pem.pub
```

Esto genera dos archivos:

* `private_key.pem` que contiene tu llave privada
* `public_key.pem` que contiene tu llave pública

Cada archivo debería verse similar a este ejemplo:

```text public_key.pem theme={null}
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoPPSwkMAHrLy6ZY+cOIP
jl6PxkrKJBicwMBMgFPf0Vtqe6QWepeOWXQuLgW+cSDI0KBjk8eZQEVB7GY3OwOl
DcknxUkaVueEvsDiY74xeC1iN2Gfb6HXd2JqgDWdWy/HNv2eUe9kmsSPSSgruA8Y
DvR6lpjPvAEJHP4Sg/B+9c0gBTDmqadL8UD291D7JbHmG4lIBT5NbhpOVnSBN0aC
R6ioxWz+VJoz68qsxHQ69TYhl8/jG79ocvZsZEWCWc/Kv7SP6/cPJHu0oGWVZwa4
5BtPLeMQ9ZleHdV6RCUbxFXKzbZF5fKQ+z5NWk+hMz5TCs4jwmg1nodWyW+bL7K9
YQIDAQAB
-----END PUBLIC KEY-----
```

```text private_key.pem theme={null}
-----BEGIN PRIVATE KEY-----
<redacted example private key content>
-----END PRIVATE KEY-----
```

<Warning>
  **Nunca compartas tu llave privada JWS**

  Tu llave privada JWS es altamente sensible y debe permanecer confidencial en todo momento. Fintoc nunca te pedirá esta llave. Si alguien obtiene acceso a tu llave privada, podría firmar solicitudes de transferencia maliciosas en tu nombre.
</Warning>

## Sube tu llave pública al Dashboard

1. Ve a [dashboard.fintoc.com](https://dashboard.fintoc.com)
2. Ve a la pestaña API Keys en la barra lateral
3. Si los productos que requieren JWS están activos para tu organización, verás una sección JWS Public Keys. Haz clic en el botón **"Add JWS Key"** y sube tu llave pública JWS.

# Generar firma

Ahora que cargaste tu llave pública en tu Dashboard de Fintoc, puedes generar una firma basada en la llave privada. Esto es necesario para asegurar la integridad y autenticidad de cada llamada a la API de Transferencias que mueve dinero.

## Usando nuestro SDK

Si estás usando Python o Node, nuestros [SDK de Python](https://github.com/fintoc-com/fintoc-python) y [SDK de Node](https://github.com/fintoc-com/fintoc-node) generan las firmas automáticamente por ti. Simplemente inicializa el cliente Fintoc con el argumento `jws_private_key` y el SDK se encarga del resto:

```python theme={null}
import os

from fintoc import Fintoc

# Provide a path to your PEM file
client = Fintoc("your_api_key", jws_private_key="private_key.pem")

# Or pass the PEM key directly as a string
client = Fintoc("your_api_key", jws_private_key=os.environ.get('JWS_PRIVATE_KEY'))

# You can now create transfers securely
```

```javascript Node theme={null}
const { Fintoc } = require('fintoc');

// Provide a path to your PEM file
const fintoc = new Fintoc('your_api_key', 'private_key.pem');

// Or pass the PEM key directly as a string
const fintoc = new Fintoc('your_api_key', process.env.JWS_PRIVATE_KEY);

// You can now create transfers securely
```

## Ejemplo paso a paso

Si quieres escribir tu propia implementación o usar otro lenguaje de programación, sigue estos pasos:

### Preparar el payload

Para la generación de la firma JWS, necesitamos trabajar con el string JSON exacto que se enviará en la solicitud HTTP. Esto típicamente se crea convirtiendo el objeto del cuerpo de la solicitud Outbound Transfer a un string JSON usando el serializador JSON de tu lenguaje.

```python theme={null}

import json

body = { ... } # your request body

raw_body = json.dumps(body)
```

```javascript theme={null}
const body = { ... } // your request body

const rawBody = JSON.stringify(body)
```

```ruby theme={null}
require 'json'

body = { ... } # your request body

raw_body = body.to_json
```

<Info>
  **El string JSON debe ser consistente**

  Al serializar el cuerpo de tu solicitud a JSON, debes usar exactamente el mismo string para dos propósitos:

  1. Crear la firma JWS
  2. Enviar como payload en tu solicitud HTTP

  Cualquier pequeña diferencia entre el JSON usado para crear la firma JWS y el payload en tu solicitud HTTP puede invalidar la firma.
</Info>

### Cargar la llave privada y configurar los headers

Carga tu llave privada desde el archivo PEM y configura los headers JWS. Los headers incluyen el algoritmo de firma (RS256), un [nonce](https://en.wikipedia.org/wiki/Nonce) único para prevenir firmas duplicadas, la marca de tiempo actual y la especificación de campos críticos.

```python theme={null}
# load the private key
with open('./private_key.pem', 'rb') as f:
    private_key = serialization.load_pem_private_key(
        f.read(),
        password=None
    )

# define jws headers
headers = {
    "alg": "RS256",                 # signing algorithm. must be "RS256"
    "nonce": secrets.token_hex(16), # unique string for each request
    "ts": int(time.time()),         # timestamp of the request
    "crit": ["ts", "nonce"]         # critical headers
}
```

```javascript theme={null}
// load the private key
const privateKey = readFileSync('./private_key.pem', 'utf8');

// define jws headers
const headers = {
  alg: 'RS256',                                  // signing algorithm. must be "RS256"
  nonce: crypto.randomBytes(16).toString('hex'), // unique string for each request
  ts: Math.floor(Date.now() / 1000),            // timestamp of the request
  crit: ['ts', 'nonce']                         // critical headers
};
```

```ruby theme={null}
# load the private key
private_key = OpenSSL::PKey::RSA.new(File.read('./private_key.pem'))

# define jws headers
headers = {
  'alg' => 'RS256',                # signing algorithm. must be "RS256"
  'nonce' => SecureRandom.hex(16), # unique string for each request
  'ts' => Time.now.to_i,           # timestamp of the request
  'crit' => ['ts', 'nonce']        # critical headers
}
```

#### Prevenir un ataque de replay

Fintoc usa los headers `nonce` y `ts` (timestamp) en su proceso de autenticación JWS para protegerse contra [ataques de replay](https://en.wikipedia.org/wiki/Replay_attack) y garantizar la integridad de las solicitudes.

El string `nonce` debe ser un valor único y aleatorio, y debe incluirse en cada solicitud, haciendo que cada firma sea distinta incluso si se envían los mismos datos varias veces. Fintoc se asegura de que cada `nonce` se use solo una vez, y rechazará cualquier solicitud que contenga un `nonce` duplicado.

El timestamp `ts` registra cuándo se creó la solicitud, y los servidores de Fintoc validan que esté dentro de una ventana de tiempo de 2 minutos para evitar el procesamiento de solicitudes obsoletas.

En conjunto, el nonce y el timestamp brindan una protección robusta al **asegurar que las solicitudes interceptadas o manipuladas no puedan reutilizarse, protegiendo tu integración contra actividades maliciosas**.

### Generar el input de firma

El input de firma para una JWS consiste en concatenar los `headers` codificados en base64url y el `raw_body` codificado en base64url con un punto `.` entre ellos, ambos sin padding:

```python theme={null}
# generate the procted section of the jws by base64 encoding the headers without padding
protected_base64 = base64.urlsafe_b64encode(
  json.dumps(headers).encode()
).rstrip(b'=').decode()

# generate the payload section of the jws by base64 encoding the raw_body without padding
payload_base64 = base64.urlsafe_b64encode(
  raw_body.encode()
).rstrip(b'=').decode()

# generate the signature input by concatenating the encoded protected and payload components
signing_input = f"{protected_base64}.{payload_base64}"
```

```javascript theme={null}
// generate the procted section of the jws by base64 encoding the headers without padding
const protectedBase64 = Buffer.from(JSON.stringify(headers))
  .toString('base64url');
  
// generate the payload section of the jws by base64 encoding the raw_body without padding
const payloadBase64 = Buffer.from(rawBody)
  .toString('base64url');

// generate the signature input by concatenating the encoded protected and payload components
const signingInput = `${protectedBase64}.${payloadBase64}`;
```

```ruby theme={null}
# generate the procted section of the jws by base64 encoding the headers without padding
protected_base64 = Base64.urlsafe_encode64(headers.to_json, padding: false)

# generate the payload section of the jws by base64 encoding the raw_body without padding
payload_base64 = Base64.urlsafe_encode64(raw_body, padding: false)

# generate the signature input by concatenating the encoded protected and payload components
signing_input = "#{protected_base64}.#{payload_base64}"
```

### Generar la firma del token JWS

Una vez que tengas el input de firma listo, crearás la firma criptográfica usando tu llave privada. El proceso involucra:

1. Firmar el input usando tu llave privada con:

   1. RSA con padding PKCS1v15
   2. Codificar la firma resultante en Base64URL (sin padding)
2. Codificar la firma resultante en Base64URL (sin padding)

```python theme={null}
# generate the raw signature by signing the signing_input 
signature_raw = private_key.sign(
     signing_input.encode(),
     padding.PKCS1v15(),
     hashes.SHA256()
)

# base64 encode the raw signature without padding
signature_base64 = base64.urlsafe_b64encode(signature_raw).rstrip(b'=').decode()
```

```javascript theme={null}
// generate the raw signature by signing the signing_input 
const signatureRaw = crypto.createSign('sha256')
  .update(signingInput)
  .sign({
    key: privateKey,
    padding: crypto.constants.RSA_PKCS1_PADDING
  });

// base64 encode the raw signature without padding
const signatureBase64 = Buffer.from(signatureRaw)
    .toString('base64url');
```

```ruby theme={null}
# generate the raw signature by signing the signing_input 
signature_raw = private_key.sign(OpenSSL::Digest::SHA256.new, signing_input)

# base64 encode the raw signature without padding
signature_base64 = Base64.urlsafe_encode64(signature_raw, padding: false)
```

### Opcional: Verificar el token JWS

Algunas bibliotecas pueden recodificar los payloads JSON con distinto espaciado u orden de claves, o cambiar su codificación. Para depurar la firma, puedes inspeccionar el token generado en [https://jwt.io](https://jwt.io) para verificar su contenido. También recomendamos verificar que el payload del token JWS sea igual al `raw_body` enviado en la solicitud HTTP.

```python theme={null}
token = f"{protected_base64}.{payload_base64}.{signature_base64}"
print(token)

payload = base64.urlsafe_b64decode(
  payload_base64 + '=' * (4 - len(payload_base64) % 4)
)
print(payload) # should be equal to raw_body sent in the http request
```

```javascript theme={null}
const token = `${protectedBase64}.${payloadBase64}.${signatureBase64}`
console.log(token)

const payload = Buffer.from(payloadBase64, 'base64url').toString()
console.log(payload) // should be equal to raw_body sent in the http request
```

```ruby theme={null}
token = "#{protected}.#{payload_base64}.#{signature}"
puts token

payload = Base64.urlsafe_decode64(payload_base64 + '=' * (4 - payload_base64.length % 4))
puts payload # should be equal to raw_body sent in the http request
```

### Construir el header Fintoc-JWS-Signature

Construye el header `Fintoc-JWS-Signature` concatenando el header protegido y la firma:

```python theme={null}
jws_signature_header = f"{protected_base64}.{signature_base64}"
```

```javascript theme={null}
const jwsSignatureHeader = `${protectedBase64}.${signatureBase64}`;
```

```ruby theme={null}
jws_signature_header = "#{protected_base64}.#{signature_base64}"
```

## Ejemplo completo

Aquí tienes un ejemplo completo de una función que lo une todo (deberás instalar las bibliotecas mencionadas para probarlo).

```python theme={null}
import base64
import json
import time
import secrets
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import serialization

def generate_jws_signature_header(raw_body):
    # Read private key
    with open('./private_key.pem', 'rb') as f:
        private_key = serialization.load_pem_private_key(
            f.read(),
            password=None
        )

    # Create headers
    headers = {
        'alg': 'RS256',
        'nonce': secrets.token_hex(16),
        'ts': int(time.time()),
        'crit': ['ts', 'nonce']
    }

    # Base64url encode without padding
    protected_base64 = base64.urlsafe_b64encode(
        json.dumps(headers).encode()
    ).rstrip(b'=').decode()
    
    payload_base64 = base64.urlsafe_b64encode(
        raw_body.encode()
    ).rstrip(b'=').decode()
    signing_input = f"{protected_base64}.{payload_base64}"

    # Create signature
    signature_raw = private_key.sign(
            signing_input.encode(),
            padding.PKCS1v15(),
            hashes.SHA256()
        )
    
    signature_base64 = base64.urlsafe_b64encode(signature_raw).rstrip(b'=').decode()

    # Debug output
    print(f"Token: {protected_base64}.{payload_base64}.{signature_base64}")
    payload = base64.urlsafe_b64decode(
        payload_base64 + '=' * (4 - len(payload_base64) % 4)
    )
    print(f"Payload: {payload.decode()}")

    return f"{protected_base64}.{signature_base64}"


body = { ... }
raw_body = json.dumps(body) # the exact payload to be sent in the http request

# signature that must be included in the 'Fintoc-JWS-Signature' request header
jws_signature_header = generate_jws_signature_header(raw_body)
```

```javascript theme={null}
import crypto from 'crypto';
import { readFileSync } from 'fs';

function generateJwsSignatureHeader(rawBody) {
  // Read private key
  const privateKey = readFileSync('./private_key.pem', 'utf8');

  const headers = {
    alg: 'RS256',
    nonce: crypto.randomBytes(16).toString('hex'),
    ts: Math.floor(Date.now() / 1000),
    crit: ['ts', 'nonce']
  };

  const protectedBase64 = Buffer.from(JSON.stringify(headers))
    .toString('base64url');
  
  const payloadBase64 = Buffer.from(rawBody)
    .toString('base64url');

  const signingInput = `${protectedBase64}.${payloadBase64}`;
  const signatureRaw = crypto.createSign('sha256')
    .update(signingInput)
    .sign({
      key: privateKey,
      padding: crypto.constants.RSA_PKCS1_PADDING
    });

  const signatureBase64 = Buffer.from(signatureRaw)
    .toString('base64url');

  // Debug output
  console.log(`Token: ${protectedBase64}.${payloadBase64}.${signatureBase64}`);
  console.log(`Payload: ${Buffer.from(payloadBase64, 'base64url').toString()}`);

  return `${protectedBase64}.${signatureBase64}`;
}


const payload = { ... }
const rawBody = JSON.stringify(payload) // the exact payload to be sent in the http request

// signature that must be included in the 'Fintoc-JWS-Signature' request header
const jwsSignatureHeader = generateJwsSignatureHeader(rawBody)
```

```ruby theme={null}
require 'base64'
require 'openssl'
require 'securerandom'
require 'json'

class JwsSignatureHeaderGenerator
  def self.generate(raw_body)
    private_key = OpenSSL::PKey::RSA.new(File.read('./private_key.pem'))

    headers = {
      'alg' => 'RS256',
      'nonce' => SecureRandom.hex(16),
      'ts' => Time.now.to_i,
      'crit' => ['ts', 'nonce']
    }

    protected = Base64.urlsafe_encode64(headers.to_json, padding: false)
    payload_base64 = Base64.urlsafe_encode64(raw_body, padding: false)

    signing_input = "#{protected}.#{payload_base64}"
    signature_raw = private_key.sign(OpenSSL::Digest::SHA256.new, signing_input)
    signature = Base64.urlsafe_encode64(signature_raw, padding: false)

    "#{protected}.#{signature}"

  end
end

body = { ... }
raw_body = body.to_json # the exact payload to be sent in the http request

# signature that must be included in the 'Fintoc-JWS-Signature' request header
jws_signature_header = JwsSignatureHeaderGenerator.generate(raw_body) 
```
