月曜の朝、サプライヤーからの請求書がスキャン PDF で受信トレイに届いた。OCR が読み取った IBAN は DE89 3704 0044 0532 O130 00。末尾から 2 番目のグループの O、気づきましたか?危うく見逃すところでした。先週の支払い処理で、12,500 EUR の振込が同じトリックで跳ね返されたばかりです——プリンタから OCR の間で 0O に化けた——銀行は返金手数料として 25 EUR を引きました。

たった一度の mod-97 検証で止められる話です。IBAN 標準はまさにこの場面のために設計されています。

今すぐ IBAN を検証する →

IBAN とは結局何なのか

IBAN(International Bank Account Number)は ISO 13616 で定義され、SWIFT が IBAN Registry を通じて運用維持しています。各 IBAN は一つの文字列に 4 つの要素を詰め込みます:

部位長さ出典
国コード2 文字ISO 3166-1 alpha-2
チェックデジット2 桁の数字残り部分の mod-97
BBAN(Basic Bank Account Number)11〜30 文字各国の標準
全長15〜34 文字国別に固定

最短はノルウェーの 15 文字、最長はセントルシアと マルタの 32・31 文字。チェックデジットは国コードの直後に位置するため、英国の IBAN は GB82 WEST… と始まります——GB が国、82 がチェックサム、WEST 以降が BBAN です。

世界中で個人向けに IBAN を発行する「世界機関」は存在しません。各国がこの標準を採用したうえで、自国の BBAN 構造(銀行コードの位置、口座番号の長さ、各位置の文字種)を定義し、SWIFT IBAN Registry に公開します。この構造こそが、すべての検証器が実装する契約です。

mod-97 の仕組みと、なぜ巧妙なのか

チェックデジットはランダムではありません。文字を数値に置換した後の IBAN 全体を 97 で割ったとき余りが 1 になるよう設計されています。アルゴリズムは 5 ステップで、ブラウザ内ではマイクロ秒で完了します:

  1. 正規化。 空白を取り除き、すべて大文字化する。
  2. 回転。 先頭 4 文字(国コード + チェックデジット)を末尾に移動。
  3. 文字置換。 各文字を 2 桁の数字へ:A → 10B → 11、…、Z → 35
  4. 剰余。 得られた数字列を 1 つの巨大整数とみなし、n mod 97 を計算。
  5. 比較。 余りが 1 ならチェックサム成立。
function isValidIban(raw) {
  const s = raw.toUpperCase().replace(/[^A-Z0-9]/g, '');
  if (!/^[A-Z]{2}[0-9]{2}[A-Z0-9]+$/.test(s)) return false;
  const rearranged = s.slice(4) + s.slice(0, 4);
  const numeric = [...rearranged]
    .map(c => (c >= 'A' ? (c.charCodeAt(0) - 55).toString() : c))
    .join('');
  // BigInt は長い IBAN(ロシアは 33 文字)で 53 ビット浮動小数の上限を超えるのを回避。
  return BigInt(numeric) % 97n === 1n;
}

なぜ 97 なのか?理由は 3 つあります:

  • 素数。 素数の剰余を取ることで、「任意の桁を 1 つ変えたら余りが変わる」確率が最大化される——どの小さな約数も誤差を吸収できないから。
  • 2 桁に収まる。 97 で割った余りは必ず 2 桁に収まり、標準がチェックデジットに割り当てた長さとちょうど一致する。
  • 頻出ミスへの高カバレッジ。 単一文字誤りや隣接 2 桁の入れ替えを圧倒的な確率で検出する——形式的解析では総合カバレッジが 99.5% を超える。

最も巧みなのはステップ 2 の 回転 です。回転をしなければ、BBAN 内のエラーしか捕まえられず、国コードは剰余演算に「無視される」ことになります。国コードとチェックデジットを末尾に移すことで、それらも除算に参加する整数の一部となり、DEFR と書き間違える、チェックデジットを打ち間違えるといったミスも数学的に検出されます。

BBAN こそが各国の独創性が出る場所

国コードとチェックデジットの後に続く BBAN のフォーマットは国ごとに定義されます。SWIFT IBAN Registry が各国のレイアウトをコード化しています。バリエーションを少しだけ紹介:

長さBBAN 構造
ノルウェー(NO)154 桁の銀行 + 6 桁の口座 + 1 桁の国内チェックデジット
ベルギー(BE)163 桁の銀行 + 7 桁の口座 + 2 桁の国内チェックデジット
オランダ(NL)184 文字の銀行(ABNA、RABO、INGB…)+ 10 桁の口座
ドイツ(DE)228 桁の銀行 + 10 桁の口座(支店なし)
英国(GB)224 文字の銀行 + 6 桁の sort code + 8 桁の口座
フランス(FR)275 桁の銀行 + 5 桁の支店 + 11 文字の口座 + 2 桁の国内チェックデジット
イタリア(IT)271 文字の国内チェック + 5 桁の銀行 + 5 桁の支店 + 12 文字の口座
サウジアラビア(SA)242 桁の銀行 + 18 文字の口座
ブラジル(BR)298 桁の銀行 + 5 桁の支店 + 10 桁の口座 + 1 文字の口座種別 + 1 文字の保有者
モーリシャス(MU)304 文字の銀行 + 4 桁の支店 + 15 桁の口座 + 3 文字の予約

注目すべきパターンが 2 つあります:

ラテン語圏グループ(FR、IT、BE、MC、MR、PT、SM、ST、TN) はすべて BBAN 内部に追加の 国内 チェックデジットを埋め込んでいます——IBAN 層の mod-97 とは独立です。フランスの RIB key は銀行 + 支店 + 口座 に対して別の mod-97 を実行します。イタリアの BBAN 先頭文字は CIN レター(AZ)で、位置加重テーブルから計算されます。IBAN の mod-97 だけを実行する場合、ほとんどのエラーは検出できますが、BBAN 内部の一部の単一文字ミスは IBAN 層を通り抜け、銀行が国内チェックを走らせた段階で初めて失敗します。「100% 正確」を謳う検証ライブラリの多くが嘘なのはこのため——彼らは IBAN 構造を検証していても、国内層のチェックを省略しているのです。

英語圏グループ(GB、IE、MT)文字で銀行コード を表します。これらの文字は銀行短縮名の冒頭(WESTBARCLOYDHSBC)で、GB IBAN を解析するとドイツのそれより可読性の高い情報が得られます:テーブルなしでも BARC が Barclays だと推測できます。

現実の IBAN エラーはどこから来るか

IBAN のミスは金額そのものより、跳ね返ったあとの後始末が高くつきます——銀行は数週間かけて差し戻すことすらあります。本当のコストは、返金手数料(1 件あたり 5〜30 EUR)、業務側のフォローアップ工数、サプライヤーとの関係に生まれる摩擦です。防ぐべき類型は次のとおり:

OCR の誤認識

スキャンされた請求書の失敗パターンは予測可能です。最も頻発する文字置換:

理由
O0字母 O と数字 0 が低品質フォントでほぼ同じ形に
I / l / 11サンセリフ系で字形が重なる
S5イタリックや装飾系フォントでよくある誤認
B8銀行明細の圧縮出力で起きやすい
Z2欧州大陸の手書き習慣

mod-97 はこれらをすべて拾えます——1 桁の差は必ず余りを変えるからです。ZeroTool の検証器は どの種類 の置換が起きたかを示唆します:失敗時に “Checksum failed, check digits or account body are wrong.” と表示するので、そこから視覚的に似た文字ペアを探せばよいわけです。

空白と幅ゼロ文字

PDF や Outlook からのコピーペーストは、ノンブレイクスペース(U+00A0)、ゼロ幅接合子(U+200D)、たまに BOM を持ち込みます。自作の IBAN チェックスクリプトの多くは ASCII スペースしか除去せず、これで詰まります。ZeroTool の正規化処理は ISO 13616 のとおり、AZ09 以外の符号位置をすべて除去してから検証します——標準が「人間可読形式の英数字以外はすべて装飾」と明記しているためです。

先頭ゼロの消失

スプレッドシートは IBAN の敵です。Excel はセルを数値と解釈して 0123123 に「親切に」変えてしまいます。IBAN が表計算を経由して支払いシステムに戻ったとき長さが合わず、mod-97 が失敗します。本来は構造的に直すべき問題ですが——IBAN 列はテキストとして保存し、決して数値としてパースしない——検証器は症状を捉えられます。

国コードの取り違え

DE(ドイツ)と DK(デンマーク)は多くのキーボードで隣り合っていて、IBAN の長さは 4 文字違います。誰かがドイツの IBAN 本体をデンマークの接頭辞で貼り付けると、長さチェックが即座に失敗します。検証器は具体的なエラーで応答します:「Wrong length for Denmark: expected 18, got 22.」

小文字

仕様は大文字のみです。一部の銀行は可読性のために大文字小文字混在で印字し、メールでは IBAN をハイパーリンクで包む処理が小文字化することもあります。正規化が処理しますが、「これは通るはず」というレポートを追うときには頭に置いておく価値があります。

IBAN 検証器が 教えてくれない こと

mod-97 が通れば検証器は「正しい」と呼びたくなりますが、そうではありません。3 つのことは常に守備範囲外です:

  1. 口座が存在するか。 銀行が先週解約したかもしれない。mod-97 には知りようがない。
  2. 口座が利用可能か。 凍結・休眠・停止のいずれでも、振込指示は受け付け、決済段階で跳ね返す。
  3. IBAN と名義が一致するか。 EU の PSD2 と SEPA スキームは、これを受け取り側銀行に押し付けます。送る側のあなたは検証しない。英国の Confirmation of Payee や、欧州で 2024〜2025 に段階展開された Verification of Payee はまさにこれを対象にしていますが、銀行層の話であり、あなたのフォーム検証の話ではありません。

正しい心構え:IBAN 検証は必要な第一フィルターであって十分条件ではない。クライアント側で安く誤入力を捕まえ、残りは銀行 API(あるいは実際の送金試行)に任せる。

チェックアウトフォームへの検証組み込み

典型的なパターン:入力中に検証して、成功時は緑のチェック、失敗時はインラインエラーを出し、mod-97 に通らない限り送信させない。バニラ JS の骨格:

<label for="iban">IBAN</label>
<input
  id="iban"
  type="text"
  inputmode="text"
  autocapitalize="characters"
  spellcheck="false"
  autocomplete="off"
  aria-describedby="iban-msg"
/>
<p id="iban-msg" role="status" aria-live="polite"></p>

<script>
  const input = document.getElementById('iban');
  const msg = document.getElementById('iban-msg');

  input.addEventListener('input', () => {
    const result = isValidIban(input.value);  // 上の関数
    msg.textContent = result ? 'Valid IBAN.' : 'IBAN checksum invalid.';
    msg.className = result ? 'ok' : 'err';
  });
</script>

実運用で効く細部 3 つ:

  • autocapitalize="characters" は iOS のオートコレクトが gB82… を送るのを防ぎます——それでは format regex でいきなり落ちる。
  • aria-live="polite" はスクリーンリーダー利用者にフォーカスを奪わず検証結果を読み上げます。
  • 送信時だけでなく、入力中に検証する。 300ms の submit round-trip を待たず n 回目のキーストロークでエラーを捕まえる——これがすべての要点です。

検証成功時に有料 IBAN API(国内チェックデジットと口座存在性照会つき)も呼ぶなら debounce を入れましょう:mod-97 は速すぎてキーストロークごとに走らせても問題ないので、API は 300〜500ms の debounce が無難です。

無料の選択肢を比較する

IBAN 検証を置く場所は 3 つ:クライアントライブラリ、SaaS API、ブラウザツール。

選択肢遅延カバレッジコストプライバシー
iban(npm、約 28KB)< 1msmod-97 + 長さ無料クライアントサイド
ZeroTool IBAN Validator & Parser< 1msmod-97 + 長さ + BBAN 分解無料クライアントサイド
iban.com REST API〜150msmod-97 + 銀行検索 + IBAN→BIC従量課金サーバー間
openiban.com REST API〜200msmod-97 + 長さ無料、レート制限サーバー間

正解は「あなたが解きたい問題」によります。個人プロジェクトの無料フォーム——npm ライブラリでも ZeroTool ページでも十分。本番でお金を動かす決済処理——API ティアの国内チェックデジットと IBAN→BIC マッピングが、自前検証で逃したエラーを 1 回拾えば元が取れる。

ZeroTool のツールは「観察」のユースケースに最適化されています:IBAN を持っていて、どう分解されるか知りたい、誰にも送りたくない。クライアントに組み込むのと同じ mod-97 エンジンに、標準的な npm ライブラリでは表に出ないことも多い国別 BBAN 分解を加えたものです。

範囲外(とその理由)

ZeroTool には以下がありません:

  • IBAN ジェネレーター。 国と銀行コードを与えれば数学的にチェックデジットの正しい「形式上有効な IBAN」を作れる。これを公開しないのは、口座詐称詐欺のハードルを下げるからです。本物の IBAN は銀行から発行されるもので、それ以外の経路で生成する開発者の正当な需要はほぼありません。
  • BIC / SWIFT 検索。 銀行コードを銀行名にマッピングするには、月次更新されるライセンス済みデータベースを再配布する必要があり、運用負担も継続的に高い。BIC 検索は SWIFT 公式ディレクトリか各国中央銀行のレジストリが正式な情報源です。
  • SEPA 入金 QR コード。 欧州決済評議会の EPC069-12 形式は、銀行 App の振込画面を事前入力する QR を生成します。ZeroTool の QR コードジェネレーター で QR は作れますが、EPC069-12 ペイロードはご自身で組み立ててください——検証器の仕事は IBAN であって、決済指示ではありません。

これらは意図的な空白です。関連能力をすべて 1 つのツールに詰め込むと、ツールの目的と信頼モデルがどちらも薄まります。

参考資料

ZeroTool 内部では、IBAN 検証器は URL パーサー(決済リダイレクト URL のトークン解析)、Cookie パーサー(決済ポータルが発行するセッション cookie のデバッグ)、QR コードジェネレーター(検証済み IBAN から SEPA EPC069-12 QR を構築)と自然に組み合わさります。

次に OCR で読んだ IBAN がついた請求書が来たら、決済画面に貼る前にまず検証器に貼ってください。mod-97 はマイクロ秒以内に「この送金が跳ね返るか」を教えてくれます。2 分の精査で、25 EUR の返金手数料を一度でも回避できれば即座に元が取れ、サプライヤーとの関係改善はそれ以上の隠れた配当を返してくれます。