身份证号的合法性判断

3,509 阅读5分钟

一、身份证号码的结构 ​

        中国的身份号码分为早期和现用两种,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;
    }
}