你的身份证号码都包含了哪些信息?

每位合法中国公民都会获得由政府机关颁发的如图身份证(多年前 P 的太渣了,凑合看吧),这 18 位的数字每位都有自己的含义,如下图所示:

其中,第 17 位是按照户口登记顺序,男性 13579,女性 24680,以保证同一地区的同年同月同日出生的人拥有唯一身份证号码(哦吼?看了一眼我身份证的第 17 位,好像发现了点什么🤭)。如果仅靠第 17 位来保证性别的同时又保证唯一,那单一性别的极端情况的容量只有 5,像最近的国庆四胞胎,假设四胞胎在极端情况下都是相同性别,那同地区同一天出生的其他人只有一个名额了,所以可能同一派出所会有多个代码。

第 18 位则是前 17 位的校验和,通过这一位,就能初步校验出身份证号码是否合法,在大多数情况下能发现填写错误,我们以11010519491231002X为例,讲解其具体的校验规则:

1、 前 17 位每位都对应一个权重值,将每位的身份证号码与权重值相乘,再求前 17 位的和 7*1 + 9*1 ... 4*0 + 2*0,结果为 167

权重 7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2
号码 1 1 0 1 0 1 1 9 0 0 0 1 0 1 1 0 0

2、 将上一步的计算结果 167 取 11 的模,得 2

为什么要取 11 的模呢?这是因为 11 是素数,而素数是只能被 1 和其本身整除的数,因此能在很大程度上保证取到余数,也保证了在大多数输入错误的情况下能检测到。

3、 余数对应校验位的值如下表,对照可得 X,正好是末位值,因此判定为符合规则

余数 0 1 2 3 4 5 6 7 8 9
校验和 1 0 X 9 8 7 6 5 4 3

了解了规则,我们就可以写出身份证合法验证的代码了

以下代码是一个简单的身份证号码合法校验,结果不一定准确,主要由于前 6 位是由民政部划分的行政区划码,部分行政区域会发生变化,经民政部的批准后会在其网页进行公示,因此同一地理位置的区域划分码是可能发生变化的,比如最近的温州龙港镇撤镇设市变更为龙港市,其区划码也随之由330327变化为330383,因此
龙港镇居民区划码是330327,龙港市的新生儿区划码就是330383,都是合法区划码。区划码的验证要考虑该地区的历史区划码。

PHP 版:

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<?php
/**
* 校验身份证号码是否非法
* @params string $id_number
* @return bool
*/
function check_id_number($id_number) {
// 前 17 位的权重值
define('WEIGHT', [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]);
// 余数对应的校验和
define('MOD_MAP', [1, 0, 'X', 9, 8, 7, 6, 5, 4, 3, 2]);

try {
// 输入参数校验
if (18 !== strlen($id_number)) {
throw new InvalidArgumentException('check_id_number function only accepts 18 characters. Input was:' . $id_number);
} elseif (
! is_numeric(substr($id_number, 0, 17)) || // 验证前 17 位是纯数字
(! is_numeric($id_number[17]) && ! in_array($id_number[17], ['x', 'X'])) || // 验证末位是数字或者大小写 x
! check_birthday(substr($id_number, 6, 8)) // 验证生日合法
) {
throw new InvalidArgumentException($id_number . ' is an invalid ID number!');
}

// 若包含 X,则统一转换为大写
if (!is_numeric($id_number)) {
$id_number = strtoupper($id_number);
}

// 截取前 17 位并转为数组
$check_code_array = str_split(substr($id_number, 0, 17));

// 计算前 17 位的校验权重
$sum = 0;
for ($i = 0; $i < 17; $i++) {
$sum += $check_code_array[$i] * WEIGHT[$i];
}

// 对校验权重取 11 的模并获取末位校验值比对
return MOD_MAP[$sum % 11] == $id_number[17];
} catch (InvalidArgumentException $e) {
return $e->getMessage();
}
}

// 验证身份证生日
function check_birthday($birthday) {
$year = substr($birthday, 0, 4);
$month = substr($birthday, 4, 2);
$day = substr($birthday, 6, 2);

if ($birthday > date('Ymd') || $birthday < '19000101') {
return false;
}

// checkdate() 已包含闰年验证
return checkdate($month, $day, $year);
}

var_dump(check_id_number('11010519491231002X'));

Python 版:

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
42
43
44
45
46
47
# -*- coding: utf8 -*-

from datetime import datetime, date

"""
校验身份证号码是否非法
"""

def check_id_number(id_number):
weight = (7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2)
mod_map = (1, 0, 'X', 9, 8, 7, 6, 5, 4, 3, 2)

try:
# 参数校验
if 18 != len(id_number):
raise ValueError('check_id_number function only accepts 18 characters. Input was:' + id_number)
elif not id_number[:17].isdigit() or \
(not id_number[17].isdigit() and id_number[17] not in ['x', 'X']) or \
not check_birthday(id_number[6:14]):
raise ValueError(id_number + ' is an invalid ID number!')

# 包含 X 时转换为大写
if not id_number.isdigit():
id_number = id_number.upper()

# 计算校验和
sum = 0
for wgt, num in zip(weight, id_number[:17]):
sum += wgt * int(num)

return str(mod_map[sum % 11]) == id_number[17]
except ValueError as e:
return e


def check_birthday(birthday):
try:
# 验证生日是否合法,包含闰年验证
datetime.strptime(birthday, '%Y%m%d').date()
except ValueError:
return False
else:
# 验证生日是否在指定范围
return '19000101' < birthday < date.today().strftime('%Y%m%d')


print(check_id_number('11010519491231002X'))

参考资料

  1. 行政区划代码 - 民政部
  2. 历史行政区划码 - 民政部
  3. 民政统计代码编制规则 - 民政部
  4. 中华人民共和国行政区划代码 - 维基百科
  5. 中华人民共和国公民身份号码 - 维基百科
  6. 如何成为一个有身份的人 - 回形针Paperclip
  7. 揭秘:你的身份证上究竟包含多少信息? - IT之家
  8. 怎样确定身份证第1516位的派出所代码?有没有各地列表? - JVKBRS的回答
  9. 身份证号的末位校验码算法最后一步模11是基于什么考虑? - 刘巍然-学酥的回答
因为热爱,所以执着。