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

> Aprende a mover dinero de manera segura con Fintoc

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/reference/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:

```
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-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDEbZHV0ODosbFn
BqcUYiRhlQWEJ9V9s5WfuFMv0n9hNWNFSBYiup+yuCNVW11vPQ7h6VUAWP8LFgmc
jsyjATlIsJDFDXXOQMyxAS2is2vZpot5QblcJMUxxMmAkUCJtpo7QSLKEPk2gRu4
tsQpzqnxCYDCnJOAHcClNrMIVJuczn/GMCm68IcAeNP8HQy9D20ER+w1tONoIzJ3
bQF0WkbtgJMYoQAq6UL3q0ws7ODvXo/ZcrFrY7q4skJopI+pz3qsGeSJX0PWjH4a
o+C9n5uYRXBxtH3nhnkSQdRZCk9fzId9+ObKrMKYuDxBeFqFIjZEh4pZ9lCPaHGK
qHwv3nLFAgMBAAECggEAHJnRpr7ryKX67UPoNw0VPAotS/Fa4hsweZmmrytotbhG
1JMq+fKPhz/NkUOk5qoOzTEi2dKbjDswuhWG0WM/uohPBAoyMY5433sK8IpMdVwN
KeI6gaKu/dCoAGrl6UdnzKHu1VpEVz3UUgB2rpmzX+/gyjVvOrPaVZQR3HApWlrr
qa/hmsiisCD1Bj7RreVAC6XfZCr8iyomNmX/Hz7Vx6s3HlBHzvQtxkwjjAPYu4SW
VsDAZESJNcOrZ8xNervaR3X+lLaF1DS/ntawPz6alAb46IyVgFU0K+rJn47MOTsi
gNSZqHV1TtTu7+jPkbF+LHHBYpYb4CSqyU41LKhDXwKBgQDk9smFRPjuSUiMGfI2
FCqwUmiYlAaEOHFLhvCapq2GfPjj1rIJMtqwox9fqKvHmTdiLjQ9+esyAbEG3CIW
njgsUsZspQOmCOqjuk0GsF9nsl/blzZrsytyN54xoRSECuecId6m31pQyJxoG1Y4
wqqAVObnepWb311xLIx/RI7NowKBgQDbn0WAuOv7JsXn/0CcZfzsbGRIMEkDLYe4
KiVOWALd6MijqtgyzRRwz2Qu7+AEfs0i4A0ZIOInZ2y5qkTeqpMNtqM9A4q5YClB
XTt/vUFxCLekL0s+zqXH9D87sTqEtTVNHpyJem3velXPDlSwR6MNyK0TRR7izBAg
mfYYSWV0dwKBgH6RfazSB9mRYS0xWpdSZpa5t2BA06lbmiVqHq8e3GWvx9YK5Lf5
CLMEOV+j2fGoXNlFOVPZR46JKNbl8WIXbG30BAQi4/VwkGSZo+LCtLqZ/CtjV44J
qUamQCinJrQnYwkIIBCW/1IQ04UpN2yBD8eJJ2tmdDWKMBlTywa/W0GJAoGBAJ12
WFKuQyNS7Vok/KIlzW2FWXEYjYClyEUWkqDVIVkRaalO+KuTtjAbweyVN7yBXXq/
wSRfG0a9NIr5tV8gVUbjx64bN/8pHusqeVpgyubMJT6mWgCyENKIID4gF6DGe2zL
odg/20p0H8nQsI+jDRj45H6IdFiPjpCRUoyfMwqJAoGBAJawh68qY9eQYWPwSq4g
S1RnrSmiamTm3zPqzeEZKcZOfrQz+W8q3Woq90HkupglXspL6bar6rZnuhcqY1xU
5m7IDq3fFRjfvsNDft5YJrtLFsJ/Uu6UHVnOa9K53aUJczMZ3CetqhJZmYvR9BPV
Yfo0J11kYXclnb1wH0svjZEA
-----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
```

```node 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)
```

```node 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
}
```

```node 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}"
```

```node 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()
```

```node 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
```

```node 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}"
```

```node 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)
```

```node 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 = JwsSignatureGenerator.generate(raw_body) 
```
