以前浏览网站时,偶尔会发现有些网站注册时需要填写身份证,而且对身份证检验相当严格,猜想必定存在某种算法。今天恰好有空,上网找了一下,有些收获,不敢独享,现与众网友共享。本人已将算法用C#实现,希望大家能多多指点。
1. 中国公民身份证常识:
我国现行使用公民身份证号码有两种尊循两个国家标准,〖GB 11643-1989〗和〖GB 11643-1999〗。
〖GB 11643-1989〗中规定的是15位身份证号码:排列顺序从左至右依次为:六位数字地址码,六位数字出生日期码,三位数字顺序码,其中出生日期码不包含世纪数。
6位行政区划分代码 |
6位出生日期 |
3位顺序码 |
|
〖GB 11643-1999〗中规定的是18位身份证号码:公民身份号码是特征组合码,由十七位数字本体码和一位数字校验码组成。排列顺序从左至右依次为:六位数字地址码,八位数字出生日期码,三位数字顺序码和一位数字校验码。
6位行政区划分代码 |
6位出生日期 |
3位顺序码 |
1位校验码 |
行政区划分代码:表示编码对象常住户口所在县(市、旗、区)的行政区划代码。
出生日期码:表示编码对象出生的年、月、日,其中年份用四位数字表示,年、月、日之间不用分隔符。
顺序码:表示同一地址码所标识的区域范围内,对同年、同月、同日出生的人员编定的顺序号。顺序码的奇数分给男性,偶数分给女性。
校验码:是根据前面十七位数字码,按照ISO 7064:1983.MOD 11-2校验码计算出来的检验码。
2. 算法
关于身份证号码最后一位的校验码的算法如下:
假设最后一位的校验码为R
则C=∑(a[i]*w[i])%11 (i=2,3,…,18)
其中∑:表示求和
i:表示身份证号码每一位的序号,从右至左,最左侧为18,最右侧位1。
*:表示乘号
a[i]:表示身份证号码第i位上的号码
w[i]:表示第i位上的权值,计算方法:w[i]=2^(i-1)%11
%:表示求模运算
^:表示求幂运算
经过上述方法得到的C的范围在0~10之间,它与身份证最后一位校验位的对应规则为:
C |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
R |
1 |
0 |
X |
9 |
8 |
7 |
6 |
5 |
4 |
3 |
2 |
由此看出 X 就是 10,罗马数字中的 10 就是X,所以在新标准的身份证号码中可能含有非数字的字母X。
3. C#实现
using System.Collections.Generic;
using System.Text;
namespace Zwf.IdCard
{
public class Check
{
//位权值数组
private static byte[] Weight = new byte[17];
//身份证行政区划代码部分长度
private static byte fPart = 6;
//算法求模关键参数
private static byte fMode = 11;
//旧身份证长度
private static byte oIdLen = 15;
//新身份证长度
private static byte nIdLen = 18;
//新身份证年份标记值
private static string yearFlag = "19";
//校验字符串
private static string checkCode = "10X98765432";
//最小行政区划分代码
private static int minCode = 110000;
//最大行政区划分代码
private static int maxCode = 820000;
private static Random rand = new Random();
/// <summary>
/// 计算位权值数组
/// </summary>
private static void SetWBuffer()
{
for (int i = 0; i < Weight.Length; i++)
{
int k = (int)Math.Pow(2, (Weight.Length - i));
Weight[i] = (byte)(k % fMode);
}
}
/// <summary>
/// 获取新身份证最后一位校验位
/// </summary>
/// <param name="idCard">身份证号码</param>
/// <returns></returns>
private static string GetCheckCode(string idCard)
{
int sum = 0;
///进行加权求和计算
for (int i = 0; i < Weight.Length; i++)
{
sum += int.Parse(idCard.Substring(i, 1)) * Weight[i];
}
///求模运算得到模值
byte mode = (byte)(sum % fMode);
return checkCode.Substring(mode, 1);
}
/// <summary>
/// 检查身份证长度是否合法
/// </summary>
/// <param name="idCard">身份证号码</param>
/// <returns></returns>
private static bool CheckLen(string idCard)
{
if (idCard.Length == oIdLen || idCard.Length == nIdLen)
{
return true;
}
return false;
}
/// <summary>
/// 验证是否是新身份证
/// </summary>
/// <param name="idCard">身份证号码</param>
/// <returns></returns>
private static bool IsNew(string idCard)
{
if (idCard.Length == nIdLen)
{
return true;
}
return false;
}
/// <summary>
/// 获取时间串
/// </summary>
/// <param name="idCard">身份证号码</param>
/// <returns></returns>
private static string GetDate(string idCard)
{
string str = "";
if (IsNew(idCard))
{
str = idCard.Substring(fPart, 8);
}
else
{
str = yearFlag + idCard.Substring(fPart, 6);
}
return str;
}
/// <summary>
/// 检查时间是否合法
/// </summary>
/// <param name="idCard"></param>
/// <returns></returns>
private static bool CheckDate(string idCard)
{
//日期是否符合格式
bool flag = false;
string strDate = GetDate(idCard);
int year = Convert.ToInt32(strDate.Substring(0, 4));
int month = Convert.ToInt32(strDate.Substring(4, 2));
int day = Convert.ToInt32(strDate.Substring(6, 2));
///年份是否合法,本例暂定年份在1900-1999之间为合法年份
if ((year > 1900) && (year < 2999))
{
flag = true;
}
else
{
flag = false;
}
///检查月份是否合法
if ((month >= 1) && (month <= 12))
{
flag = true;
}
else
{
flag = false;
}
///检查天是否合法,本例以农历为准
if ((day >= 1) && (day <= 30))
{
flag = true;
}
else
{
flag = false;
}
return flag;
}
/// <summary>
/// 根据年份和月份检查天是否合法
/// </summary>
/// <param name="year">年份</param>
/// <param name="month">月份</param>
/// <param name="day">天</param>
/// <returns></returns>
private static bool CheckDay(int year, int month, int day)
{
///是否是闰年
bool rYearFlag = false;
///天是否合法
bool rDayFlag = false;
if (((year % 4 == 0) && (year % 3200 != 0)) || (year % 400 == 0))
{
rYearFlag = true;
}
#region 检查天是否合法
if (month == 2)
{
if (rYearFlag)
{
if (day > 0 && day <= 29)
{
rDayFlag = true;
}
}
else
{
if (day > 0 && day <= 28)
{
rDayFlag = true;
}
}
}
else if (month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12)
{
if (day > 0 && day <= 31)
{
rDayFlag = true;
}
}
else
{
if (day > 0 && day <= 30)
{
rDayFlag = true;
}
}
#endregion
return rDayFlag;
}
/// <summary>
/// 检查身份证是否合法
/// </summary>
/// <param name="idCard"></param>
/// <returns></returns>
public static bool CheckCard(string idCard, out string msg)
{
///身份证是否合法标志
bool flag = false;
msg = string.Empty;
SetWBuffer();
if (!CheckLen(idCard))
{
msg = "身份证长度不符合要求";
flag = false;
}
else
{
if (!CheckDate(idCard))
{
msg = "身份证日期不符合要求";
flag = false;
}
else
{
if (!IsNew(idCard))
{
idCard = GetNewIdCard(idCard);
}
string checkCode = GetCheckCode(idCard);
string lastCode = idCard.Substring(idCard.Length - 1, 1);
if (checkCode == lastCode)
{
flag = true;
}
else
{
msg = "身份证验证错误";
flag = false;
}
}
}
return flag;
}
/// <summary>
/// 旧身份证号码转换成新身份证号码
/// </summary>
/// <param name="oldIdCard">旧身份证号码</param>
/// <returns>新身份证号码</returns>
private static string GetNewIdCard(string oldIdCard)
{
if (oldIdCard.Length == 15)
{
string newIdCard = oldIdCard.Substring(0, fPart);
newIdCard += yearFlag;
newIdCard += oldIdCard.Substring(fPart, 9);
newIdCard += GetCheckCode(newIdCard);
return newIdCard;
}
return string.Empty;
}
/// <summary>
/// 新身份证号码转换成旧身份证号码
/// </summary>
/// <param name="newIdCard">新身份证号码</param>
/// <returns>旧身份证号码</returns>
private static string GetOldIdCard(string newIdCard)
{
if (newIdCard.Length == 18)
{
string oldIdCard = newIdCard.Substring(0, fPart);
oldIdCard += newIdCard.Substring(8, 9);
return oldIdCard;
}
return string.Empty;
}
}
}
我们可以直接执行方法Zwf.IdCard.Check.CheckCard(身份证号码)进行验证身份证是否正确
该方法返回true表示身份证号码合法,false表示身份证号码不合法。
写得比较粗糙,还望各位高人多指点。
4. 参考资料
"将15位身份证转换为18位身份证":http://www.joyblog.cn/article.asp?id=105