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 →