Skip to content
Utilities2026-10-30Shuai Li6 min read

China Resident ID Numbers: Structure, the MOD 11-2 Check Digit, and Why One in Eleven Ends in X

If your platform onboarded even one mainland-China user, you hold 18-character resident identity numbers (身份证号). They look like a birthdate glued to random digits, and the strangest part — a trailing Roman X — causes a disproportionate share of formatting bugs. This guide breaks down the full structure, the check-digit algorithm, and the ways real copies get mangled, all verifiable in the ID card validator, which runs entirely in your browser.

The 18 digits, field by field

PositionsFieldNotes
1–2Province11 Beijing, 31 Shanghai, 44 Guangdong … plus 71 Taiwan, 81 Hong Kong, 82 Macao in the standard table
3–6City / county divisionAdministrative code; with the province it forms the 6-digit registration division
7–14BirthdateYYYYMMDD, real calendar date (February 30th is rejectable before you ever touch the checksum)
15–17SequenceAssigned within the division on the birthdate; the 17th digit's parity encodes sex — odd male, even female
18Check digitISO 7064 MOD 11-2 over the first 17; 10 renders as X

Two cheap rejections most validators skip: the birthdate must be a real date, and the province prefix must exist. Both cost two lines and catch more garbage than the checksum does, because they encode what the garbage got wrong semantically.

The check digit, exactly

Sum each of the first 17 digits times a fixed weight, take the remainder mod 11, and reflect:

weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]

v = (12 - (Σ digitᵢ × weightᵢ) mod 11) mod 11
check = str(v) if v < 10 else "X"

The weights are powers of 2 mod 11, counted from the right: position 17 gets 2¹, position 16 gets 2², … wrapping with period 10 (2¹⁰ ≡ 1 mod 11). The same primitive-root construction we described for the Unified Social Credit Code — there with base 3 mod 31, here with base 2 mod 11 — and the payoff is identical: every weight is a nonzero residue, so every single-digit substitution is caught, and since adjacent weights always differ (ratio 2 or its inverse, never ≡ 1 mod 11), every adjacent transposition is caught too. That beats Luhn, the mod-10 scheme behind credit cards, which misses transpositions of digit pairs like 0/9.

Worked example, hand-checkable — body 11010119900815001 (Beijing division 110101, born 1990-08-15, sequence 001):

  • First four contributions: 1×7 + 1×9 + 0×10 + 1×5 = 21
  • Full sum through position 17: 199
  • 199 mod 11 = 1; 12 − 1 = 11 → 11 mod 11 = 0; check digit 0

The valid number is 110101199008150010. Change the sequence to 002 — same division, same birthdate, legal-looking — and the correct check digit changes; the validator rejects the original 0 with a checksum error. You can reproduce every step, including the birthdate and gender parse, in the validator.

The X, handled properly

The check value 10 has no single decimal digit, so the standard writes X. Three practical rules:

  1. Uppercase before validating. Lowercase x in your data is a transcription artifact; the standard's character is X. (This is also why the check character must be stored in a text column — an integer column physically cannot hold it, and the IDs that end in X — about 1 in 11 — silently corrupt on import.)
  2. Full-width X (U+FF38) is the PDF/IME artifact, same as with USCCs; normalize width (NFKC) first.
  3. Never "fix" a failing check digit by recomputing it — that manufactures a well-formed number that identifies nobody and defeats every later check. Route to manual review.

How real copies get corrupted

SymptomUsual causeHandling
Lowercase x or full-width XIME / PDF / OCRUppercase, NFKC, re-validate
O for 0OCR autocorrectMap O→0 only during repair attempts, never blanket
15 digitsLegacy pre-1999 numbers (no century in the birthdate, no check digit at all)These are structurally different — flag for upgrade workflows, don't checksum them
Future birthdate or Feb 30Typo'd birthdate; validator enforces real calendar datesReject before checksum; the error message should say which field
Valid checksum, wrong personCopy-paste from the wrong row / sibling's numberOut of scope for any checksum — see below

That last row is the honest boundary: a checksum-valid ID is well-formed. It does not prove the number was ever issued, that it belongs to the person who typed it, or that the person is who they claim. That assurance requires government verification services, which the validator deliberately does not attempt — it computes locally, which is also why pasting an ID into it leaks nothing. For test fixtures you want numbers that are checksum-valid and provably synthetic: the China test data generator produces them with correct division shapes and real dates.

FAQ

Do all ID numbers end in X for people born on certain days? No — the trailing X falls out of the arithmetic (check value 10, roughly 1 in 11 numbers regardless of birthdate).

Is the 18-digit format used in Hong Kong / Macao / Taiwan? No. Those regions have their own formats (HK's 8-character, Taiwan's 10-character alphabetic schemes). The standard table reserves province codes 71/81/82 for them, but issued numbers there differ — our guides cover the mainland format only.

Why do some systems store 19 characters? They are padding or appending a space. The number is exactly 18 characters, and the last one may be X.

Is it safe to paste a real ID into an online validator? Not into one that uploads. Ours computes in your browser and sends nothing — check any validator's Network panel before trusting it with a national ID.

In this series: China identifiers

Implementation notes

The reference implementation is one weight table, one check map, and — the part worth copying — a real-calendar birthdate check via a UTC round-trip (new Date(Date.UTC(y, m-1, d)) must land on the same day it started), which catches Feb 30 and friends without a month-length lookup table. The one bug class we saw in the wild: validators that uppercase but don't NFKC-normalize, so full-width digits 1990 fail charset with a confusing message instead of a repairable one. Round-trip tests (generate N syntactically valid numbers, validate all, tamper each digit, assert rejection) pin all of it.