身份证号码的编码规则及校验

前言

根据最新修订的《中华人民共和国居民身份证法》第二十三条规定,依照《中华人民共和国居民身份证条例》领取的居民身份证,自2013年1月1日起停止使用。即一代身份证已于2013年1月1日起停止使用,本文内容适用于二代身份证,如无特殊说明,本文中所说的身份证皆指二代身份证。

笔者目前使用的jdk版本是1.6.0_29,Eclipse版本是Juno Release,Build id 20120614-1722。如无特殊说明,本文所有的Java代码都是基于此。

本文包含大量数学公式和Java代码,手机端浏览体验较差,可在手机端浏览文字性内容,数学公式和Java代码在PC端浏览,同时建议准备好纸、笔用于数学公式的演算。具有一定数学基础的读者也可以忽略此条建议。

本文中提供的Java代码都是笔者逐行编写、反复斟酌,以求能够抛砖引玉,使初学者可以孜孜不倦,在Java的学习道路上更进一层楼。本文内容,不足之处,欢迎大家批评指正。

修订记录

版本号 修订日期 修订说明
V0.1 2018/08/13 初稿
V1.0 2018/09/02 发布

参考资料

  1. 中华人民共和国居民身份证法
  2. 中华人民共和国国家标准 GB/T 2260-2007 中华人民共和国行政区划代码
  3. 中华人民共和国国家标准 GB 11643-1999 公民身份证号码
  4. 中华人民共和国国家标准 GB/T 17710-1999 数据处理 校验码系统
  5. 中华人民共和国国家标准 GB/T 17710-2008 信息技术 安全技术 校验字符系统
  6. ISO 7064:1983 Data processing - Check character systems
  7. ISO/IEC 7064:2003 Information technology - Security techniques - Check character systems

身份证号码的编码规则

身份证号码共18位,由17位本体码和1位校验码组成。

  1. 前6位是地址码,表示登记户口时所在地的行政区划代码,依照《中华人民共和国行政区划代码》国家标准(GB/T2260)的规定执行;
  2. 7到14位是出生年月日,采用YYYYMMDD格式;
  3. 15到17位是顺序码,表示在同一地址码所标识的区域范围内,对同年、同月、同日出生的人编订的顺序号,顺序码的奇数分配给男性,偶数分配给女性,即第17位奇数表示男性,偶数表示女性;
  4. 第18位是校验码,采用ISO 7064:1983, MOD 11-2校验字符系统,计算规则下一章节说明。

一代身份证与二代身份证的区别在于:

  1. 一代身份证是15位,二代身份证是18位;
  2. 一代身份证出生年月日采用YYMMDD格式,二代身份证出生年月日采用YYYYMMDD格式;
  3. 一代身份证无校验码,二代身份证有校验码。

校验码计算规则

身份证号码中各个位置上的号码字符值应满足下列公式的校验:
\sum_{i=1}^{18} \left ( a_{i} \times W_{i}\right ) \equiv 1 \pmod{11}

  • i表示号码字符从右至左包括校验码字符在内的位置序号;
  • a_{i}表示第i位置上的号码字符值,a_{1}是身份证号码第18位校验码;
  • W_{i}表示第i位置上的加权因子,加权因子计算公式:W_{i} = 2^{i-1} \pmod{11}

举例说明:

笔者身份证号码为370683198901117657,根据上述公式进行校验。

\sum_{i=1}^{18} \left ( a_{i} \times W_{i}\right ) = 397 \equiv 1 \pmod{11}

大家可以根据此方法验证自己的身份证号码。

了解了身份证号码的校验公式后,根据同余定理可证得身份证号码校验码的计算公式:
\begin{align*} & \because W_{1} = 2^{1-1} \pmod{11} = 1 \pmod{11} = 1 \\ & \therefore a_{1} + \sum_{i=2}^{18} ( a_{i} \times W_{i} )\equiv 1 \pmod{11} \\ & \therefore a_{1} \pmod{11} + \sum_{i=2}^{18} ( a_{i} \times W_{i} ) \pmod{11} \equiv 1 \pmod{11} \\ & \therefore a_{1} \pmod{11} + \sum_{i=2}^{18} ( a_{i} \times W_{i} ) \pmod{11} \equiv 12 \pmod{11} \\ & \therefore a_{1} \pmod{11} \equiv ( 12 - \sum_{i=2}^{18} ( a_{i} \times W_{i} ) \pmod{11} ) \pmod{11} \\ & \because 0 \leqslant a_{1} \leqslant 10 \land a_{1} \in N \\ & \therefore a_{1} = ( 12 - \sum_{i=2}^{18} ( a_{i} \times W_{i} ) \pmod{11} ) \pmod{11} \end{align*}

a_{1}值等于10时,用罗马数字符X表示。此处需要注意:是罗马数字X,不应理解为英文字母X

实际应用

在金融行业软件系统中,对于身份证号码的采集、校验用途甚广。

  1. 身份证号码前6位,可采集客户户籍所在地,只需将国家标准GB/T 2260中定义的行政区划代码导入数据库,程序中进行映射即可;但需要注意的是,行政区划代码每隔几年会修订一次,从笔者手上的2007版来看,共经历了1982年、1984年、1986年、1988年、1991年、1995年、1999年、2002年、2007年九次修订,所以要预留更新机制;
  2. 身份证号码7到14位,可采集客户的出生日期、年龄、生日;
  3. 身份证号码17位,可采集客户的性别,奇数表示男性,偶数表示女性;
  4. 身份证号码的验证渠道,不管验证成功与否,往往都是收费的,比如银行渠道、公安部渠道,在发往这些渠道验证之前,先对其进行长度、正则表达式、校验码的验证,能够适当提高收费验证的成功率,节省成本支出;而且也可以提升用户体验,在用户输入错误时及时反馈而不必等待验证渠道结果的返回。

下面,就以实际代码为例,说明身份证号码校验的方法。首先,给出身份证号码的正则表达式:

/**
 * 18位二代身份证号码的正则表达式
 */
public static final String REGEX_ID_NO_18 = "^"
        + "\\d{6}" // 6位地区码
        + "(18|19|([23]\\d))\\d{2}" // 年YYYY
        + "((0[1-9])|(10|11|12))" // 月MM
        + "(([0-2][1-9])|10|20|30|31)" // 日DD
        + "\\d{3}" // 3位顺序码
        + "[0-9Xx]" // 校验码
        + "$";

/**
 * 15位一代身份证号码的正则表达式
 */
public static final String REGEX_ID_NO_15 = "^"
        + "\\d{6}" // 6位地区码
        + "\\d{2}" // 年YYYY
        + "((0[1-9])|(10|11|12))" // 月MM
        + "(([0-2][1-9])|10|20|30|31)" // 日DD
        + "\\d{3}"// 3位顺序码
        + "$";

校验身份证号码:

/**
 * 校验身份证号码
 * 
 * <p>
 * 适用于18位的二代身份证号码
 * </p>
 * 
 * @param IDNo18 身份证号码
 * @return true - 校验通过<br>
 *         false - 校验不通过
 * @throws IllegalArgumentException 
 *             如果身份证号码为空或长度不为18位或不满足身份证号码组成规则
 *             <i>6位地址码+
 *             出生年月日YYYYMMDD+3位顺序码
 *             +0~9或X(x)校验码</i>
 */
public static boolean checkIDNo(String IDNo18) {
    // 校验身份证号码的长度
    if (!checkStrLength(IDNo18, 18)) {
        throw new IllegalArgumentException();
    }
    // 匹配身份证号码的正则表达式
    if (!regexMatch(IDNo18, REGEX_ID_NO_18)) {
        throw new IllegalArgumentException();
    }
    // 校验身份证号码的验证码
    return validateCheckNumber(IDNo18);
}

/**
 * 校验字符串长度
 * 
 * @param inputString 字符串
 * @param len 预期长度
 * @return true - 校验通过<br>
 *         false - 校验不通过
 */
private static boolean checkStrLength(String inputString, int len) {
    if (inputString == null || inputString.length() != len) {
        return false;
    }
    return true;
}

/**
 * 匹配正则表达式
 * 
 * @param inputString 字符串
 * @param regex 正则表达式
 * @return true - 校验通过<br>
 *         false - 校验不通过
 */
private static boolean regexMatch(String inputString, String regex) {
    return inputString.matches(regex);
}

/**
 * 校验码校验
 * <p>
 * 适用于18位的二代身份证号码
 * </p>
 * 
 * @param IDNo18 身份证号码
 * @return true - 校验通过<br>
 *         false - 校验不通过
 */
private static boolean validateCheckNumber(String IDNo18) {
    // 加权因子
    int[] W = { 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2 };
    char[] IDNoArray = IDNo18.toCharArray();
    int sum = 0;
    for (int i = 0; i < W.length; i++) {
        sum += Integer.parseInt(String.valueOf(IDNoArray[i])) * W[i];
    }
    // 校验位是X,则表示10
    if (IDNoArray[17] == 'X' || IDNoArray[17] == 'x') {
        sum += 10;
    } else {
        sum += Integer.parseInt(String.valueOf(IDNoArray[17]));
    }
    // 如果除11模1,则校验通过
    return sum % 11 == 1;
}

计算校验码:

/**
 * 计算身份证号码的校验码
 * <p>
 * 适用于18位的二代身份证号码,身份证号码由17位本体码和1位校验码组成
 * </p>
 * 
 * @param masterNumber 本体码
 * @return 身份证号码
 * @throws IllegalArgumentException 
 *             如果本体码为空或长度不为17位或不满足本体码组成规则
 *             <i>6位地址码+
 *             出生年月日YYYYMMDD+3位顺序码</i>
 */
public static String computeIDNoCheckNumber(String masterNumber) {
    // 校验本体码的长度
    if (!checkStrLength(masterNumber, 17)) {
        throw new IllegalArgumentException();
    }
    // 匹配本体码的正则表达式
    if (!regexMatch(masterNumber, REGEX_MASTER_NUMBER)) {
        throw new IllegalArgumentException();
    }
    // 计算校验码
    String checkNumber = computeCheckNumber(masterNumber);
    // 返回本体码+校验码=完整的身份证号码
    return masterNumber + checkNumber;
}

/**
 * 计算校验码
 * <p>
 * 适用于18位的二代身份证号码
 * </p>
 * 
 * @param masterNumber 本体码
 * @return 校验码
 */
private static String computeCheckNumber(String masterNumber) {
    // 加权因子
    int[] W = { 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2 };
    char[] masterNumberArray = masterNumber.toCharArray();
    int sum = 0;
    for (int i = 0; i < W.length; i++) {
        sum += Integer.parseInt(String.valueOf(masterNumberArray[i])) * W[i];
    }
    // 根据同余定理得到的校验码数组
    String[] checkNumberArray = { "1", "0", "X", "9", "8", "7", "6", "5", "4",
            "3", "2" };
    // 得到校验码
    String checkNumber = checkNumberArray[sum % 11];
    // 返回校验码
    return checkNumber;
}

虽然15位的一代身份证已经停用,但是难免有需要用到将15位的一代身份证升级为18位的二代身份证的情形,代码示例如下:

/**
 * 15位一代身份证号码升级18位二代身份证号码
 * <p>
 * 为15位的一代身份证号码增加年份的前2位和最后1位校验码
 * </p>
 * 
 * @param IDNo15 15位的一代身份证号码
 * @return 18位的二代身份证号码
 */
public static String updateIDNo15to18(String IDNo15) {
    // 校验身份证号码的长度
    if (!checkStrLength(IDNo15, 15)) {
        throw new IllegalArgumentException();
    }
    // 匹配身份证号码的正则表达式
    if (!regexMatch(IDNo15, REGEX_ID_NO_15)) {
        throw new IllegalArgumentException();
    }
    // 得到本体码,因一代身份证皆为19XX年生人,年份中增加19,组成4位
    String masterNumber = IDNo15.substring(0, 6) + "19" + IDNo15.substring(6);
    // 计算校验码
    String checkNumber = computeCheckNumber(masterNumber);
    // 返回本体码+校验码=完整的身份证号码
    return masterNumber + checkNumber;
}

同余

同余的定义

给定一个正整数m,如果两个整数a和b满足a-b能够被m整除,即(a-b)/m得到一个整数,那么就称整数a与b对模m同余,记作a \equiv b \pmod m

同余的性质

  1. 反身性:a \equiv a \pmod m
  2. 对称性:若a \equiv b \pmod m,则b \equiv a \pmod m
  3. 传递性:若a \equiv b \pmod mb \equiv c \pmod m,则a \equiv c \pmod m
  4. 同余式相加:若a \equiv b \pmod mb \equiv d \pmod m,则a \pm c \equiv b \pm d \pmod m
  5. 同余式相乘:若a \equiv b \pmod mc \equiv d \pmod m,则ac \equiv bd \pmod m

校验码计算规则章节中,用到了以下公式
( a + b ) \pmod{m} = ( a \mod m + b \mod m ) \pmod{m}
我们以此为例进行证明,设
\begin{align*} a = q_{1}m + r_{1} \\ b = q_{2}m + r_{2} \end{align*}

\begin{align*} ( a + b ) \pmod{m} &= ( q_{1}m + r_{1} + q_{2}m + r_{2} ) \pmod{m} \\ &= ( ( q_{1} + q_{2} ) m + r_{1} + r_{2} ) \pmod{m} \\ &= ( r_{1} + r_{2} ) \pmod{m} \\ &= ( a \mod m + b \mod m ) \pmod{m} \\ \end{align*}

校验字符系统

关于校验字符系统,其国际标准ISO 7064有2个版本,分别是ISO 7064:1983和ISO/IEC 7064:2003,从内容上来说,除了表面的调整,本质上没有区别,我想可以理解为是IEC成立后对其工作范围主权的宣示。那么,对应的国家标准,也有了2个版本,分别是GB/T 17710-1999和GB/T 17710-2008,基本上保证了对国际标准的高水准翻译水平,使英文阅读能力欠佳的读者可以通过国家标准来体会国际标准制定的严谨,并从中受益。

标准中,提供了如下几个校验字符系统,基本涵盖日常所需。身份证号码校验使用的ISO 7064, MOD 11-2,便是其中之一。在实际项目中,可按需选用。

系统类型 系统名称 适用范围 校验码数目及类型 数字表示法
纯系统 ISO 7064, MOD 11-2 数字 1位数字或附加符X 1
纯系统 ISO 7064, MOD 37-2 字母数字 1位数字或字母或附加符* 2
纯系统 ISO 7064, MOD 97-10 数字 2位数字 3
纯系统 ISO 7064, MOD 661-26 字母 2位字母 4
纯系统 ISO 7064, MOD 1271-36 字母数字 2位数字或字母 5
混合系统 ISO 7064, MOD 11,10 数字 1位数字 6
混合系统 ISO 7064, MOD 27,26 字母 1位字母 7
混合系统 ISO 7064, MOD 37,36 字母数字 1位数字或字母 8

表格中可见,校验字符系统,包括纯系统和混合系统。使用一个模数的称为纯系统,系统名称中MOD后第1个数字是模数,第2个数字是基数;使用两个模数的称为混合系统,系统名称中MOD后的2个数字都是模数。

纯系统

纯系统又包括使用一个校验字符和使用两个校验字符。使用一个校验字符的纯系统与使用两个校验字符的纯系统本质上是相同的,都遵守校验公式:
\sum_{i=1}^{n} ( a_{i} \times r^{i-1} ) \equiv 1 \pmod{M}

  • n - 包括校验字符在内的字符串的字符个数;
  • i - 表示从右至左包括校验码字符在内的字符的位置索引,即最右边的字符i=1
  • a_{i} - 表示第i位置上字符值;
  • r - 基数;
  • M - 模数。

只不过,使用一个校验字符的纯系统与使用两个校验字符的纯系统在计算校验字符的方式上略有不同:

  • 使用一个校验字符的纯系统的校验字符计算公式
    \begin{align*} & ( \sum_{i=2}^{n} ( a_{i} \times r^{i-1} ) + a_{1} ) \equiv 1 \pmod{M} \\ \Rightarrow & a_{1} = ( M + 1 - \sum_{i=2}^{n} ( a_{i} \times r^{i-1} ) \pmod{M} ) \pmod{M} \end{align*}
    其中a_{1}是校验字符,r^{i-1}也可以使用W_{i}替代,W_{i} = r^{i-1} \pmod{M}

  • 使用两个校验字符的纯系统的校验字符计算公式
    \begin{align*} & ( \sum_{i=3}^{n} ( a_{i} \times r^{i-1} ) + a_{2} \times r + a_{1} ) \equiv 1 \pmod{M} \\ & a_{2} \times r + a_{1} = ( M + 1 - \sum_{i=3}^{n} ( a_{i} \times r^{i-1} ) \pmod{M} ) \pmod{M} \\ \Rightarrow & a_{2} = ( ( M + 1 - \sum_{i=3}^{n} ( a_{i} \times r^{i-1} ) \pmod{M} ) \pmod{M} ) / r \\ & a_{1} = ( ( M + 1 - \sum_{i=3}^{n} ( a_{i} \times r^{i-1} ) \pmod{M} ) \pmod{M} ) \pmod{r} \end{align*}
    其中a_{2}a_{1}是校验字符,r^{i-1}也可以使用W_{i}替代,W_{i} = r^{i-1} \pmod{M}

纯系统有两种基本的计算方法,纯系统递归法和纯系统多项式法:

  • 递归法
    从左往右计算:
    ( \cdots ((a_{n} \times r + a_{n-1}) \times r + a_{n-2}) \times r + \cdots + a_{1}) \equiv 1 \pmod M

  • 多项式法
    使用\sum公式表示:
    \sum_{i=1}^{n}(a_{i} \times r^{i-1} \pmod{M}) \equiv 1 \pmod M

其实递归法的计算完全展开,得到的也就是多项式法,所以两种计算方法产生相同的结果。

混合系统

混合系统使用一个校验字符,遵守校验公式:
( \cdots ((((((M+a_{n})||_{M}\times 2)|_{(M+1)}+a_{n-1})||_{M}\times 2)|_{(M+1)}+ \cdots + a_{i})||_{M}\times 2)|_{(M+1)} \cdots +a_{1})||_{M}=1

  • n - 包括校验字符在内的字符串的字符个数;
  • i - 表示从右至左包括校验码字符在内的字符的位置索引,即最右边的字符i=1
  • a_{i} - 表示第i位置上字符值;
  • M(M+1) - 两个模数;
  • ||_{M} - 除以M后的余数,如果其值为0,则用M代替;
  • |_{(M+1)} - 除以(M+1)后的余数,在经过上述处理后,余数绝不会为0。

上述公式本身就是递归法,无法展开,因此混合系统仅支持递归法计算,不支持多项式法。

最后,附上大家喜闻乐见的代码。

package com.godson.util;

import java.math.BigInteger;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * ISO7064工具类
 * <p>
 * 使用ISO7064规范中定义的校验字符系统进行字符串的校验以及生成校验字符
 * </p>
 * 
 */
public class ISO7064Util {

    /**
     * ISO7064规范中定义的校验字符系统
     * <p>
     * <li>ISO 7064, MOD 11-2使用 {@link #ISO_7064_MOD_11_2}表示
     * </li>
     * <li>ISO 7064, MOD 37-2使用{@link #ISO_7064_MOD_37_2}表示</li>
     * <li>ISO 7064, MOD 97-10使用{@link #ISO_7064_MOD_97_10}
     * 表示</li>
     * <li>
     * ISO 7064, MOD 661-26使用 {@link #ISO_7064_MOD_661_26}表示
     * </li>
     * <li>ISO 7064, MOD 1271-36使用
     * {@link #ISO_7064_MOD_1271_36}表示</li>
     * <li>ISO 7064, MOD 11,10使用
     * {@link #ISO_7064_MOD_11_HYBRID_10}表示</li>
     * <li>ISO 7064, MOD 27,26使用
     * {@link #ISO_7064_MOD_27_HYBRID_26}表示</li>
     * <li>ISO 7064, MOD 37,36使用
     * {@link #ISO_7064_MOD_37_HYBRID_36}表示</li>
     */
    public enum Designation {
        /** ISO 7064, MOD 11-2 */
        ISO_7064_MOD_11_2,
        /** ISO 7064, MOD 37-2 */
        ISO_7064_MOD_37_2,
        /** ISO 7064, MOD 97-10 */
        ISO_7064_MOD_97_10,
        /** ISO 7064, MOD 661-26 */
        ISO_7064_MOD_661_26,
        /** ISO 7064, MOD 1271-36 */
        ISO_7064_MOD_1271_36,
        /** ISO 7064, MOD 11,10 */
        ISO_7064_MOD_11_HYBRID_10,
        /** ISO 7064, MOD 27,26 */
        ISO_7064_MOD_27_HYBRID_26,
        /** ISO 7064, MOD 37,36 */
        ISO_7064_MOD_37_HYBRID_36
    }

    /**
     * 计算校验字符
     * 
     * @param withoutCheckCharacterString 不含校验字符的字符串
     * @param designation 校验字符系统
     * @return 校验字符
     * @throws IllegalArgumentException
     *             如果字符串不匹配对应校验字符系统的正则表达式
     */
    public static String computeCheckCharacter(
            String withoutCheckCharacterString, Designation designation) {
        // 检查字符串是否匹配对应校验字符系统的正则表达式
        if (!RegexMatcher.withoutCheckCharacterStringIsMatch(
                withoutCheckCharacterString, designation)) {
            throw new IllegalArgumentException();
        }
        // 计算校验字符
        return CheckCharacterComputor.compute(withoutCheckCharacterString,
                designation);
    }

    /**
     * 校验字符串
     * 
     * @param withCheckCharacterString 含校验字符的字符串
     * @param designation 校验字符系统
     * @return true - 校验通过<br>
     *         false-校验不通过
     * @throws IllegalArgumentException
     *             如果字符串不匹配对应校验字符系统的正则表达式
     */
    public static boolean checkString(String withCheckCharacterString,
            Designation designation) {
        // 检查字符串是否匹配对应校验字符系统的正则表达式
        if (!RegexMatcher.withCheckCharacterStringIsMatch(
                withCheckCharacterString, designation)) {
            throw new IllegalArgumentException();
        }
        // 校验字符串
        return CheckCharacterSystemValidator.validate(withCheckCharacterString,
                designation);
    }

    /**
     * 正则表达式匹配器
     * <p>
     * 检查字符串是否匹配对应校验字符系统的正则表达式
     * </p>
     * <table border="1">
     * <tr>
     * <th>系统名称</th>
     * <th>适用范围</th>
     * <th>校验码数目及类型</th>
     * </tr>
     * <tr>
     * <td>ISO 7064, MOD 11-2</td>
     * <td>数字</td>
     * <td>1位数字或附加符X</td>
     * </tr>
     * <tr>
     * <td>ISO 7064, MOD 37-2</td>
     * <td>字母数字</td>
     * <td>1位数字或字母或附加符*</td>
     * </tr>
     * <tr>
     * <td>ISO 7064, MOD 97-10</td>
     * <td>数字</td>
     * <td>2位数字</td>
     * </tr>
     * <tr>
     * <td>ISO 7064, MOD 661-26</td>
     * <td>字母</td>
     * <td>2位字母</td>
     * </tr>
     * <tr>
     * <td>ISO 7064, MOD 1271-36</td>
     * <td>字母数字</td>
     * <td>2位数字或字母</td>
     * </tr>
     * <tr>
     * <td>ISO 7064, MOD 11,10</td>
     * <td>数字</td>
     * <td>1位数字</td>
     * </tr>
     * <tr>
     * <td>ISO 7064, MOD 27,26</td>
     * <td>字母</td>
     * <td>1位字母</td>
     * </tr>
     * <tr>
     * <td>ISO 7064, MOD 37,36</td>
     * <td>字母数字</td>
     * <td>1位数字或字母</td>
     * </tr>
     * </table>
     */
    private static class RegexMatcher {

        /**
         * 检查不含校验字符的字符串是否匹配对应校验字符系统的正则表达式
         * 
         * @param withoutCheckCharacterString 不含校验字符的字符串
         * @param designation 校验字符系统
         * @return true - 匹配<br>
         *         false - 不匹配
         */
        static boolean withoutCheckCharacterStringIsMatch(
                String withoutCheckCharacterString, Designation designation) {
            return regexMatch(withoutCheckCharacterString,
                    REGEX_MAPPING_WITHOUT_CHECK_CHARACTER_STRING
                            .get(designation));
        }

        /**
         * 检查有校验字符的字符串是否匹配对应校验字符系统的正则表达式
         * 
         * @param withCheckCharacterString 含校验字符的字符串
         * @param designation 校验字符系统
         * @return true - 匹配<br>
         *         false - 不匹配
         */
        static boolean withCheckCharacterStringIsMatch(
                String withCheckCharacterString, Designation designation) {
            return regexMatch(withCheckCharacterString,
                    REGEX_MAPPING_WITH_CHECK_CHARACTER_STRING.get(designation));
        }

        /** 数字正则表达式 */
        static final String REGEX_NUMBERIC_STRINGS = "^[0-9]+$";
        /** 含补充校验字符X的数字正则表达式 */
        static final String REGEX_NUMBERIC_STRINGS_WITH_SUPPLEMENTARY_CHECK_CHARACTER = "^[0-9]+[0-9X]$";
        /** 字母正则表达式 */
        static final String REGEX_ALPHABETIC_STRINGS = "^[A-Z]+$";
        /** 字母数字正则表达式 */
        static final String REGEX_ALPHANUMBERIC_STRINGS = "^[0-9A-Z]+$";
        /** 含补充校验字符*的字母数字表达式 */
        static final String REGEX_ALPHANUMBERIC_STRINGS_WITH_SUPPLEMENTARY_CHECK_CHARACTER = "^[0-9A-Z]+[0-9A-Z*]$";

        /** 校验字符系统对应的正则表达式(不含校验字符) */
        @SuppressWarnings("serial")
        static final Map<Designation, String> REGEX_MAPPING_WITHOUT_CHECK_CHARACTER_STRING = new HashMap<Designation, String>() {
            {
                put(Designation.ISO_7064_MOD_11_2, REGEX_NUMBERIC_STRINGS);
                put(Designation.ISO_7064_MOD_37_2, REGEX_ALPHANUMBERIC_STRINGS);
                put(Designation.ISO_7064_MOD_97_10, REGEX_NUMBERIC_STRINGS);
                put(Designation.ISO_7064_MOD_661_26, REGEX_ALPHABETIC_STRINGS);
                put(Designation.ISO_7064_MOD_1271_36,
                        REGEX_ALPHANUMBERIC_STRINGS);
                put(Designation.ISO_7064_MOD_11_HYBRID_10,
                        REGEX_NUMBERIC_STRINGS);
                put(Designation.ISO_7064_MOD_27_HYBRID_26,
                        REGEX_ALPHABETIC_STRINGS);
                put(Designation.ISO_7064_MOD_37_HYBRID_36,
                        REGEX_ALPHANUMBERIC_STRINGS);
            }
        };

        /** 校验字符系统对应的正则表达式(含校验字符) */
        @SuppressWarnings("serial")
        static final Map<Designation, String> REGEX_MAPPING_WITH_CHECK_CHARACTER_STRING = new HashMap<Designation, String>() {
            {
                put(Designation.ISO_7064_MOD_11_2,
                        REGEX_NUMBERIC_STRINGS_WITH_SUPPLEMENTARY_CHECK_CHARACTER);
                put(Designation.ISO_7064_MOD_37_2,
                        REGEX_ALPHANUMBERIC_STRINGS_WITH_SUPPLEMENTARY_CHECK_CHARACTER);
                put(Designation.ISO_7064_MOD_97_10, REGEX_NUMBERIC_STRINGS);
                put(Designation.ISO_7064_MOD_661_26, REGEX_ALPHABETIC_STRINGS);
                put(Designation.ISO_7064_MOD_1271_36,
                        REGEX_ALPHANUMBERIC_STRINGS);
                put(Designation.ISO_7064_MOD_11_HYBRID_10,
                        REGEX_NUMBERIC_STRINGS);
                put(Designation.ISO_7064_MOD_27_HYBRID_26,
                        REGEX_ALPHABETIC_STRINGS);
                put(Designation.ISO_7064_MOD_37_HYBRID_36,
                        REGEX_ALPHANUMBERIC_STRINGS);
            }
        };

        static boolean regexMatch(String inputString, String regex) {
            Pattern pattern = Pattern.compile(regex);
            Matcher matcher = pattern.matcher(inputString);
            return matcher.matches();
        }
    }

    /** 适用于数字的校验字符系统的数值对应表 */
    private static final String[] NUMBERIC_STRINGS = { "0", "1", "2", "3", "4",
            "5", "6", "7", "8", "9", "X" };
    /** 适用于字母的校验字符系统的数值对应表 */
    private static final String[] ALPHABETIC_STRINGS = { "A", "B", "C", "D",
            "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q",
            "R", "S", "T", "U", "V", "W", "X", "Y", "Z" };
    /** 适用于字母数字的校验字符系统的数值对应表 */
    private static final String[] ALPHANUMBERIC_STRINGS = { "0", "1", "2", "3",
            "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G",
            "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T",
            "U", "V", "W", "X", "Y", "Z", "*" };

    /**
     * 校验字符系统验证器
     */
    private static class CheckCharacterSystemValidator {
        static boolean validate(String inputString, Designation designation) {
            switch (designation) {
                case ISO_7064_MOD_11_2:
                case ISO_7064_MOD_37_2:
                case ISO_7064_MOD_97_10:
                case ISO_7064_MOD_661_26:
                case ISO_7064_MOD_1271_36:
                    return validatePureSystem(inputString, designation);
                case ISO_7064_MOD_11_HYBRID_10:
                case ISO_7064_MOD_27_HYBRID_26:
                case ISO_7064_MOD_37_HYBRID_36:
                    return validateHybridSystem(inputString, designation);
                default:
                    return false;
            }
        }

        /**
         * 纯系统校验
         */
        static boolean validatePureSystem(String inputString,
                Designation designation) {
            int M = 0; // 模数
            int r = 0; // 基数
            List<String> mapping = null;
            switch (designation) {
                case ISO_7064_MOD_11_2:
                    M = 11;
                    r = 2;
                    mapping = Arrays.asList(NUMBERIC_STRINGS);
                    break;
                case ISO_7064_MOD_37_2:
                    M = 37;
                    r = 2;
                    mapping = Arrays.asList(ALPHANUMBERIC_STRINGS);
                    break;
                case ISO_7064_MOD_97_10:
                    M = 97;
                    r = 10;
                    mapping = Arrays.asList(NUMBERIC_STRINGS);
                    break;
                case ISO_7064_MOD_661_26:
                    M = 661;
                    r = 26;
                    mapping = Arrays.asList(ALPHABETIC_STRINGS);
                    break;
                case ISO_7064_MOD_1271_36:
                    M = 1271;
                    r = 36;
                    mapping = Arrays.asList(ALPHANUMBERIC_STRINGS);
                    break;
                default:
                    return false;
            }
            char[] strArray = inputString.toCharArray();
            int S = 0;
            int n = strArray.length;
            for (int i = 1; i <= n; i++) {
                // 注意这里不要使用Math的pow方法
                S += mapping.indexOf(String.valueOf(strArray[i - 1]))
                        * BigInteger.valueOf(r).pow(n - i)
                                .mod(BigInteger.valueOf(M)).intValue();
            }
            return S % M == 1;
        }

        /**
         * 混合系统校验
         */
        static boolean validateHybridSystem(String inputString,
                Designation designation) {
            int M = 0; // 模数1
            List<String> mapping = null;

            switch (designation) {
                case ISO_7064_MOD_11_HYBRID_10:
                    M = 10;
                    mapping = Arrays.asList(NUMBERIC_STRINGS);
                    break;
                case ISO_7064_MOD_27_HYBRID_26:
                    M = 26;
                    mapping = Arrays.asList(ALPHABETIC_STRINGS);
                    break;
                case ISO_7064_MOD_37_HYBRID_36:
                    M = 36;
                    mapping = Arrays.asList(ALPHANUMBERIC_STRINGS);
                    break;
                default:
                    return false;
            }
            int Mplus1 = M + 1; // 模数2
            char[] strArray = inputString.toCharArray();
            int S = M + mapping.indexOf(String.valueOf(strArray[0]));
            int P = 0;
            for (int i = 1; i < strArray.length; i++) {
                P = ((S % M == 0 ? M : S % M) * 2) % Mplus1;
                S = P + mapping.indexOf(String.valueOf(strArray[i]));
            }
            return S % M == 1;
        }
    }

    /**
     * 校验字符生成器
     */
    private static class CheckCharacterComputor {
        static String compute(String inputString, Designation designation) {
            switch (designation) {
                case ISO_7064_MOD_11_2:
                case ISO_7064_MOD_37_2:
                    return polynomialMethod4PureSystemWith1CheckChar(
                            inputString, designation);
                case ISO_7064_MOD_97_10:
                case ISO_7064_MOD_661_26:
                case ISO_7064_MOD_1271_36:
                    return polynomialMethod4PureSystemWith2CheckChar(
                            inputString, designation);
                case ISO_7064_MOD_11_HYBRID_10:
                case ISO_7064_MOD_27_HYBRID_26:
                case ISO_7064_MOD_37_HYBRID_36:
                    return recursiveMethod4HybridSystemWith1CheckChar(
                            inputString, designation);
                default:
                    return null;
            }
        }

        /**
         * 通过多项式法计算纯系统一位校验字符
         */
        static String polynomialMethod4PureSystemWith1CheckChar(String str,
                Designation designation) {
            int M = 0; // 模数
            int r = 0; // 基数
            List<String> mapping = null;
            switch (designation) {
                case ISO_7064_MOD_11_2:
                    M = 11;
                    r = 2;
                    mapping = Arrays.asList(NUMBERIC_STRINGS);
                    break;
                case ISO_7064_MOD_37_2:
                    M = 37;
                    r = 2;
                    mapping = Arrays.asList(ALPHANUMBERIC_STRINGS);
                    break;
                default:
                    break;
            }
            char[] strArray = str.toCharArray();
            int S = 0;
            int n = strArray.length + 1;
            for (int i = n; i >= 2; i--) {
                // 注意这里不要使用Math的pow方法
                S += mapping.indexOf(String.valueOf(strArray[n - i]))
                        * BigInteger.valueOf(r).pow(i - 1)
                                .mod(BigInteger.valueOf(M)).intValue();
            }
            return mapping.get((M + 1 - S % M) % M);
        }

        /**
         * 通过多项式法计算纯系统二位校验字符
         */
        static String polynomialMethod4PureSystemWith2CheckChar(String str,
                Designation designation) {
            int M = 0; // 模数
            int r = 0; // 基数
            List<String> mapping = null;
            switch (designation) {
                case ISO_7064_MOD_97_10:
                    M = 97;
                    r = 10;
                    mapping = Arrays.asList(NUMBERIC_STRINGS);
                    break;
                case ISO_7064_MOD_661_26:
                    M = 661;
                    r = 26;
                    mapping = Arrays.asList(ALPHABETIC_STRINGS);
                    break;
                case ISO_7064_MOD_1271_36:
                    M = 1271;
                    r = 36;
                    mapping = Arrays.asList(ALPHANUMBERIC_STRINGS);
                    break;
                default:
                    break;
            }
            char[] strArray = str.toCharArray();
            int S = 0;
            int n = strArray.length + 2;
            for (int i = n; i >= 3; i--) {
                // 注意这里不要使用Math的pow方法
                S += mapping.indexOf(String.valueOf(strArray[n - i]))
                        * BigInteger.valueOf(r).pow(i - 1)
                                .mod(BigInteger.valueOf(M)).intValue();
            }
            return mapping.get(((M + 1 - S % M) % M) / r)
                    + mapping.get(((M + 1 - S % M) % M) % r);
        }

        /**
         * 通过递归法法计算混合系统一位校验字符
         */
        static String recursiveMethod4HybridSystemWith1CheckChar(
                String inputString, Designation designation) {
            int M = 0; // 模数1
            List<String> mapping = null;
            switch (designation) {
                case ISO_7064_MOD_11_HYBRID_10:
                    M = 10;
                    mapping = Arrays.asList(NUMBERIC_STRINGS);
                    break;
                case ISO_7064_MOD_27_HYBRID_26:
                    M = 26;
                    mapping = Arrays.asList(ALPHABETIC_STRINGS);
                    break;
                case ISO_7064_MOD_37_HYBRID_36:
                    M = 36;
                    mapping = Arrays.asList(ALPHANUMBERIC_STRINGS);
                    break;
                default:
                    break;
            }
            int Mplus1 = M + 1; // 模数2
            char[] strArray = inputString.toCharArray();
            int S = 0;
            int P = M;
            int n = strArray.length + 1;
            for (int i = n; i >= 2; i--) {
                S = P + mapping.indexOf(String.valueOf(strArray[n - i]));
                P = ((S % M == 0 ? M : S % M) * 2) % Mplus1;
            }
            return mapping.get((M + 1 - P % M) % M);
        }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,302评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,563评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,433评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,628评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,467评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,354评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,777评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,419评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,725评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,768评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,543评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,387评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,794评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,032评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,305评论 1 252
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,741评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,946评论 2 336

推荐阅读更多精彩内容

  • 申明:本文除特别说明外,身份证号码专指18位公民身份号码 一、身份证号码结构 早期‘身份证号码’叫‘社会保障号’,...
    安东的漫长岁月阅读 21,436评论 0 4
  • 简言 在做用户实名验证时,常会用到身份证号码的正则表达式及校验方案。本文列举了两种验证方案,大家可以根据自己的项目...
    毛三十阅读 953评论 0 5
  • 在进行互联网产品设计时,尤其是互联网金融产品,实名认证是很重要的环节。今天和大家聊聊一个很容易被忽视的话题:身份证...
    威理阅读 1,291评论 4 7
  • 时间最残忍。往昔尚如昨,转眼间我们已经毕业分别十年。十年前,我们不懂离别的哀愁,不懂前途的凶险,错以为,时光永远不...
    黄淮雄风阅读 392评论 2 2
  • 文 / 潇 萱 晴朗湛蓝的天空 情人节 涌出无数的温情 醇香的芒果千层 甜蜜着节日的气氛 迷人的蓝色妖姬 成为第二...
    潇萱阅读 233评论 0 1