Advertencia importante
Advertencia importante
No es recomendable manipular llaves privadas FIEL/PFX en JavaScript del lado del cliente por razones de seguridad:
Las llaves privadas nunca deberían exponerse en el navegador
JavaScript no es el entorno más seguro para operaciones criptográficas
El SAT tiene protocolos específicos para firma
🛡️ Alternativa Segura: Usando Web Crypto API
Para contextos NO relacionados con FIEL/SAT, puedes usar la Web Crypto API:
<!DOCTYPE html>
<html>
<head>
<title>Firma Digital Básica</title>
</head>
<body>
<textarea id="documento" rows="10" cols="50" placeholder="Escribe tu documento aquí"></textarea>
<br>
<button onclick="generarLlaves()">Generar Llaves</button>
<button onclick="firmarDocumento()">Firmar Documento</button>
<button onclick="verificarFirma()">Verificar Firma</button>
<div id="resultado"></div>
<script>
let clavePrivada;
let clavePublica;
let firma;
async function generarLlaves() {
try {
const keyPair = await window.crypto.subtle.generateKey(
{
name: "RSASSA-PKCS1-v1_5",
modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"}
},
true,
["sign", "verify"]
);
clavePrivada = keyPair.privateKey;
clavePublica = keyPair.publicKey;
document.getElementById('resultado').innerHTML =
'<p style="color: green;">Llaves generadas correctamente</p>';
} catch (error) {
console.error('Error:', error);
}
}
async function firmarDocumento() {
if (!clavePrivada) {
alert('Primero genera las llaves');
return;
}
const documento = document.getElementById('documento').value;
const encoder = new TextEncoder();
const data = encoder.encode(documento);
try {
firma = await window.crypto.subtle.sign(
"RSASSA-PKCS1-v1_5",
clavePrivada,
data
);
// Convertir a base64 para mostrar
const firmaBase64 = btoa(String.fromCharCode(...new Uint8Array(firma)));
document.getElementById('resultado').innerHTML =
`<p>Firma generada: ${firmaBase64.substring(0, 100)}...</p>`;
} catch (error) {
console.error('Error firmando:', error);
}
}
async function verificarFirma() {
if (!clavePublica || !firma) {
alert('Primero genera llaves y firma un documento');
return;
}
const documento = document.getElementById('documento').value;
const encoder = new TextEncoder();
const data = encoder.encode(documento);
try {
const esValida = await window.crypto.subtle.verify(
"RSASSA-PKCS1-v1_5",
clavePublica,
firma,
data
);
document.getElementById('resultado').innerHTML =
`<p style="color: ${esValida ? 'green' : 'red'};">
Firma ${esValida ? 'VÁLIDA' : 'INVÁLIDA'}
</p>`;
} catch (error) {
console.error('Error verificando:', error);
}
}
</script>
</body>
</html>📝 Para Firma FIEL/SAT (Recomendado)
Opción 1: Integración con servicios del SAT
// Ejemplo conceptual - NO funcional directo
class FirmaSAT {
constructor() {
this.apiUrl = 'https://api.sat.gob.mx';
}
async firmarCFDI(datosCFDI, certificadoPFX, password) {
// En la práctica, esto se hace del lado del servidor
const response = await fetch('/api/firmar-sat', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
cfdi: datosCFDI,
pfx: certificadoPFX,
password: password
})
});
return await response.json();
}
}Opción 2: Usando librerías especializadas (Backend)
// backend/nodejs/firmador.js
const forge = require('node-forge');
const fs = require('fs');
class FirmadorSAT {
firmarXML(xml, pfxPath, password) {
try {
// Leer certificado PFX
const pfxData = fs.readFileSync(pfxPath, 'binary');
const pfxAsn1 = forge.asn1.fromDer(pfxData);
const pfx = forge.pkcs12.pkcs12FromAsn1(pfxAsn1, password);
// Obtener llave privada y certificado
const keyBag = pfx.getBags({bagType: forge.pki.oids.pkcs8ShroudedKeyBag});
const certBag = pfx.getBags({bagType: forge.pki.oids.certBag});
const privateKey = keyBag[forge.pki.oids.pkcs8ShroudedKeyBag][0].key;
const certificate = certBag[forge.pki.oids.certBag][0].cert;
// Firmar el XML (simplificado)
const signature = this.generarFirmaXML(xml, privateKey);
return {
success: true,
xmlFirmado: this.insertarFirmaXML(xml, signature, certificate),
cadenaOriginal: this.generarCadenaOriginal(xml)
};
} catch (error) {
return {success: false, error: error.message};
}
}
generarCadenaOriginal(xml) {
// Implementar según especificaciones del SAT
return "cadena_original_generada";
}
generarFirmaXML(xml, privateKey) {
// Lógica de firma según SAT
return "firma_generada";
}
insertarFirmaXML(xml, firma, certificado) {
// Insertar firma en el XML
return xml;
}
}
module.exports = FirmadorSAT;🌐 Arquitectura Recomendada
Cliente (Navegador) → API Backend → Servicios SAT
(JavaScript) (Node.js/Java) (WS/WCF)Frontend seguro:
// frontend.js
class ClienteFirma {
async subirArchivoFirmar(archivo) {
const formData = new FormData();
formData.append('archivo', archivo);
const response = await fetch('/api/firmar', {
method: 'POST',
body: formData
});
return await response.blob();
}
async descargarArchivoFirmado(contenido, nombre) {
const blob = new Blob([contenido], {type: 'application/xml'});
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = nombre;
a.click();
window.URL.revokeObjectURL(url);
}
}🔐 Consideraciones de Seguridad Críticas
Nunca proceses PFX en el frontend
Usa HTTPS siempre
Valida y sanitiza todas las entradas
Maneja errores adecuadamente
Audita y registra todas las operaciones
📦 Librerías Útiles
Backend:
node-forge: Para operaciones criptográficas
xml-crypto: Para firmar XML
node-xmldom: Para manipulación XML
Frontend:
Web Crypto API: Para operaciones básicas
jsrsasign: Librería JavaScript para RSA
🚀 Ejemplo de Implementación Segura
// Ejemplo de flujo seguro
document.getElementById('btnFirmar').addEventListener('click', async () => {
const archivo = document.getElementById('archivo').files[0];
const firmador = new ClienteFirma();
try {
const archivoFirmado = await firmador.subirArchivoFirmar(archivo);
firmador.descargarArchivoFirmado(archivoFirmado, 'documento_firmado.xml');
} catch (error) {
console.error('Error:', error);
alert('Error al firmar el documento');
}
});¿Qué tipo de documentos necesitas firmar específicamente? ¿Facturas CFDI, contratos, o otro tipo de documentos? Esto me ayudaría a darte una guía más específica.
Comentarios
Publicar un comentario