Skip to content

IBAN Validator — C# source

Validate International Bank Account Numbers (IBAN) with the mod-97 checksum, verify the country-specific length, and format the result. 100% client-side, no network.

This is the C# implementation — the same logic the interactive tool runs, in a shareable, citable form.

// iban-validator — pure IBAN validation logic (ISO 13616 mod-97 checksum).
//
// Language: C# (C# 12, standard library only)
// Source:   CosmoDev polyglot showcase port of the iban-validator tool,
//           ported from src/lib/iban.ts (the canonical TypeScript
//           implementation).
// License:  display source — part of CosmoDev's polyglot tool pages
//
// Normalizes the input (uppercase, strip whitespace/dashes), checks the
// structure (country code + 2 check digits + 1-30 BBAN chars), verifies
// the per-country length, then runs the ISO 13616 mod-97 checksum: move
// the first 4 chars to the end, map A=10..Z=35, and confirm the
// resulting integer is congruent to 1 mod 97. The remainder is folded
// one decimal digit at a time (it stays < 97), so no BigInteger or
// regex machinery is required.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CosmoDev.Snippets;

public static class IbanValidator
{
    /// <summary>Per-country IBAN lengths (ISO 13616) — a representative subset.</summary>
    private static readonly Dictionary<string, int> IbanLengths = new()
    {
        ["AL"] = 28, ["AD"] = 24, ["AT"] = 20, ["AZ"] = 28, ["BH"] = 22,
        ["BY"] = 28, ["BE"] = 16, ["BA"] = 20, ["BR"] = 29, ["BG"] = 22,
        ["CR"] = 22, ["HR"] = 21, ["CY"] = 28, ["CZ"] = 24, ["DK"] = 18,
        ["DO"] = 28, ["EE"] = 20, ["FO"] = 18, ["FI"] = 18, ["FR"] = 27,
        ["GE"] = 22, ["DE"] = 22, ["GI"] = 23, ["GR"] = 27, ["GL"] = 18,
        ["GT"] = 28, ["HU"] = 28, ["IS"] = 26, ["IE"] = 22, ["IL"] = 23,
        ["IT"] = 27, ["JO"] = 30, ["KZ"] = 20, ["XK"] = 20, ["KW"] = 30,
        ["LV"] = 21, ["LB"] = 28, ["LI"] = 21, ["LT"] = 20, ["LU"] = 20,
        ["MK"] = 19, ["MT"] = 31, ["MR"] = 27, ["MU"] = 30, ["MC"] = 27,
        ["MD"] = 24, ["ME"] = 22, ["NL"] = 18, ["NO"] = 15, ["PK"] = 24,
        ["PS"] = 29, ["PL"] = 28, ["PT"] = 25, ["QA"] = 29, ["RO"] = 24,
        ["LC"] = 32, ["SM"] = 27, ["ST"] = 25, ["SA"] = 24, ["RS"] = 22,
        ["SC"] = 31, ["SK"] = 24, ["SI"] = 19, ["SG"] = 19, ["ES"] = 24,
        ["SE"] = 24, ["CH"] = 21, ["TL"] = 23, ["TN"] = 24, ["TR"] = 26,
        ["UA"] = 29, ["AE"] = 23, ["GB"] = 22, ["VG"] = 24,
    };

    /// <summary>Result of validation — mirrors the TypeScript lib's result shape.</summary>
    public sealed record IbanInfo(
        string Input,
        string Cleaned,
        string? CountryCode,
        bool Valid,
        bool ChecksumOk,
        bool LengthOk,
        int? ExpectedLength,
        string Formatted,
        string? Error);

    /// <summary>ISO 13616 mod-97 checksum over a CLEANED iban (uppercase, no spaces).</summary>
    public static bool Mod97Check(string cleaned)
    {
        if (cleaned.Length < 4)
            return false;
        // Move the first 4 chars (country + check) to the end.
        string rearranged = cleaned[4..] + cleaned[..4];
        int rem = 0;
        foreach (char ch in rearranged)
        {
            int value, digits;
            if (char.IsAsciiDigit(ch))
            {
                value = ch - '0';
                digits = 1;
            }
            else if (char.IsAsciiLetterUpper(ch))
            {
                value = ch - 'A' + 10; // A=10 .. Z=35
                digits = 2;
            }
            else
            {
                return false; // invalid character
            }
            // Fold one decimal digit at a time; rem stays < 97.
            if (digits == 2)
                rem = (rem * 10 + value / 10) % 97;
            rem = (rem * 10 + value % 10) % 97;
        }
        return rem == 1;
    }

    /// <summary>
    /// Insert a space every 4 characters (last group may be short) — the
    /// hand-rolled equivalent of the TS regex /(.{4})(?=.)/g + trim.
    /// </summary>
    private static string FormatGroups(string cleaned)
    {
        var sb = new StringBuilder(cleaned.Length + cleaned.Length / 4);
        for (int i = 0; i < cleaned.Length; i++)
        {
            if (i > 0 && i % 4 == 0)
                sb.Append(' ');
            sb.Append(cleaned[i]);
        }
        return sb.ToString();
    }

    /// <summary>Validate an IBAN. Never throws — all failures land in <see cref="IbanInfo.Error"/>.</summary>
    public static IbanInfo ValidateIban(string? input)
    {
        string src = input ?? "";

        // Uppercase and strip whitespace/dashes.
        var cleanedSb = new StringBuilder();
        foreach (char ch in src.ToUpperInvariant())
        {
            if (ch is ' ' or '-' or '\t' or '\n' or '\r')
                continue;
            cleanedSb.Append(ch);
        }
        string cleaned = cleanedSb.ToString();

        string? cc = cleaned.Length >= 2
            && char.IsAsciiLetterUpper(cleaned[0])
            && char.IsAsciiLetterUpper(cleaned[1])
                ? cleaned[..2]
                : null;
        int? expected = cc is not null && IbanLengths.TryGetValue(cc, out int n) ? n : null;

        string formatted = FormatGroups(cleaned);

        // Structure: 2 letters, 2 digits, then 1..30 alnum, total len 5..34.
        bool structOk = cleaned.Length is >= 5 and <= 34
            && char.IsAsciiLetterUpper(cleaned[0])
            && char.IsAsciiLetterUpper(cleaned[1])
            && char.IsAsciiDigit(cleaned[2])
            && char.IsAsciiDigit(cleaned[3])
            && cleaned[4..].All(c => char.IsAsciiLetterUpper(c) || char.IsAsciiDigit(c));
        if (!structOk)
            return new IbanInfo(src, cleaned, cc, Valid: false, ChecksumOk: false,
                LengthOk: false, expected, formatted, "Invalid IBAN format.");

        bool checksumOk = Mod97Check(cleaned);
        bool lengthOk = expected is null || cleaned.Length == expected;
        if (!lengthOk)
            return new IbanInfo(src, cleaned, cc, Valid: false, checksumOk,
                LengthOk: false, expected, formatted,
                $"Length should be {expected} for {cc}.");
        if (!checksumOk)
            return new IbanInfo(src, cleaned, cc, Valid: false, ChecksumOk: false,
                lengthOk, expected, formatted, "Checksum failed.");

        return new IbanInfo(src, cleaned, cc, Valid: true, ChecksumOk: true,
            lengthOk, expected, formatted, Error: null);
    }

    public static void Main()
    {
        // GB82 WEST 1234 5698 7654 32 — a known-good reference IBAN.
        IbanInfo r = ValidateIban("GB82WEST12345698765432");
        Console.WriteLine($"valid={r.Valid} error={r.Error ?? "(none)"}");
    }
}

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →