Blogverifactufacturacioseguretat

Verifactu implementat de veritat: la cadena de hash i l'error que vam tenir

Com s'encadenen les factures amb SHA-256, els detalls que trenquen la cadena i l'error d'autorització que ens hauria pogut corrompre l'historial.

26 d’agost del 2026 3 min

Què demana Verifactu, de veritat

Deixant de banda el llenguatge del BOE, Verifactu demana tres coses: que cada factura porti una empremta única, que aquella empremta inclogui la de la factura anterior, i que hi hagi una manera de verificar-la des de fora.

La segona és la important i és la que gairebé ningú explica bé. No es tracta de signar factures una per una: es tracta que estiguin encadenades. Si algú n'esborra una del mig o en canvia l'import, totes les posteriors deixen de quadrar. Aquesta és tota la gràcia.

Com ho hem implementat

Quan una factura es tanca, es construeix un JSON canònic amb els camps que no poden canviar mai: el hash de la factura anterior, el número, la data d'emissió, el NIF i el nom de l'emissor, el NIF i el nom del client, la base, l'IVA, l'IRPF i el total.

D'aquest JSON se'n treu un SHA-256 i es desa a la factura, juntament amb el hash anterior i la data de signatura.

La primera factura de cada empresa no té anterior, així que fa servir un hash gènesi de seixanta-quatre zeros. És una convenció, però ha de ser explícita: si es deixés buit, dues implementacions diferents produirien hashos diferents per a la mateixa primera factura.

Els detalls que decideixen si funciona

Els imports van amb dos decimals sempre. Number(x).toFixed(2). Sembla una tonteria fins que t'adones que 100 i 100.00 donen hashos diferents, i que un canvi de configuració que afecti com es formaten els números et trencaria la cadena sencera cap enrere. El format del payload és part de l'especificació, no un detall d'implementació.

L'ordre de les claus és fix. Un JSON amb les mateixes dades i les claus en un altre ordre és una altra cadena de text i, per tant, un altre hash.

Signar dues vegades no pot fer res. Si la factura ja té hash, la funció retorna l'existent i no en calcula cap de nou. Sense això, un doble clic o un reintent de xarxa et pot canviar l'empremta d'una factura ja emesa.

L'error que vam tenir, i és el que val la pena explicar

La funció que signa comprovava que qui la cridava estigués autenticat. No comprovava que la factura fos seva.

Amb un identificador de factura d'un altre client, qualsevol usuari amb sessió podia signar-la. No podia llegir-la ni modificar-ne els imports —d'això ja se n'encarrega la seguretat a nivell de fila— però sí que li podia posar un hash i corrompre-li la cadena a partir d'aquell punt.

L'arreglo són tres línies: resoldre el tenant de qui truca i comparar-lo amb el de la factura. Però la lliçó no és aquesta.

Una cadena de hash només val el que val el camí d'escriptura més fluix. Pots tenir la criptografia perfecta i perdre-ho tot per una comprovació d'autorització que faltava. Quan implementis Verifactu, la pregunta no és «el hash és correcte?»: és «qui pot escriure en aquesta columna?».

El QR

Es genera apuntant a la URL de verificació de la seu electrònica de l'AEAT, amb el NIF, el número de sèrie, la data i l'import. Va al PDF de la factura.

En mode local és informatiu. Quan s'activa l'enviament real a l'AEAT, la factura passa a estat pendent i és l'Agència qui torna el codi de verificació. Els dos modes conviuen a la mateixa taula amb un camp d'entorn, perquè el canvi sigui una configuració i no una migració.

El que els venedors de software no et diuen

Que la responsabilitat de mantenir això al dia és de qui manté el sistema. Si el compres, els canvis normatius els fan ells i van dins de la quota. Si te'l fas a mida, passen a ser feina teva per sempre.

És un argument real a favor de comprar i l'hem de posar sobre la taula quan algú ens demana un ERP a mida. Ho fem: està escrit a la nostra pàgina d'ERPs, a la secció de quan no t'has de construir res.

Comparteix: