Belgische Rijksregisternummer checksum testen (Dutch)

Note: This text is written in Dutch, one of the three official Belgian languages. The code example is annotated in English.

Iedere Belgsiche inwoner heeft een rijksregister nummer. De Belgische overheid kan hiermee alle persoongegevens achterhalen van die persoon. Dit is dus een uniek nummer.

Bij ‘unieke’ nummers in het algemeen is het verstandig om deze nummers slim te kiezen. Als deze direct opvolgend zouden zijn (1, 2, 3, etc.) dan is een typefout snel gemaakt en niet direct op te merken. Daarom worden unieke nummers (zoals nummers op papiergeld of bankrekeningnummers) versterkt met bijvoorbeeld een 11-proef. Het idee is dat alleen correcte nummers dan deelbaar moeten zijn door een priemgetal, zoals elf in dit geval. Als dan toch een typefout wordt gemaakt, wordt dit direct opgemerkt. Een typefout die nog steeds uitkomt op een getal dat ook door 11 deelbaar is, is dan heel klein.

Het Belgische rijksregisternummer is echter niet zomaar een ‘willekeurig’ uniek. Het is opgebouwd uit oa. de geboortedatum.

Hoe is dan het nummer ‘beveildigd’ tegen typefouten?

Het Belgische rijksregisternummer is opgebouwd in drie delen:

  1. De geboortedatum (YYMMDD, zonder eeuw-notatie), bv. 690310
  2. Een dagteller van het aantal geboren mannen (oneven) of vrouwen (even) . Dit kunnen dus maximaal 499 personen van hetzelfde geslacht zijn op die dag
  3. Een controlegetal berekend over de voorgaande getallen

Het betekenisvolle gedeelte van het rijksregisternummer is dus uitgebreid met een checksum (deel drie).

En die checksum is gebaseerd op de modulo 97 (wat dus ook een primegetal is) over de eerste twee delen!

Er zijn echter wel op aanmerkingen te maken op deze berekening:

  • Technisch is het mogelijk om personen zonder dag, maand of jaar te hebben (bv. immigranten waarvan men die data niet weet). Op die (maand- en/of dag-) posities staan nullen
  • Het jaar kan als ’00’ (bv. voor het jaar 2000) geschreven worden
  • Het weglaten van de eeuwnotatie (18, 19, 20, etc.) levert voor twee verschillende data (bv. 1901-01-01 en 2001-01-01) dezelfde datum op (010101). Als het geslacht en het geboortenummer ook hetzelfde zijn, dan zou dat dus tot dezelfde checksum kunnen leiden…

Hoewel de checksum dus prima te berekeken is, moet er dus wel goed opgeled worden:

  • bij een conversie van string naar integer notatie moet er over het correct omgaan met voorloopnullen nagedacht worden
  • Die situatie rond twee verschillende, maar toch dezelfde data (omdat de eeuw-notatie weggelaten is) moet extra aandacht krijgen

Wat betreft die dubbel gebruikte data is er wel een oplossing. Om de twee data (bv. 1901-01-01 en 2001-01-01) van elkaar te onderscheiden, is er voor gekozen om de checksum te laten afwijken.

Hoe ziet dit algoritme er dan uit?

  1. Behandel het opgegeven rijksregisternummer als één met een geboortedatum van vóór 2000.
  2. Bereken gewoon de checksum
  3. Indien de checksum klopt, behandel het rijksregisternummer als zijnde correct
  4. Indien de checksum niet correct is, controleer of het rijksregisternummer klopt voor een geboortedatum vanaf 2000
  5. Berekende de ‘afwijkende’ checksum
  6. Indien de checksum nu wel klopt, behandel het rijksregisternummer als zijnde correct
  7. Indien de checksum nog steeds niet correct is, behandel het rijksregisternummer als incorrect

Blijft over de vraag: hoe kan die checksum dan afwijken voor geboortedatums vanaf 2000?

De oplossing is simpel, vooraf aan de tweede berekening moet de samenvoeging van het eerste deel (de datum) en het tweede deel (het volgnummer) eerst nog voorafgegaan worden door een ‘2’. Dus de checksum wordt dan niet over 9 cijfers maar over 10 cijfers uitgerekend (een ‘2’, de datum en dan dat volgnummer).

Dit levert het volgende algoritme in C# code op:

/// <summary>
/// Check the checksum for a Rijksregister
/// </summary>
/// <param name="rrn">Rijksregister number</param>
/// <returns>True means a correct RRN checksum</returns>
public static bool CheckRijksRegisterNumberChecksum(string rrn)
{
    var rrnChecksum = Convert.ToInt32(rrn.Substring(9, 2));

    // we pick the RRN part we want to recalculate the checksum for
    var partToCalculate = rrn.Substring(0, 9);
    var rrnInt = Int64.Parse(partToCalculate);

    // we calculate the expected checksum
    var checksum = 97 - (rrnInt % 97);

    // we compare the excisting checksum with the calculated
    if (rrnChecksum == checksum)
    {
        // we have a good checksum
        return true;
    }

    //// Checksum not yet ok. We check for a possible 1900/2000 situation;

    // we repeat the same test but now with the extra '2' added to the part
    partToCalculate = "2" + partToCalculate;
    rrnInt = Int64.Parse(partToCalculate);

    // we calculate the expected checksum. again
    checksum = 97 - (rrnInt % 97);

    // we compare the excisting checksum with the calculated, again
    if (rrnChecksum == checksum)
    {
        // we have a good checksum. Person born between 2000 and now
        return true;
    }
    else
    {
        // invalid number, even after 2000 check
        return false;
    }
}

Dit betekent dat deze bijna identieke rijksregisternummers, ‘02021518897′ voor een datum uit 1902 en ‘02021518829′ uit 2002, beiden correct zijn. Het enige wat verschilt is die checksum dus.

Opmerking: het algoritme wat hieronder bescheven is, is ook zo uitgewerkt in de C# broncode. Technisch zou het omdraaien van de twee berekeningen niet direct iets mogen uitmaken (dus eerst de 2000 situatie berekenen en dan pas de 1900 situatie). Statistisch zal het over enkele decennia wat rekenkracht gaan schelen als er vanuit gegaan wordt dat het rijksregisternummer eerder van iemand uit de huidige eeuw is dan uit de vorige eeuw.

Conclusie

Omdat bewust gekozen is om het rijksregsternummer niet met een eeuwnotatie uit te breiden, is het soms nodig om tweemaal een checksum te berekenen om tot het gewenste resultaat te komen. Hiermee is een potentiele millenniumbug opgelost maar moet dit wel met extra rekenkracht gecompenseerd worden.

Met bovenstaande code voorbeeld moet het eenvoudig zijn om deze om te zetten naar andere programmeertalen. Let hierbij wel op de lengte van het rijksregisternummer bij het omzetten naar een integer waarde (dit werkt niet met een 32 bits integer). Tevens moet goed gecontroleerd worden op voorloopnummer welke wellicht onopgemerkt wegvallen.