すべての国際銀行口座番号(IBAN)には、見た目以上に重要な役割を果たす2桁の小さな数字が含まれています。これは国コードの直後、3桁目と4桁目に位置し、IBANに組み込まれたエラー検出の仕組みです。口座番号のどこか1桁でも間違えると、ほとんどの場合この2桁のチェックディジットが合わなくなります。その結果、1円も動く前に支払いが拒否されます。
このガイドでは、これらのチェックディジットがどのように計算されるのか、モジュロ97のステップがなぜこれほど効果的なのか、そして自分のシステムで確実に計算・検証する方法を解説します。
IBANのチェックディジットとは実際に何か
国コードの後に続く2桁の数字は、正式にはチェックディジットと呼ばれます。これは乱数ではなく、また口座番号の一部でもありません。ISO 7064(他の多くのチェックディジット方式を支えているのと同じ規格群)で定義された数式を用いて、IBANの残りの部分から導出されます。
その狙いは完全性の確保です。利用者が支払いフォームにIBANを入力するとき、指は滑ります。3が8になったり、隣り合う2桁が入れ替わったり、1文字が抜け落ちたりします。チェックディジットがあれば、どの銀行でも――あるいは自分の検証コードでも――送金先の銀行に問い合わせることなく、こうした誤りを即座に検出できます。
これは、はるかに大きなスケールのパリティビットのようなものだと考えてください。守るべきデータと一緒に運ばれる、口座参照のコンパクトな指紋なのです。
チェックディジットの計算の仕組み
検証ルーチンは、一見すると驚くほど単純です。FR76 3000 6000 0112 3456 7890 189のようなフランスのIBANを例に取りましょう。これを検証するには次のようにします。
- 先頭の4文字を末尾に移動します。
FR76が後ろに回り、30006000011234567890189FR76になります。 - 各文字を2桁の数字に置き換えます。 各文字は数値に対応します。
A=10、B=11、…Z=35です。したがってF=15、R=27となり、FR76は15277 6になります。 - その結果を1つの大きな整数として読み取り、
整数 mod 97を計算します。 - 余りがちょうど1なら、そのIBANは有効です。 それ以外の余りであれば、番号が壊れていることを意味します。
これがアルゴリズムのすべてです。巧妙なのはステップ4で、チェックディジットはIBANが最初に作成される際に、この余りがちょうど1になるように選ばれているのです。
チェックディジットをゼロから計算する
まったく新しいIBANを生成する場合は、この処理を逆向きに実行します。
- BBAN(国ごとに異なる銀行コード・支店・口座番号)を組み立てます。
- 国コードに続けて、チェックディジットのプレースホルダーとして
00を付加します。 - 文字を数字に変換し、大きな整数を計算します。
98 - (整数 mod 97)を計算します。- 結果を2桁になるようゼロ埋めします。それがチェックディジットの組です。
たとえば、有効なBBANをもとにドイツのIBANを構築すると、DE89 3704 0044 0532 0130 00のような番号になり、89が計算された組です。これはまさにRandom IBANジェネレーターが対応する各国について内部で行っていることで、構造的に正しいBBANを構築したうえで、最終的な番号がMOD-97を通過するようにチェックディジットを導出します。
なぜモジュロ97なのか(もっと単純な方法ではなく)
設計者は「すべての桁を合計する」といった単純なチェックサムを使うこともできました。しかし、彼らは意図的にモジュロ97を選びました。その選択は精度に実際の影響を及ぼします。
- 97は素数です。 素数を法として用いると、誤りが均等に分散され、法が素数でない方式につきものの盲点を避けられます。
- 1桁の誤りを必ず検出します。 どの桁を1つ変えても余りは変わります。これは保証されています。
- ほぼすべての入れ替えを検出します。 隣り合う2文字の入れ替え(典型的な入力ミス)は、圧倒的多数のケースで検出されます。
- 複数文字にわたるランダムな誤りに対する検出率はおよそ98.9%で、単純な合計や1桁だけのチェックディジットでは到底達成できない高さです。
その代償として、34文字のIBANは数十桁の数値に展開されるため、多倍長整数の演算が必要になります。とはいえ実務上は問題になりません。主要な言語はいずれもネイティブに、あるいは1行のライブラリで対応しており、多倍長整数を完全に避けられる分割処理のテクニック(後述)もあります。
コードでチェックディジットを計算する
多倍長整数の型が必要になることはめったにありません。剰余演算の性質上、IBANを小さなまとまりごとに処理し、余りを逐次保持していけます。いくつかの言語での代表的なアプローチを示します。
JavaScript:
function isValidIban(iban) {
const s = iban.replace(/\s+/g, '').toUpperCase();
// Move first 4 chars to the end
const rearranged = s.slice(4) + s.slice(0, 4);
// Convert letters to numbers (A=10 ... Z=35)
const numeric = rearranged.replace(/[A-Z]/g, ch =>
(ch.charCodeAt(0) - 55).toString()
);
// Piece-wise mod 97 to avoid BigInt
let remainder = 0;
for (const digit of numeric) {
remainder = (remainder * 10 + Number(digit)) % 97;
}
return remainder === 1;
}
Python:
def is_valid_iban(iban: str) -> bool:
s = "".join(iban.split()).upper()
rearranged = s[4:] + s[:4]
numeric = "".join(
str(ord(c) - 55) if c.isalpha() else c
for c in rearranged
)
return int(numeric) % 97 == 1 # Python ints are arbitrary-precision
PHP:
function isValidIban(string $iban): bool {
$s = strtoupper(preg_replace('/\s+/', '', $iban));
$rearranged = substr($s, 4) . substr($s, 0, 4);
$numeric = '';
foreach (str_split($rearranged) as $ch) {
$numeric .= ctype_alpha($ch) ? (string)(ord($ch) - 55) : $ch;
}
// bcmod handles the large integer safely
return bcmod($numeric, '97') === '1';
}
3つとも同じ4ステップに従います。唯一の本質的な違いは、各言語が大きな数値をどう扱うかです。Pythonはネイティブの多倍長整数を使い、PHPはbcmodに頼り、JavaScriptは逐次的に余りを求めるループで問題を回避します。
早めに検証し、二重に検証する
不正なIBANを適切なタイミングで検出できれば、誰もが面倒を避けられます。一貫して効果のある実践をいくつか挙げます。
- まずクライアント側で検証する。 利用者が入力している最中、またはフォーカスが外れたタイミングでブラウザ上でMOD-97チェックを実行し、送信後ではなくその場で入力ミスを直せるようにします。
- サーバー側で再検証する。 クライアントだけを信用してはいけません。改ざんや自動化されたリクエストに備えて、バックエンドでチェックを繰り返します。
- チェックの前に正規化する。 空白を除去し、すべて大文字に変換し、
A〜Zと0〜9以外の文字はアルゴリズムを実行する前に拒否します。 - フォーマットの誤りとチェックディジットの誤りをメッセージで区別する。 「このIBANはドイツには短すぎます」は、ありきたりな「無効なIBAN」よりも利用者にとってはるかに役立ちます。
チェックディジットの限界を忘れないでください。これは番号が構造的に正しいことを保証するだけで、口座が実在することまでは保証しません。完全に有効なIBANであっても、解約済みや存在しない口座を指している可能性があります。それを確かめるには、銀行への口座照会や専用サービスが必要です。
テスト用に有効なテストIBANを生成する
テストスイートに単一のIBANをハードコードするのは脆く、実在のIBANを使うのはコンプライアンス上のリスクです。代わりに、チェックディジットが有効な合成IBANを必要に応じて生成しましょう。
Random IBANで番号を作成すると、各値はISO 13616のMOD-97方式で構築されるため、規格に準拠したどのライブラリでも検証を通過します。次のような用途に使えます。
- 既知の正しい入力と不正な入力で、検証関数を単体テストする。
- 数百件のIBANをCSVやJSONにエクスポートして、一括インポートのQAを行う。
- 実在の口座番号に一切触れることなく、支払いの流れをデモする。
これをIBANバリデーターと組み合わせれば、自分の実装が参照アルゴリズムと一致することを確認できます。また、ある国がIBANのフォーマットを更新するたびに、テストデータを再確認しましょう。規格そのものについて詳しく知りたい場合は、IBAN番号とは何かのガイドをご覧ください。
よくある質問
なぜチェックディジットは必ず2桁なのですか?
IBAN規格では、総桁数にかかわらず、すべての国でチェックディジットの欄を2桁に固定しています。2桁あれば100通りの値が表せ、モジュロ97の要件(余りは0から96まで)を満たすのに十分でありながら、世界中で形式をコンパクトかつ統一的に保てます。
誤ったIBANでもMOD-97チェックを通過することはありますか?
はい、可能性は低いですが起こり得ます。このアルゴリズムはランダムな誤りのおよそ98.9%と、1桁のミスはすべて検出しますが、複数文字にわたる誤りのごく一部は、偶然にも余りが1になってしまうことがあります。チェックが保証するのは構造であって、口座が実在することや正しいことではありません。
チェックディジットと各国独自のチェックディジットの違いは何ですか?
IBANの2桁のチェックディジット(3〜4桁目)は、MOD-97によってIBAN全体を保護します。一部の国――スペイン、フランス、イタリアなど――は、これに加えて国内口座番号を保護するために、独自の各国チェックディジットをBBANの内部に埋め込んでいます。両者は異なる階層で機能する別個の仕組みです。
IBANの検証に多倍長整数ライブラリは必要ですか?
必ずしも必要ではありません。上で示した分割処理の方法は、余りを97未満に保ちながら数値を少しずつ処理するため、通常の整数演算で十分です。多倍長整数が必要になるのは、Pythonの例のようにint(numeric) % 97を一度に計算する場合だけです。
なぜ文字の変換はA=1ではなくA=10を使うのですか?
各文字は10から35までの2桁の数値に展開されます。1ではなく10から始めることで、どの文字も必ず2桁に対応するようになり、桁位置に基づく計算の整合性が保たれます。もしAが1だと、文字が1桁と2桁の値が混在することになり、計算が破綻してしまいます。
テスト用に有効なチェックディジットを持つIBANを生成するにはどうすればよいですか?
Random IBANジェネレーターを使ってください。国を選ぶだけで、正しく構造化されたBBANを構築し、チェックディジットを自動で計算します。すべての結果がMOD-97の検証を通過するため、実在の銀行データを一切公開することなく、そのままテストデータとして利用できます。