すべての公的番号には二つの顔があります。一つは形式で、桁数と、どの位置にどの文字が使えるかを示します。もう一つは、国が公開している場合に限られますが、計算式です。末尾の文字が他の桁から導かれるなら、打ち間違いをした番号は「実在しない誰かの番号」に静かにすり替わるのではなく、算術の段階で落ちます。この違いが、本物のように振る舞うテスト用の値と、見た目だけが本物の値を分けます。
チェックディジットとは何か?
チェックディジットは冗長性です。発行者は意味を持つ桁を取り出し、決まった計算規則を当てはめ、その結果を末尾に付け加えるか、途中に埋め込みます。番号を持っている人は誰でも同じ規則をなぞって照合できます。計算した値と印字された値が食い違えば、少なくとも一文字が間違っていると分かります。
テストにとって重要な性質は二つあります。第一に、偶発的な誤りのほとんどを捕まえます。一桁の打ち間違いと、多くの方式で隣り合う二桁の入れ替えです。これはまさに、誰かがフォームに番号を打ち込むときの失敗の形です。第二に、それ以外は何も捕まえません。その番号がかつて発行されたのか、生きている人のものか、生成されたものかは分かりません。
つまりこれは誤り検出の符号であって、安全性の機能ではありません。チェックディジットが通る番号を作る生成器が作っているのは、算術的に整合した番号、それだけです。
mod-11 系という一族
最もよくある作りは、重み付きの和を 11 で割った余りに落とすものです。各桁に重みを掛け、積を足し、11 で割った余りを取り、その余りを文字に戻します。
似た作りでも、細部は国ごとに十分違います。重みの周期、走査する向き、余りが 10 や 11 のときの扱い、文字に使う字母、いずれもその国に固有です。ある国の実装を別の国へ持っていくと、もっともらしいが間違った結果になります。実装は近隣国のコードではなく、発行機関が公開している仕様に従う必要があります。
実務上の微妙な点もあります。これらの書類のいくつかは検証の層を複数持ちます。ある層で落ちても別の層では通るため、どの層で落ちたかを言わずに「無効」とだけ返す検証器はデバッグしづらくなります。層ごとに分けて確かめる価値があります。
国ごとに何を計算しているのか?
似ているという印象は、実装した瞬間に効いてくる違いを隠してしまいます。
| 制度 | 桁の構成 | 算法の考え方 |
|---|---|---|
| CPF(ブラジル) | 十一桁。意味を持つ九桁と検査用の二桁 | 重み付き和の余りを、単純な剰余ではなく対応表で文字に戻す |
| DNI(スペイン) | 八桁と一文字 | 数値を 23 で割った余りを、公開された文字表の添字に使う |
| T.C. Kimlik(トルコ) | 十一桁。検査用の桁を二つ持つ | 奇数位置と偶数位置の重み付き和から導く。先頭は零にならない |
| CURP(メキシコ) | 十八文字 | 生年月日・性別・出生州の構造規則の上に検査文字を載せる |
| RUT(チリ) | 数字と検査文字 | 下位から重みを循環させ、余りが 10 のときは文字 K に対応させる |
表のとおり、いずれも近い親戚ですが同じではありません。とくに落とし穴になるのが途中の値です。余りが被除数の符号を保つ言語では式が負になり得るため、比較の前に正規化が必要です。また、対応表を使う方式では、剰余をそのまま使う実装と結果がずれます。
Luhn とカード番号の検査
クレジットカードもチェックディジットを持っていて、多くの開発者が最初に実装するのがこれです。Luhn は mod 10 とも呼ばれ、右から走査します。末尾の桁を検査用に残し、左へ向かって一つおきに桁を二倍し、九を超えた結果からは九を引き、すべてを足し合わせて、合計が 10 で割り切れることを要求します。
これも打ち間違いの検査であって、真正性の検査ではありません。末尾の桁は他の桁で決まってしまうので、一桁の誤りは合計を壊します。ただし生き残る誤りの型もあります。隣り合う零と九を入れ替えても合計は変わらないため、この方式はその入れ替えを受け入れます。
発行者については何も言いません。検査を通る文字列が、割り当て済みの番号範囲に属するとは限りません。先頭の識別子と桁数の規則は別の検査なので、算術だけを満たす値は、決済フォームがそれを見る地点では本物のカードのように振る舞いません。クレジットカードツールが形式の規則と算術をまとめて適用しているのは、まさにそのためです。
向きを間違えるのも簡単です。右ではなく左から二倍したり、検査用の桁自体を二倍したりすると、有効な番号を拒否し、無効な番号を受け入れる検証器ができます。
自作の検証器をテストする
検証器も他のコードと同じように壊れます。短い境界値のセットでほとんどのバグが見つかります。
- すべて零。桁数は正しく、先頭桁は多くの国が禁じています。偶発的にではなく、正しい理由で拒否されることを確かめます。
- 桁数が一つずれる。有効と分かっている値から末尾の文字を落とし、検査の計算より先に桁数の確認が働くことを見ます。
- 検査文字が違う。最後の文字だけを変え、失敗が検査の段階に帰属されることを確かめます。
- 隣接の入れ替え。最後の二文字を交換します。桁数は保たれるので、算術ではなく誤り検出のほうを試すことになります。
- 先頭の零。零を付けた値と外した値を入れ、途中のどこかが数値として解釈していないかを見ます。
- 書式の変種。区切り記号、空白、小文字、そして全角の数字です。
これらはアプリケーションが呼ぶのと同じ関数に通します。単体では正しいのに、フォームへは別の正規表現で配線されている検証器は、バグが二つあることになります。しかも画面に出るのは経路の最後の段階だけです。
「有効」という一語に畳まれた三つの問い
「有効」という一語には三つの異なる問いが畳み込まれていて、答えも三つに分かれます。
形は合っているか。これは形式の規則が答えます。そして形式を通ったからといって、その番号が存在するかについては何も分かりません。
算術は成り立つか。成り立つのは、その国が算法を公開している場合だけで、しかもその算法が検出できる誤りの型に限られます。打ち消し合う二つの誤りは捕まりません。チェックディジットは、盲点が知られている打ち間違い検出器です。
その番号は発行されたことがあるか。番号の中の何もこれには答えません。発行の事実は、手元にない登録簿の中にあります。米国の社会保障番号は九桁で、公開されたチェックサムを持ちません。その構造は算術ではなく行政上のもので、割り当ての規則は時代とともに変わり、一度も発行されなかった範囲もあります。
算法が非公開の国ではどう扱うのか?
国が検査の算法を公開していないとき、生成器に取れる道は三つあります。でっち上げるか、黙っているか、何を保証するのかを正確に述べるかです。一つ目は積極的に有害で、権威があるように見えてそうではない番号を作ります。二つ目は利用者を推測させるだけです。
擁護できるのは三つ目だけです。算法が存在する場合、生成される値はそれと算術的に整合しているべきです。国が何も公開していない場合は、生成器は形式だけを保証し、その項目に「形式のみ」と表示すべきです。そうすれば、形の整った番号を有効な番号と取り違える人はいません。どの国が算法を公開しているかは、国一覧の各国ページで確認できます。ここで扱わなかった族の全体像はチェックディジットのアルゴリズムに、公開された算法を持たない番号の扱いはチェックディジットがない番号に整理しています。
テスト用の値はどう作ればよいのか?
検証する側が検査の桁を見るなら、検査が通る値を使ってください。本人確認の流れ、決済フォーム、行政の窓口との連携は、算術が成り立たない番号を拒否します。そこに引っかかる値は、正常系ではなくエラー処理のほうをテストすることになります。
意図的に壊した値は否定ケース用に使い、きれいな値のセットとは分けて保管します。役に立つ否定側の値は、一桁を入れ替えたもの、一桁を変えたもの、検査文字を省いたものです。どれも利用者が実際にやる間違いです。
実行をまたいで同じ値が必要なら、桁をファイルへコピーするのではなく識別子を使ってください。番号検証ツールでは、同じ識別子と同じ国を指定すれば同じ結果が返るため、スクリーンショットやデモの値が勝手に変わることがありません。各国の制度ごとの考え方は各国の公的番号を検証するルールはなぜそろわないのかで扱っています。
実在の人物の番号は、たとえ公開文書やサンプルコード、掲示板の投稿で見つけたものでも、絶対に使わないでください。本物の番号は、どう表面化したかにかかわらず個人データであり、テスト環境に置いた瞬間に、合成データの問題がコンプライアンスの問題に変わります。それらしい番号が必要なら、自分で生成してください。本物の値を使わずに済ませる考え方の背景は、テスト身元データとはで整理しています。
この記事で扱ったのは公的な番号の一般的な性質と、算法を説明するための構成上の例です。挙げた桁や値はどれも実在の人物・事業者・口座を指すものではなく、特定の番号の真偽や発行の有無を示すものではありません。
次の一手
手元にある番号を一つ選び、番号検証ツールに通して、どの層まで進むかを確かめてみてください。形式で止まった場合は、その制度に公開された算法がないという意味であり、入力が誤っているという意味ではありません。検証器を自作しているなら、境界値のセットを先に用意し、失敗がどの層に帰属するかをメッセージに含めるところから始めるのが近道です。