一、身份证号码的结构
中国的身份号码分为早期和现用两种,1999年以前叫"社会保障号",长15位;之后统一更改为18位,就是我们现在常用的"居民身份证号"。目前这两种号码都被统一叫做"居民身份证号"。这是第一个注意点。 关于这两种身份证号的组成结构,也是不相同的。
15位组成结构:地址码 + 出生日期码(6位) + 顺序号
18位组成结构:地址码 + 出生日期码(8位) + 顺序号 + 校验码
1.1、身份证号相关名词解释
1.2、15位的身份证号组成规则
1.3、18位的身份证号组成规则
二、身份证号码的合法性校验
2.1、验证规则
在做身份证号码校验时,一般针对15位的身份证号码,验证以下两点:
1、验证传入的字符串长度,必须是15位;
2、验证15位数据类型,必须是0-9中的整数;
在现在的实际使用中,15位是少数情况,绝大多数我们面对的是18位的身份证号码。针对18位的,验证点有如下两点:
1、验证传入的字符串长度,必须是18位;
2、验证前17位数据类型,必须是0-9中的整数,第18位必须从{0,1,2,3,4,5,6,7,8,9,X}中取值; 3、验证第18位校验码的值是否符合校验码算法(该算法在较大程度上可以验证身份证号码的真假);
2.2、18位身份证号码中校验码规则
18位身份证号中的校验码是使用特定算法,根据前17位上的值计算得来。计算规则分为如下三步: 第一步:将前17位各个位置上的数字乘以对应位置的加权因子(见加权因子表)并求和, 第二步:用求得的和除以11得到余数, 第三步:使用余数在校验码对照表(见校验码对照表)中得到相应的校验码。
加权因子表
校验码对照表
2.3、校验码算法举例
假设存在身份证号码:120102200301013457,最后一位校验码为7。
第一步:1x7+2x9+0x10+1x5+0x8+2x4+2x2+0x1+0x6+3x3+0x7+1x9+0x10+1x5+3x8+4x4+5x2 = 115
第二步:115/11 = 10······5
第三步:5 -------校验码对照表---------->7
反之如果最后一位的值和计算结果不一致,那么说明该身份证号是非法的。
三、使用Java验证身份证号码的正确性
package com.zhaoyin.utils; // TODO 使用时注意此处需要修改
import org.springframework.util.StringUtils;
import org.springframework.util.CollectionUtils;
import java.util.*;
/**
* @author wk.liu
* @date 0000/00/00 00:00
*
* 该类包含两个验证方法
* 1、idCardNoValidateMany:一次性验证多个身份证号
* 2、idCardNoValidateSingleton:验证单个身份证号
**/
public class IdCardNoUtils {
static int[] weights = {7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2}; // 新版身份证号 1-17 位的加权因子. 该数组顺序不能变,和index有对应关系
static String[] checkCode = {"1","0","X","9","8","7","6","5","4","3","2"}; // 新身份证号最后一位校验位的值. 该数组顺序不能变,和index有对应关系
static Set<String> newIdCardNoLastNOSet = new HashSet<>(Arrays.asList(checkCode));
static Set<String> numberSet = new HashSet<>(Arrays.asList("9","8","7","6","5","4","3","2","1","0"));
/**
* 中国身份证号验证,包含对新旧身份证验证。旧版身份证15位,新版身份证18位. 支持同时验证多个身份证号
* @param idCardNoList 多个身份证号列表
* @return 所有编号的验证结果合集 <idCardNo, true or false></>
*/
public static Map<String, Boolean> idCardNoValidateMany(List<String> idCardNoList) {
if (CollectionUtils.isEmpty(idCardNoList)) {
return Collections.emptyMap();
}
Map<String, Boolean> idCardNoMap = new HashMap<>(idCardNoList.size());
for (String idCardNo : idCardNoList) {
idCardNoMap.put(idCardNo, idCardNoValidateSingleton(idCardNo));
}
return idCardNoMap;
}
/**
* 中国身份证号验证,包含对新旧身份证验证。旧版身份证15位,新版身份证18位
* @param idCardNo 身份证号字符串
* @return true-验证通过,是身份证号,false-验证不通过,不是身份证号
*/
public static boolean idCardNoValidateSingleton(String idCardNo) {
if (!basicLegality(idCardNo)) { // 身份证号基础校验不通过,返回false
return false;
}
return oldIdCardNoValidate(idCardNo) || newIdCardNoValidate(idCardNo);
}
/**
* 验证旧版身份证号,没有校验位,做是否是数字验证
* @param oldIdCardNo 旧版身份证号
* @return true-验证通过,是身份证号,false-验证不通过,不是身份证号
*/
private static boolean oldIdCardNoValidate(String oldIdCardNo) {
return !StringUtils.isEmpty(oldIdCardNo) && oldIdCardNo.length() == 15;
}
/**
* 验证新版身份证号,有校验位
* @param newIdCardNo 新版身份证号
* @return true-验证通过,是身份证号,false-验证不通过,不是身份证号
*/
private static boolean newIdCardNoValidate(String newIdCardNo) {
if (StringUtils.isEmpty(newIdCardNo) || newIdCardNo.length() != 18) { // 不是合格的新版身份证号
return false;
}
int sum = 0; // 此处用int即可。即使每位都是9,求和之后也远远不会溢出,达到int的最大值
for (int i = 0; i < 17; i++) {
int c = Integer.parseInt(String.valueOf(newIdCardNo.charAt(i)));
sum += weights[i] * c;
}
int checkCodeIndex = sum % 11;
return checkCode[checkCodeIndex].equals(newIdCardNo.substring(17));
}
/**
* 身份证号的基本合法性校验。15位保证全是数字,18位保证除最后一位是大写X外,全是数字
* @return true-验证通过,是身份证号,false-验证不通过,不是身份证号
*/
private static boolean basicLegality(String idCardNo) {
if (StringUtils.isEmpty(idCardNo) || (idCardNo.length() != 15 && idCardNo.length() != 18)) { // 不是合格的身份证号,新版18位,旧版15位
return false;
}
String number = idCardNo;
if (idCardNo.length() == 18) { // 新身份证号18位,最后一位可能包含X,单独验证
String lastNumber = idCardNo.substring(17);
boolean contains = newIdCardNoLastNOSet.contains(lastNumber);
if (!contains) return false; // 如果不包含在这里面,那么验证不通过
number = number.substring(0, 17);
}
// 做数字验证
for (int i = 0; i < number.length() ; i++) {
boolean contains = numberSet.contains(String.valueOf(number.charAt(i)));
if (!contains) return false; // 如果不包含在这里面,那么验证不通过
}
return true;
}
}