身份证校验位怎么算:ISO 7064 算法、15 位升 18 位与末位 X 传参的 3 个坑
身份证第 18 位校验位的 ISO 7064 MOD 11-2 算法(加权因子表+余数映射表)、15 位升 18 位规则与可直接跑的 Python 实现;末位 X 传参必须转小写 x 否则 1103、校验位通过不等于号码真实、1103 的完整排查顺序。
一句话结论
身份证第 18 位是校验位:前 17 位按 ISO 7064 MOD 11-2 加权求和、取 mod 11 后查表得出。它只能证明"这个号在格式上算得通",不能证明号码真实存在——本地校验通过后仍要调核验接口确认一致性。传参时还有一个高频翻车点:算法算出来的是大写 X,而接口要求转小写 x,不转返回 1103 编码不合法。
一、算法:两张表说清
| 步骤 | 规则 |
|---|
| 1. 取本体码 | 身份证号前 17 位 |
|---|---|
| 2. 乘加权因子 | 7、9、10、5、8、4、2、1、6、3、7、9、10、5、8、4、2 |
| 3. 求和取余 | S = Σ(数字 × 因子),T = S mod 11 |
| 4. 查表得校验位 | T=0→1,1→0,2→X,3→9,4→8,5→7,6→6,7→5,8→4,9→3,10→2 |
第 4 步是最常见的实现错误:X 对应的是余数 2,不是余数 10。网上不少代码写成 {10: 'X'} 再拿余数直接查——那是把另一套等价公式 R=(12−T) mod 11 里的 R 和 T 混了。两种写法要配套使用,直接拿 T 去查 {10:'X'},会把所有末位为 X 的号码判成校验失败,而这类号码占全部号码的约 1/11。
WEIGHTS = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
CHECK = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'] # 下标 = 余数 T
def check_digit(first17: str) -> str: s = sum(int(c) * w for c, w in zip(first17, WEIGHTS)) return CHECK[s % 11]
def is_valid_id(id18: str) -> bool: id18 = id18.strip().upper() return len(id18) == 18 and id18[:17].isdigit() \ and check_digit(id18[:17]) == id18[17]
验证用例:前 17 位为 11010519491231002 时 S=167、T=2、校验位 X——即国标示例号 11010519491231002X。写完代码先拿它跑一遍,跑不过就是表抄错了。
先查表再算日期:出生日期段(第 7-14 位)本身有合法性——19990230 这种不存在的日期,校验位算得再对也是废号。日期校验比校验位更早拦住脏数据,两道都要有。
二、15 位升 18 位
15 位老证号没有校验位,结构是 6 位地址码 + 6 位出生日期(YYMMDD)+ 3 位顺序码;18 位是 6 位地址码 + 8 位出生日期(YYYYMMDD)+ 3 位顺序码 + 1 位校验码。升位规则:
19(15 位证 1999 年停发,持证人出生年份均为 19XX);def upgrade15(id15: str) -> str:
id15 = id15.strip() if len(id15) != 15 or not id15.isdigit(): raise ValueError("15 位号码应为纯数字") body = id15[:6] + "19" + id15[6:] # → 17 位 return body + check_digit(body) # → 18 位
升位后用 110105491231002 验证,应得到 11010519491231002X。
接口不收 15 位号:位数不对直接 1103 编码不合法,接口不会帮你升位。批量数据里的老号必须在入参校验阶段本地升位,等接口报错才发现,批量任务已经跑了一半。
三、传参的 3 个坑
坑 1:算法输出大写 X,接口要小写 x
国标校验字符集里的 X 是大写(它是罗马数字 10,不是字母),证件上印的也是大写;但接口文档明确要求末位 X 转小写 x,不转返回 1103。这不是"不一致",是格式拒绝,不计费,但会污染你的失败率统计。
id_for_api = id18.strip().upper().replace("X", "x") # 先归一化,再算签名
顺序不能反:身份证核验的签名串是 md5(mall_id + realname + idcard + tm + appkey),idcard 参与签名。先算签名再转小写,服务端用收到的小写值重算签名必然对不上,报的还是 1104 签名不合法——错误码把你的排障方向带偏到拼接顺序上,而根因只是大小写。
坑 2:校验位通过 ≠ 号码真实
校验位的设计目标是发现录入错误(抄错一位、串位),不是鉴别真伪。一个按算法编出来的 18 位号码可以通过全部本地校验,但在权威库里查无此号。所以本地校验的定位是表单前置拦截:把格式错误挡在请求之前,减少无效调用;真实性核验必须走接口,返回 1001 不一致 或 1002 库中无此号 都是有效结果。
坑 3:校验位对了,仍可能返回 1103
1103 编码不合法 的完整触发条件不止校验位:末位大小写、前后空格与零宽字符、全角数字、15 位未升位,任何一条都会触发。排查顺序按命中率排:
X 没转小写;strip() 挡不住零宽字符,要用正则清;四、合规边界
校验位计算是纯本地运算:不联网、不外发号码、不产生接口调用,放开在浏览器表单里做完全没问题,还能在用户提交前就提示"号码有误"。但要注意两件事:
五、上线检查单
□ 校验位用加权因子表 7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2,下标映射 1 0 X 9 8 7 6 5 4 3 2
□ 已用国标示例号 11010519491231002X 跑通自测 □ 出生日期段单独校验(不存在的日期直接拒) □ 归一化(大写 X → 小写 x、去空白、全角转半角)发生在算签名之前 □ 15 位老号入参阶段本地升位,升位后重算校验位 □ 性别位取值:18 位制取第 17 位,15 位制取第 15 位 □ 失败分支区分 1103(格式,不计费)与 1001(不一致,计费),别合并处理 □ 本地校验日志脱敏,不落完整号码
相关阅读
关于诺正通
校验位验算在本地完成,调用核验接口才计费。身份证核验 API 按量计费、100 次免费试用用于联调,具体价格向商务询价。