Qué hay dentro de un .p12
Es un contenedor (formato PKCS#12) que guarda tu certificado y tu clave privada, protegido con una contraseña. El certificado es público. La clave privada no lo es. Con ambas puedes firmar.
Errores que vemos con más frecuencia
| ERROR | POR QUÉ ES PELIGROSO |
|---|---|
| Subir el .p12 a un repositorio de código | Queda en el historial para siempre, aunque lo borres después |
| Guardar el archivo y su clave en el mismo lugar | La contraseña existe para proteger el archivo. Si viajan juntos, no protege nada |
| Dejarlo en una carpeta del servidor sin cifrar | Cualquiera con acceso al disco o a un respaldo lo obtiene |
| Enviarlo por correo o por chat | Queda copiado en servidores y dispositivos que no controlas |
| Usar la misma contraseña para todos | Si se filtra una, se filtran todas |
| Escribir la clave en los registros (logs) | Los logs se copian, se envían y se retienen |
El modelo recomendado
- Cifra el archivo en reposo Guárdalo siempre cifrado, nunca en texto plano.
- Separa el archivo de su clave El archivo va en un almacén; la clave, en un gestor de secretos distinto.
- Usa cifrado de sobre (envelope encryption) Un KMS protege una clave maestra que nunca sale de él. Esa clave maestra cifra una clave de datos, y la clave de datos cifra tu archivo.
- Descifra solo en memoria, solo al firmar El archivo descifrado no debe tocar el disco ni los registros.
- Da el mínimo acceso Solo el servicio que firma puede leer el certificado, y solo ese.
- Audita cada uso Registra quién y cuándo accedió al certificado.
Este ejemplo muestra la parte de cifrar y descifrar el archivo con AES-256-GCM, que además detecta si alguien lo alteró:
import crypto from "node:crypto";
// En producción, la clave de datos la genera y la protege un KMS (cifrado de sobre).
// Aquí se recibe como parámetro para mostrar solo el cifrado del archivo.
export function cifrar(contenido, claveDatos) {
const iv = crypto.randomBytes(12);
const cifrador = crypto.createCipheriv("aes-256-gcm", claveDatos, iv);
const cifrado = Buffer.concat([cifrador.update(contenido), cifrador.final()]);
return { iv, etiqueta: cifrador.getAuthTag(), cifrado };
}
export function descifrar({ iv, etiqueta, cifrado }, claveDatos) {
const descifrador = crypto.createDecipheriv("aes-256-gcm", claveDatos, iv);
descifrador.setAuthTag(etiqueta);
return Buffer.concat([descifrador.update(cifrado), descifrador.final()]);
}Si tienes varios clientes (multi-tenant)
- Una clave por cliente Así un incidente en uno no expone a los demás.
- Aislamiento de acceso El proceso que firma para el cliente A no debe poder leer el certificado del cliente B.
- Offboarding Cuando un cliente se va, elimina su certificado y su clave de datos, y confirma que también salen de los respaldos.
Ciclo de vida del certificado
- Alta Verifica el archivo, cífralo y registra su fecha de vencimiento.
- Uso Descifra en memoria y firma. Nada más.
- Antes de vencer Avisa con anticipación para renovar. Un certificado vencido provoca firmas inválidas.
- Renovación Carga el nuevo y retira el anterior.
- Baja Elimina el certificado, su clave y sus copias.
Cuánta seguridad necesitas
| ESCENARIO | QUÉ USAR |
|---|---|
| Poco volumen y un solo certificado | Un gestor de secretos y cifrado en reposo son suficientes |
| Varios clientes o volumen alto | KMS con cifrado de sobre y una clave por cliente |
| Exigencias regulatorias altas | Módulos de seguridad de hardware (HSM), donde la clave privada nunca sale del dispositivo |
Cómo lo hace Flashpi
Conclusión
Un .p12 mal guardado es una puerta abierta a tu identidad tributaria. Cífralo, sepáralo de su clave, descífralo solo cuando firmes y registra cada uso.