python

Python 身份证号码校验算法详解:从编码规则到穷举验证

2026-07-06 #社会工程学#python

身份证号码不是随机数字,它有严格的编码规则——前 6 位是地区码,中间 8 位是出生日期,最后 1 位是校验码。这篇彻底拆解身份证号码的生成与校验算法,写一个能”算”出完整身份证号的 Python 工具。

声明:本文仅讨论身份证号码的数学结构和编码规则,所涉及的校验算法为公开知识。严禁用于非法获取或验证他人身份信息。


18 位身份证号码的结构

1
2
3
4
5
┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
│ 3 │ 7 │ 1 │ 5 │ 2 │ 1 │ 1 │ 9 │ 9 │ 5 │ 0 │ 6 │ 1 │ 5 │ 1 │ 2 │ 3 │ X │
└──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘
└─── 地区码 ──┘└──── 出生日期 ────┘└ 顺序码 ┘└ 校验码 ┘
1-6 位 7-14 位 15-17 位 18 位
位置 位数 含义 取值范围
1-6 6 行政区划代码 国家标准 GB/T 2260
7-14 8 出生日期 YYYYMMDD 格式
15-17 3 顺序码 000-999,奇数为男性,偶数女性
18 1 校验码 0-9 或 X(罗马数字 10)

校验码算法(ISO 7064:1983 MOD 11-2)

这是最关键的部分——最后一位不是随便填的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def calculate_check_digit(id_17):
"""
计算身份证第 18 位校验码

加权因子: [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
校验码映射: {0:'1', 1:'0', 2:'X', 3:'9', 4:'8', 5:'7', 6:'6', 7:'5', 8:'4', 9:'3', 10:'2'}
"""
if len(id_17) != 17:
raise ValueError("需要 17 位数字")

# 1. 加权求和
weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
total = sum(int(digit) * weight for digit, weight in zip(id_17, weights))

# 2. 取模
remainder = total % 11

# 3. 查表得到校验码
check_map = {0: '1', 1: '0', 2: 'X', 3: '9', 4: '8',
5: '7', 6: '6', 7: '5', 8: '4', 9: '3', 10: '2'}

return check_map[remainder]

# 验证
print(calculate_check_digit('37152119950615123')) # 输出: X

验证流程

1
2
3
4
5
6
身份证前 17 位: 3 7 1 5 2 1 1 9 9 5 0 6 1 5 1 2 3
加权因子: 7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2

∑(digit × weight) = 3×7 + 7×9 + 1×10 + ... + 3×2 = 198
198 % 11 = 0
查表 {0: '1', 1: '0', 2: 'X', ...} → 校验码 = 'X'

完整校验函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import re
from datetime import datetime

def validate_id_card(id_number):
"""验证身份证号码是否合法"""
if len(id_number) != 18:
return False, "长度不是 18 位"

if not id_number[:17].isdigit():
return False, "前 17 位必须为数字"

# 1. 校验出生日期
birth_str = id_number[6:14]
try:
birth_date = datetime.strptime(birth_str, '%Y%m%d')
# 不能是未来日期
if birth_date > datetime.now():
return False, "出生日期不能是未来时间"
# 不能早于 1900 年(合理范围)
if birth_date.year < 1900:
return False, "出生日期不合理"
except ValueError:
return False, "出生日期格式无效"

# 2. 校验最后一位
calculated = calculate_check_digit(id_number[:17])
actual = id_number[17].upper()

if calculated != actual:
return False, f"校验码错误(期望: {calculated}, 实际: {actual})"

return True, "合法"

穷举生成:已知前 14 位 + 性别 → 推完整号码

如果你知道一个人的出生地区、生日和性别,可以穷举所有可能的身份证号:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
def generate_possible_ids(region_code, birth_date, gender):
"""
根据地区码 + 生日 + 性别穷举可能的身份证号

region_code: 6 位地区码
birth_date: '19950615'
gender: 'male' | 'female'
"""
valid_ids = []

# 前 14 位固定
prefix = f"{region_code}{birth_date}"

# 顺序码: 000-999
for seq in range(1000):
seq_str = f"{seq:03d}"

# 性别校验:奇数为男,偶数为女
if gender == 'male' and seq % 2 == 0:
continue
if gender == 'female' and seq % 2 == 1:
continue

# 生成 17 位
id_17 = f"{prefix}{seq_str}"
# 计算校验位
check = calculate_check_digit(id_17)
# 完整 18 位
full_id = f"{id_17}{check}"

# 二次验证
valid, _ = validate_id_card(full_id)
if valid:
valid_ids.append(full_id)

return valid_ids

# 示例:山东聊城高唐(371521) + 1995年6月15日 + 男
possible = generate_possible_ids('371521', '19950615', 'male')
print(f"可能的身份证号: {len(possible)} 个")
# 7000-7999 范围(奇数顺序码)约 500 个

地区码验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 地区码结构(简化版)
REGION_CODES = {
'11': '北京市',
'12': '天津市',
'31': '上海市',
'37': '山东省',
'44': '广东省',
# ... 完整的 GB/T 2260 行政区划代码
}

def parse_region(id_number):
"""解析身份证地区码"""
province_code = id_number[:2]
city_code = id_number[:4]
district_code = id_number[:6]

province = REGION_CODES.get(province_code, '未知省份')
return {
'province': province,
'city_code': city_code,
'district_code': district_code,
}

15 位旧版身份证升级 18 位

老身份证是 15 位(没有世纪位和校验码),升级算法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def upgrade_15_to_18(id_15):
"""15 位身份证升级为 18 位"""
if len(id_15) != 15:
raise ValueError("需要 15 位身份证号")

# 插入世纪位 '19' + 年份
id_17 = id_15[:6] + '19' + id_15[6:]

# 计算校验码
check = calculate_check_digit(id_17)

return id_17 + check

# 示例
old_id = '371521950615123'
new_id = upgrade_15_to_18(old_id)
print(f"15 位: {old_id}")
print(f"18 位: {new_id}")

总结

身份证号码是教科书级别的校验编码设计——加权求和、模 11 校验、性别嵌入顺序码。理解了这个算法,你就能验证、生成、升级任何合法的身份证号。


评论
分享