Dev.to Security 🔐 Cybersecurity 👁 0 📖 2 min read

Implementing Iran's Moadian E-Invoicing Cryptography: RSA-2048 CSR (OID 2.5.4.97), Verhoeff TaxID, and Stuff ID Validation

published: true tags: python, cryptography, fintech, erp Building an accounting or ERP integration for Iran's National Tax Administration (Moadian / سامانه مودیان) comes with strict cryptographic and schema validation r

published: true
tags: python, cryptography, fintech, erp

Building an accounting or ERP integration for Iran's National Tax Administration (Moadian / سامانه مودیان) comes with strict cryptographic and schema validation requirements. Even a minor mismatch in the certificate subject, payload signing, or checksum digit results in gateway rejections such as Error 4103 or Error 0300101.

Here is a practical engineering breakdown of the three core components required for compliant e-invoicing, along with open reference tools we use at VORLEX.

  1. Generating an RSA-2048 Key Pair and CSR with OID 2.5.4.97

Before submitting any electronic invoice, the taxpayer's portal (my.tax.gov.ir) requires a 2048-bit RSA public key to issue a 6-character Unique Fiscal Memory ID. Unlike generic TLS certificates, the CSR subject must include the organizationIdentifier attribute (OID 2.5.4.97) containing the 10-digit National ID (for individuals) or 11-digit National ID (for legal entities).

In Python (cryptography), you can generate a compliant CSR like this:

from cryptography import x509
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.x509.oid import NameOID, ObjectIdentifier

private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)

subject = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, "IR"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Vorlex Trading Co"),
x509.NameAttribute(ObjectIdentifier("2.5.4.97"), "14001234567"),
x509.NameAttribute(NameOID.COMMON_NAME, "Vorlex Trading Co"),
])

csr = x509.CertificateSigningRequestBuilder().subject_name(subject).sign(
private_key, hashes.SHA256()
)

If you need to quickly generate or verify test keys in the browser without running OpenSSL scripts, you can use the free Moadian CSR & Key Pair Generator.

  1. Constructing the 22-Character TaxID with Verhoeff Checksum

Every invoice requires a globally unique 22-character taxid:

Characters 1–6: Fiscal Memory ID (6 alphanumeric chars)

Characters 7–11: Days elapsed since 1970-01-01 (5 base-36/hex digits)

Characters 12–21: Invoice serial number (inno, 10 hex chars)

Character 22: Checksum digit computed via the Verhoeff algorithm

Before computing the Verhoeff digit, each alphanumeric character in the 21-character prefix is mapped to its numeric ASCII/decimal representation. You can inspect and verify any 22-character identifier using the TaxID Verhoeff Structure Validator.

  1. Resolving 13-Digit Stuff ID Codes and VAT Rates

A frequent cause of invoice rejection (Error 0304401 and Error 0303301) is a mismatch between the 13-digit commodity/service code (sstid) and the official VAT rate published in stuffid.tax.gov.ir.

Because the official catalog contains over 200,000 rows, indexing it into a lightweight SQLite/FTS5 store allows sub-20ms lookups during invoice entry. For quick manual verification of specific 13-digit codes and their official VAT percentages, you can query the Stuff ID 13-Digit Catalog Finder.

Implementing proper PKCS#1 v1.5 JWS signing, RSA-OAEP-SHA256 payload encryption, Verhoeff TaxID generation, and pre-flight Stuff ID VAT alignment eliminates over 90% of gateway rejections before an invoice ever leaves your ERP.

📰 Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.