The Wiert Corner – irregular stream of stuff

Jeroen W. Pluimers on .NET, C#, Delphi, databases, and personal interests

  • My badges

  • Twitter Updates

  • My Flickr Stream

  • Pages

  • All categories

  • Enter your email address to subscribe to this blog and receive notifications of new posts by email.

    Join 1,446 other followers

Things I tripped over when implementing the “Elf proef”: digit check for Dutch bank account numbers and social security numbers (bankrekeningnummer, BSN/Sofinummer)

Posted by jpluimers on 2012/09/13

It was a long time ago that I ever did something with the Elf proef.

It is the algorithm that is used to calculate the check digit for Dutch bank account numbers (bankrekeningnummers) and a variation for BSNs (Social Security Numbers).

I needed it (or more exactly: a variation of it) in order to support anonymization of customer data for the DTA/OTA portions of a DTAP/OTAP environment.

So, I started reading on the Elf proef, and getting some sample data to setup some unit tests.

Wrong and wrong:

To start with the latter, they get it wrong because the check digit is modulo 11 (like the ISBN 10 check digit), but only numeric digits are valid. Their bank.js algorithm module tries to accommodate for that in the wrong way.

In addition they copy-pasted code between their other number generation algorithms which you can see form the variable SofiNr which is an abbreviation for SofiNummer,  the old name for the Dutch Social Security Number (now called Burgerservicenummer aka BSN).

Their generated sample 290594880 is wrong because the check digit should be 10, and 10 is not a digit. Their generated number 936977590 is OK as the check digit should be zero (0) which it is.

More on their fault a bit further on. First lets concentrate on getting proper test data, and the right algorithm.

I will cover code for the bankrekeningnummer here. The complete code including BSN is at BeSharp.CodePlex.com.

Getting test data

I found a couple of sites that provided bankrekeningnummers in smaller or greater amounts:

When testing algorithms (in this case for validation, generating check digits, and generating validating fake numbers) you need both correct and false results. It is easy to go from a range of valid bankrekeningnummers to invalid ones: just change the last digit (I replaced the end digits 9..0 by digits 0..9):

        readonly string[] invalidBankrekeningNummerTestNummers =
        {
            "5780076210", "8888950501", "2570126352", "6822313203", "4490777794", "4669658485", "6769091146", "5118605967", "1609038688", "7174960489",
            "242993380", "838606001", "917005932", "918089953", "304195544", "284151115", "880107076", "140206447", "144116568", "705366179",
            "959446150" // "959446150" generated by Testnummers.nl
        };
        readonly string[] validBankrekeningNummerTestNummers =
        {
            "5780076219", "8888950508", "2570126357", "6822313206", "4490777795", "4669658484", "6769091143", "5118605962", "1609038681", "7174960480",
            "242993389", "838606008", "917005937", "918089956", "304195545", "284151114", "880107073", "140206442", "144116561", "705366170"
        };

Getting the right algorithm

Based on the 9 digit algorithm at WikiPedia, I extended it to a 10 digit algorithm, then verified it against two descriptions on the Exact Software site (they build financial software, so they’d better get it right <g>).

It is pretty simple:

  1. Zero extend the bankrekeningnummer to the left with zero until it is 10 long
  2. From the left to the right, add a weighted sum. From left to right, the weights are 10, 9, 8 , 7 , 6 , 5, 4, 3, 2, and 1.
  3. Verify the weighted sum modulus 11 is zero

In a similar way you can calculate the check digit. You calculate the weighted sum for all but the last digit, then calculate the check digit so it makes the modulus become zero.

I added some extra code to make it accept dots in the bankrekeningnummers (it is a custom to write a 9 digit bankrekeningnummer like this 19.28.37.465).

This is the result:

using System;
using BeSharp.Generic;

namespace BeSharp.NumberVerification
{
    ///
<summary> /// http://nl.wikipedia.org/wiki/Elfproef - note they are wrong for 10 digit bankrekeningnummer!
 /// http://en.wikipedia.org/wiki/Check_digit
 /// http://www.exactsoftware.com/docs/DocView.aspx?DocumentID=%7B1d45f854-8b31-457d-8348-cc7c9c9349d5%7D
 /// http://www.testnummers.nl/
 /// </summary>
    public class BankrekeningNummerVerification
    {
        public const int MaxBankRekeningNummerLength = 10;
        public const int MinBankRekeningNummerLength = 9;
        private const int modulus = 11;
        private const char zeroDigit = '0';

        public static string Generate(int bankRekeningNummerLength)
        {
            if (isInvalidBankRekeningNummerLength(bankRekeningNummerLength))
                throw new Exception("{0} is an invalid length".With(Reflector.GetNameSeparatorValue(new { bankRekeningNummerLength })));

            Random random = new Random();
            while (true)
            {
                try
                {
                    string result = string.Empty;

                    for (int i = 1; i < bankRekeningNummerLength; i++) // 1 less, because Complete will compute the CheckDigit at the end
                    {
                        int minDigit = 1 == i ? 1 :  0; // do not make leading zeros
                        int digit = random.Next(minDigit, 9);
                        result += digit.ToString();
                    }
                    result = Complete(result);

                    return result;
                }
                catch (Exception)
                {
                    continue; // we will eventually get a good number, the chance of having many bad numbers in a row is very low.
                }
            }
        }

        public static string Complete(string partialBankrekeningNummer)
        {
            string value = partialBankrekeningNummer + zeroDigit;

            bool passedSanityCheck = saneWithoutPoints(ref value);
            if (!passedSanityCheck)
                throw new Exception("{0} didn't pass sanity check".With(Reflector.GetNameSeparatorValue(new { partialBankrekeningNummer })));

            int weightedModulus = getWeightedModulus(ref value);

            int lastDigit = (modulus - weightedModulus) % modulus;

            if (lastDigit > 9)
                throw new Exception("Cannot complete {0} as it would result in {1}".With(Reflector.GetNameSeparatorValue(new { partialBankrekeningNummer }), Reflector.GetNameSeparatorValue(new { lastDigit })));

            value = partialBankrekeningNummer + lastDigit.ToString();

            return value;
        }

        public static bool IsValid(string bankrekeningNummer)
        {
            string value = bankrekeningNummer;

            bool passedSanityCheck = saneWithoutPoints(ref value);

            if (!passedSanityCheck)
                return false;

            int weightedModulus = getWeightedModulus(ref value);

            bool result = weightedModulus == 0;
            return result;
        }

        private static int getWeightedModulus(ref string value)
        {
            value = value.PadLeft(MaxBankRekeningNummerLength, zeroDigit);

            int weightedSum = 0;
            int weight = 10;
            foreach (char item in value)
            {
                int digit = int.Parse(item.ToString());
                weightedSum += digit * weight;
                weight--;
            }

            int weightedModulus = weightedSum % modulus;
            return weightedModulus;
        }

        private static bool isInvalidBankRekeningNummerLength(int value)
        {
            return (value < MinBankRekeningNummerLength) || (value > MaxBankRekeningNummerLength);
        }

        private static bool passesSanityCheck(string value)
        {
            if (!value.IsAllDigits())
                return false;

            int valueLength = value.Length;
            if (isInvalidBankRekeningNummerLength(valueLength))
                return false;

            return true;
        }

        private static bool saneWithoutPoints(ref string value)
        {
            value = stripPoints(value);

            bool passedSanityCheck = passesSanityCheck(value);
            return passedSanityCheck;
        }

        private static string stripPoints(string bankrekeningNummer)
        {
            string value = bankrekeningNummer.Replace(".", string.Empty);
            return value;
        }
    }
}

The Testnummers.nl fault: not using Yoda conditions

What happens is that for both the check 11 and 10, they make the digit zero. All of that because they didn’t use yoda conditions, but mixed up the assignment operator and equality comparison operator, trapping in one of the big JavaScript issues combinations around:

  • an assignment is an expression returning the assigned value
  • virtually anything is accepted as a Boolean expression, including integer numbers

This is what I did:

  1. I extraced the bank.js code,
  2. ran it through the jsbeautifier.org JavaScript code formatter/beautifier,
  3. used jslint.com to find the “Expected a conditional expression and instead saw an assignment.”,
  4. cut out the function header and footer,
  5. added some debugging writeln statements,
  6. ran it on the writecodeonline.com online JavaScript runner.

The solution was rephrasing this tiny but if code

        if (Nr1 > 9) if (Nr1 = 11) {
            Nr1 = 0;
        }

into

        if (Nr1 > 9) if (11 == Nr1) {
            Nr1 = 0;
        }

instantly solved the problem.

–jeroen

PS: This is bank.js script cleaned up a bit with a couple of document.writeln statements and their output.

    var Result1 = 234;
    var Nr1 = 10;
    while (Nr1 == 10) {
        var Result1 = "";
        var Nr9 = Math.floor(Math.random() * 10);
        var Nr8 = Math.floor(Math.random() * 10);
        var Nr7 = Math.floor(Math.random() * 10);
        var Nr6 = Math.floor(Math.random() * 10);
        var Nr5 = Math.floor(Math.random() * 10);
        var Nr4 = Math.floor(Math.random() * 10);
        var Nr3 = Math.floor(Math.random() * 10);
        var Nr2 = Math.floor(Math.random() * 10);
        var Nr1 = 0;
        var Nra;
        var SofiNr = 0;
        if (Nr9 == 0) {
            Nr9 = 1;
        }
        var Sofinr = 0;
        SofiNr = 9 * Nr9 + 8 * Nr8 + 7 * Nr7 + 6 * Nr6 + 5 * Nr5 + 4 * Nr4 + 3 * Nr3 + 2 * Nr2;
        Nra = Math.floor(SofiNr - (Math.floor(SofiNr / 11)) * 11);
        var Nraa = Nra - 11;
        document.writeln(Nraa);
        var Nr1 = Math.abs(Nraa);
        document.writeln(Nr1);
        if (Nr1 > 9) if (Nr1 = 11) {
            Nr1 = 0;
            document.writeln(Nr1);
        }
    }
    Result1 += Nr9;
    Result1 += Nr8;
    Result1 += Nr7;
    Result1 += Nr6;
    Result1 += Nr5;
    Result1 += Nr4;
    Result1 += Nr3;
    Result1 += Nr2;
    Result1 += Nr1;
    document.writeln(Result1);

-5 5 207333955
-10 10 0 439372330
-11 11 0 671452800
-11 11 0 936977590

The fixed code below gave this sequence for when it generated 10 as check digit value:

-10 10 -6 6 279327706

    var Result1 = 234;
    var Nr1 = 10;
    while (Nr1 == 10) {
        var Result1 = "";
        var Nr9 = Math.floor(Math.random() * 10);
        var Nr8 = Math.floor(Math.random() * 10);
        var Nr7 = Math.floor(Math.random() * 10);
        var Nr6 = Math.floor(Math.random() * 10);
        var Nr5 = Math.floor(Math.random() * 10);
        var Nr4 = Math.floor(Math.random() * 10);
        var Nr3 = Math.floor(Math.random() * 10);
        var Nr2 = Math.floor(Math.random() * 10);
        var Nr1 = 0;
        var Nra;
        var SofiNr = 0;
        if (Nr9 == 0) {
            Nr9 = 1;
        }
        var Sofinr = 0;
        SofiNr = 9 * Nr9 + 8 * Nr8 + 7 * Nr7 + 6 * Nr6 + 5 * Nr5 + 4 * Nr4 + 3 * Nr3 + 2 * Nr2;
        Nra = Math.floor(SofiNr - (Math.floor(SofiNr / 11)) * 11);
        var Nraa = Nra - 11;
        document.writeln(Nraa);
        var Nr1 = Math.abs(Nraa);
        document.writeln(Nr1);
        if (Nr1 > 9) if (11 == Nr1) {
            Nr1 = 0;
            document.writeln(Nr1);
        }
    }
    Result1 += Nr9;
    Result1 += Nr8;
    Result1 += Nr7;
    Result1 += Nr6;
    Result1 += Nr5;
    Result1 += Nr4;
    Result1 += Nr3;
    Result1 += Nr2;
    Result1 += Nr1;
    document.writeln(Result1);

4 Responses to “Things I tripped over when implementing the “Elf proef”: digit check for Dutch bank account numbers and social security numbers (bankrekeningnummer, BSN/Sofinummer)”

  1. thaddy said

    I am pretty sure they took my Delphi 2 code and tried a monkey translation to js. (At that time I used that code for ABNAMRO, RABO, ING and Interpay, now Currence).
    Funny that such old code survives and then gets miss-interpreted….

  2. […] a follow-up on my earlier number validation posts (Elf proef in C# and Other number verifications), I found a nice T-SQL version of the Elfproef for Dutch bank […]

  3. […] via: Things I tripped over when implementing the “Elf proef”: digit check for Dutch bank account numb…. Rate this:Share this:LinkedInTwitterFacebookStumbleUponDiggRedditEmailPrintLike this:LikeBe the first to like this. […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

 
%d bloggers like this: