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

# Generar llaves JWS

> Aprende a mover dinero de forma segura con Fintoc

Para hacer solicitudes a nuestros endpoints de Transfers que mueven dinero, deberás incluir una JSON Web Signature (JWS). JWS es un estándar para firmar digitalmente datos y garantizar su integridad y autenticidad. Las acciones protegidas incluyen crear transferencias salientes 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 JWS privada**

  Tu llave JWS privada 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 maliciosamente solicitudes de transferencias 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 JWS pública.

# 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 garantizar la integridad y autenticidad de cada llamada a la API de Transfers 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 automáticamente las firmas por ti. Simplemente inicializa el cliente de Fintoc con el argumento `jws_private_key`, y el SDK se encargará 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 generar la firma JWS, necesitamos trabajar con el string JSON exacto que será enviado en la solicitud HTTP. Esto normalmente se crea convirtiendo el objeto del cuerpo de la solicitud de Outbound Transfer en 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. Enviarlo 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, el timestamp 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 la solicitud.

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ía la misma información 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 2 minutos para evitar el procesamiento de solicitudes antiguas.

Juntos, 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 signing input

El signing input de un JWS consiste en concatenar los `headers` codificados en base64url y el `raw_body` codificado en base64url con un punto `.` entre ambos, 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}.#{payload_base64}"
```

### Generar la firma del token JWS

Una vez que tengas el signing input 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 en Base64URL la firma resultante (sin padding)
2. Codificar en Base64URL la firma resultante (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 librerías pueden re-codificar payloads JSON con espacios o ordenamiento de keys distintos, 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í un ejemplo completo de una función que junta todo (tendrás que instalar las librerías mencionadas para probarla).

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