1、PIN

PIN(Personal identification Number),即个人密码,是在联机交易中识别持卡人身份合法性的数据信息,在计算机和网络系统中任何环节不允许PIN以明文的格式出现

PIN规定为4~12位数字,越长越安全,但是从易用的角度考虑不应该超过6位,所以目前的银行卡密码都是6位。但是6位不是唯一的,银行卡密码完全可以支持更少或更多位数。

关于PIN的长度,Wiki上给我们讲了一个小故事:ATM的发明者John Shepherd-Barron最初设想是使用6位长度的密码,但是他的妻子只能记住4位数字,所以国外很多地方是使用4位长度PIN的。毛主席教育我们:妇女能顶半边天。女人是伟大的,女人总在不经意间改变历史。

要对pin进行加密时,由于pin长度一般为6位,不符合加密算法的固定分组长度要求,所以需要对pin进行填充使之达到一个分组的长度,然后加密计算从而保证pin的机密性,填充后的PIN的分组数据块称之为PINBLOCK

PINBLOCK一般用于银行卡取款密码的加密传输处理过程中,遵循ANSI X.98规范,该规范被目前主流的卡组织所接受,包括中国银联。

2、PAN

PAN  :主账户号码 (Primary Account Number的缩写),ISO 9564中定义:发行者分配的号码,由发行者识别号码、个人账户标识和ISO/IEC 7812-1中规定的校验数字组成,用于识别发卡机构和持卡人。详见银行卡号的编码规则及校验

3、PIN block

PIN 格式块。

ISO 9564标准中定义了5种格式,Format 0,Format 1, Format 2, Format 3,Format 4。

3.1、Format 0 PIN block

由PIN域 和 PAN域 异或 得到。

  • PIN域 共64bit,每4bit为1位十六进制数字,共16位十六进制数字。
  • 第1位(1~4bit):固定值0x0(0000)
  • 第2位(5~8bit):PIN长度,取值范围0x4(0100) ~ 0xC(1100)
  • 第3~16位(9~64bit):PIN,不足14位右补0xF(1111),因为PIN最多12位,所以最后2位一定是0xFF(1111,1111)

 

  • PAN域 共64bit,每4bit为1位十六进制数字,共16位十六进制数字。
  • 第1~4位(1~16bit):固定值0x0000(0000,0000,0000,0000)
  • 第5~16位(17~64bit):PAN去掉最右边1位校验数字后,从右边数12位,不足12位左补0x0(0000)

以银行卡号6225760008219524,密码123456为例:

代码示例:

/**
 * Format 0 PIN block
 * 
 * @param PIN 密码
 * @param PAN 账号
 * @return Format 0 PIN block 十六进制字符串
 */
private static String format0(String PIN, String PAN) {
    // PIN域,64bit,16位十六进制数字
    // 固定值0x0 + PIN长度 + PIN(不足14位右补F)
    String PINField = "0"
            + Integer.toHexString(PIN.length())
            + String.format("%-14s", PIN).replace(' ', 'F');
    // PAN域,64bit,16位十六进制数字
    // 固定值0x0000 + PAN,去掉校验数字,从右边数12位,不足12位左补0x0
    String PANWithoutCheckDigit = PAN.substring(0, PAN.length() - 1);
    String PANField = "0000"
            + (PANWithoutCheckDigit.length() > 12 ? PANWithoutCheckDigit
                    .substring(PANWithoutCheckDigit.length() - 12,
                            PANWithoutCheckDigit.length()) : String.format(
                    "%12s", PANWithoutCheckDigit).replace(' ', '0'));
    // 十六进制转byte数组
    byte[] PINFieldByteArray = hexString2ByteArr(PINField);
    // 十六进制转byte数组
    byte[] PANFieldByteArray = hexString2ByteArr(PANField);
    // 异或
    byte[] PINBlockByteArray = new byte[8];
    for (int i = 0; i < 8; i++) {
        PINBlockByteArray[i] = (byte) (PINFieldByteArray[i] ^ PANFieldByteArray[i]);
    }
    // 返回十六进制
    return byteArr2HexString(PINBlockByteArray).toUpperCase();
}

3.2、Format 1 PIN block

用于PAN不可获取的场景,由PIN域 拼接 交易域得到。

交易域:在每次PIN block时应当是唯一的,其来源于交易序列号、时间戳、随机数或其他相似情况,取值范围是0x0(0000) ~ 0xF(1111)。

PIN block上送时,交易域不必上送,因为PIN的长度已经知道了。

  • PIN block 共64bit,每4bit为1位十六进制数字,共16位十六进制数字。
  • 第1位(1~4bit):固定值0x1(0001)
  • 第2位(5~8bit):PIN长度,取值范围0x4(0100) ~ 0xC(1100)
  • 第3~16位(9~64bit):PIN,不足14位右补交易域。

以密码123456为例,交易域使用随机数:

代码示例:

/**
 * Format 1 PIN block
 * 
 * @param PIN 密码
 * @return Format 1 PIN block 十六进制字符串
 */
private static String format1(String PIN) {
    // PIN block,64bit,16位十六进制数字
    // 固定值0x1 + PIN长度 + PIN,不足14位右补交易域
    String PINBlock = "1" + Integer.toHexString(PIN.length()) + PIN;
    // 交易域使用随机数,取值范围是0x0~0xF
    Random r = new Random();
    for (int i = 0; i < 14 - PIN.length(); i++) {
        PINBlock += Integer.toHexString(r.nextInt(16));
    }
    return PINBlock.toUpperCase();
}

3.3、Format 2 PIN block

指定IC卡使用,只能用于离线环境,不能用于线上PIN验证。

  • PIN域 共64bit,每4bit为1位十六进制数字,共16位十六进制数字。
  • 第1位(1~4bit):固定值0x2(0010)
  • 第2位(5~8bit):PIN长度,取值范围0x4(0100) ~ 0xC(1100)
  • 第3~16位(9~64bit):PIN,不足14位右补0xF(1111),因为PIN最多12位,所以最后2位一定是0xFF(1111,1111)

以密码123456为例:

preview

代码示例:

/**
 * Format 2 PIN block
 * 
 * @param PIN 密码
 * @return Format 2 PIN block 十六进制字符串
 */
private static String format2(String PIN) {
    // PIN block,64bit,16位十六进制数字
    // 固定值0x2 + PIN长度 + PIN(不足14位右补F)
    return "2"
            + Integer.toHexString(PIN.length())
            + String.format("%-14s", PIN).replace(' ', 'F');
}

3.3、Format 3 PIN block

PIN域 PAN域 异或 得到。

  • PIN域 共64bit,每4bit为1位十六进制数字,共16位十六进制数字。
  • 第1位(1~4bit):固定值0x3(0011)
  • 第2位(5~8bit):PIN长度,取值范围0x4(0100) ~ 0xC(1100)
  • 第3~16位(9~64bit):PIN,不足14位右补0xA(1010) ~ 0xF(1111)中随机的或顺序的数字

 

  • PAN域 共64bit,每4bit为1位十六进制数字,共16位十六进制数字。
  • 第1~4位(1~16bit):固定值0x0000(0000,0000,0000,0000)
  • 第5~16位(17~64bit):PAN,去掉最右边1位校验数字后,从右边数12位,不足12位左补0x0(0000)

以银行卡号6225760008219524,密码123456为例:

代码示例:

/**
 * Format 3 PIN block
 * 
 * @param PIN 密码
 * @param PAN 账号
 * @return Format 3 PIN block 十六进制字符串
 */
private static String format3(String PIN, String PAN) {
    // PIN域,64bit,16位十六进制数字
    // 固定值0x3 + PIN长度 + PIN(不足14位右补随机数)
    String PINField = "3" + Integer.toHexString(PIN.length()) + PIN;
    // 随机数取值范围0xA~0xF
    Random r = new Random();
    for (int i = 0; i < 14 - PIN.length(); i++) {
        PINField += Integer.toHexString(r.nextInt(6) + 10);
    }
    // PAN域,64bit,16位十六进制数字
    // 固定值0x0000 + PAN,去掉校验数字,从右边数12位,不足12位左补0x0
    String PANWithoutCheckDigit = PAN.substring(0, PAN.length() - 1);
    String PANField = "0000"
            + (PANWithoutCheckDigit.length() > 12 ? PANWithoutCheckDigit
                    .substring(PANWithoutCheckDigit.length() - 12,
                            PANWithoutCheckDigit.length()) : String.format(
                    "%12s", PANWithoutCheckDigit).replace(' ', '0'));
    // 十六进制转byte数组
    byte[] PINFieldByteArray = hexString2ByteArr(PINField);
    // 十六进制转byte数组
    byte[] PANFieldByteArray = hexString2ByteArr(PANField);
    // 异或
    byte[] PINBlockByteArray = new byte[8];
    for (int i = 0; i < 8; i++) {
        PINBlockByteArray[i] = (byte) (PINFieldByteArray[i] ^ PANFieldByteArray[i]);
    }
    // 返回十六进制
    return byteArr2HexString(PINBlockByteArray).toUpperCase();
}

3.4、Format 4 PIN block

  • PIN域 128bit,每4bit为1位十六进制数字,共32位十六进制数字。
  • 第1位(1~4bit):固定值0x4(0100)
  • 第2位(5~8bit):PIN长度,取值范围0x4(0100) ~ 0xC(1100)
  • 第3~16位(9~64bit):PIN,不足14位右补0xA(1010)
  • 第17~32位(65~128bit):随机数字,取值范围0x0(0000) ~ 0xF(1111)

 

  • PAN域 共128bit,每4bit为1位十六进制数字,共32位十六进制数字。
  • 第1位(1~4bit):PAN长度减12,因为PAN最大19位,所以取值范围0x0(0000) ~ 0x7(0111)PAN长度小于12时,取值0x0(0000)
  • 第2~32位(5~128bit):PAN,不足12位左补0x0(0000),超过12位不足20位右补0x0(0000)

以银行卡号6225760008219524,密码123456为例:

Format 4 与0/1/2/3不同,不是先得到PIN block再加密,而是将 PIN域PAN域放到加密过程中,最终得到加密的PIN block。

加密过程:

  • 使用密钥K对PIN域加密,得到结果A;
  • 结果A与PAN域做异或,得到结果B;
  • 使用密钥K对结果B加密,得到加密的PIN block。
  • 解密过程(加密过程的逆过程)
  • 使用密钥K对加密的PIN block解密,得到结果B;
  • 结果B与PAN域做异或,得到结果A;
  • 使用密钥K对结果A解密,得到PIN域。

因Format 4 PIN block涉及密钥加解密过程,此处不提供代码示例,PIN域、PAN域、异或,可参照其他格式PIN block代码示例。

4、ANSI X9.8 Format

ANSI - American National Standards Institute,美国国家标准学会

ANS - American National Standards,美国国家标准

ANS X9.8是在ISO 9564-1标准的基础上增加几处笔记形成的,内容可以认为是一致的。

ANS X9.8中指出PIN block只有Format 0和3建议在此标准下使用,Format 3应当在多次PIN加密都使用相同的PIN加密密钥时使用。

ANSI X9.8 个人密码加密标准。

ANSI X3.92 数据加密标准 (DES)

ANSI X9.9 金融机构信息验证标准。

5、Q/CUP 006.4

中国银联股份有限公司企业标准,Q表示企业,CUP表示中国银联(China UnionPay)

Q/CUP 006.4中规定了PIN block使用ANSI X9.8 Format(带主账号信息),通过标准比对,可以确定就是ANS X9.8中的Format 0,也就是ISO 9564-1中的Format 0。

但是,笔者未查找到ANSI X9.8 Format(不带主账号信息)的定义。不过,从2016年4月生效版的《中国银联银行卡交换系统技术规范》中,可以确定ANSI X9.8 Format(不带主账号信息)的PIN block的生成方式与ANSI X9.8 Format(带主账号信息)中PIN域的生成方式一致,因此,笔者有理由猜测银联标准定义的ANSI X9.8 Format(不带主账号信息)就是ANSI X9.8 Format(带主账号信息)的简化,但是无法猜测其应用场景,对此有了解的朋友可留言告知。

同时,银联标准也定义了互联网支付密码的PIN block计算方式。 1. 互联网支付密码的长度必须在6到20个字符以内; 2. 互联网支付密码均为ASCII码字符,既可以为字符,也可以为数字,或其他符号; 3. PIN block 共24个字节,每个字节使用2位十六进制数字表示,共48位十六进制数字; 前2个字节,互联网支付密码的长度; 剩余22个字节,PIN,6~20位字符,每个字符占1个字节,不足右补0xFF。

以互联网支付密码Hello!123为例:

PIN长度为9,2个字节表示是09,ASCII码是48和57,转为十六进制是0x30和0x39,因此可以得到PIN block为:303948656C6C6F21313233FFFFFFFFFFFFFFFFFFFFFFFFFF

代码示例:

/**
 * 银联定义互联网支付密码PIN block
 * 
 * @param PIN 密码
 * @return PIN block 十六进制字符串
 */
private static String formatQCUP(String PIN) {
    StringBuilder PINBlock = new StringBuilder();
    // 支付密码长度
    String PINLength = String.format("%2s", Integer.toHexString(PIN.length()))
            .replace(' ', '0');
    char[] PINLengthArray = PINLength.toCharArray();
    for (int i = 0; i < PINLengthArray.length; i++) {
        PINBlock.append(Integer.toHexString((int) PINLengthArray[i]));
    }
    // 密码
    char[] PINArray = PIN.toCharArray();
    for (int i = 0; i < PINArray.length; i++) {
        PINBlock.append(Integer.toHexString((int) PINArray[i]));
    }
    // 补0xFF
    for (int i = 0; i < 22 - PINArray.length; i++) {
        PINBlock.append("FF");
    }
    return PINBlock.toString().toUpperCase();
}

6、不安全的PIN

ISO 9564-1标准建议客户在设置或更换密码时遵循以下原则:

  1. 不包括客户信息,比如:姓氏、手机号码、生日等;
  2. 不包括账号中连续的数字;
  3. 不包括三个及以上的相同数字;
  4. 不包括三个及以上递增或递减的连续数字;
  5. 不包括历史上重要的日期。

除了这些,笔者从自身项目经验中总结出以下几点供大家参考(主要针对6位数字的密码):

  1. 两个及以上的相同数字;
  2. AABBCC格式,比如:112233
  3. AAABBB格式,比如:111222
  4. AAAABB格式,比如:111122
  5. AAAAAB格式,比如:111112
  6. AAAAAA格式,比如:111111
  7. 两个及以上递增或递减的连续数字;
  8. ABABAB格式,比如:121212、212121
  9. ABCABC格式,比如:123123、321321
  10. ABCDAB格式,比如:123412、432121
  11. ABCDEA格式,比如:123451、543211
  12. ABCDEF格式,比如:123456、654321
  13. 账号中任意连续的6个数字;
  14. 身份证号中任意连续的6个数字;
  15. 手机号中任意连续的6个数字;
  16. 不与之前设置密码相同;
  17. 建立弱密码表,位于弱密码表中的都不安全。

互联网支付密码一般会建议:包含大写字母、小写字母、数字、特殊符号中三种或三种以上的不小于8位的支付密码。

7、彩虹表

简单来说,彩虹表就是密码的合集,你的密码在人家的彩虹表里,就容易被破解;彩虹表很大,大到你的密码很容易出现在其中,但是彩虹表又很小,掌握了彩虹表的原理,你的密码就可以消失的无影无踪。

彩虹表如果展开内容较多,后续会开辟新篇专题交流。本篇主要从以下几点来探讨彩虹表破解的防范措施

存储密码明文是大忌

前几年,存储密码明文的事件被多次曝光,不断刷新着IT从业人员的安全意识下限,好在经过多年的网络安全普及与国家对相关行业软件系统的严格监管,至少笔者所在的金融软件行业领域,存储密码明文的系统已经被淘汰在历史的洪流中了。

公开的哈希算法不安全

彩虹表正是针对公开的哈希算法的破解之法,尽管哈希算法是不可逆的,但是破解它其实更加简单,随着科技的发展,穷举的速度越来越快,有了彩虹表的加持,更加势如破竹。

哈希算法,越来越沦为完整性校验的工具。

为哈希算法加盐是个好主意

没错,我为自己带盐。给密码加盐后再进行哈希,即使被破解,也是加盐的密码,原始密码也是相对安全的。PIN block也可以算作一种加盐方法,只不过PIN block的盐包含在账号中,在不知道账号的前提下,得到了PIN block确实很难破解出密码。

足够聪明可以自定义加密算法

使用自定义的加密算法,彩虹表就失去了用武之地,在加密算法和密钥不泄露的前提下,破解基本是不可能的。但是,世界上聪明人那么多,切忌自以为是,须知人外有人,要时刻保持警惕。

谨防数据库泄露

如果数据库中密码数据泄露,聪明的黑客总能探索到破解之法,因此对数据库要做足够的安全防护。

如果可能

  1. 密码加密算法不可逆,算法不公开,限定于仅核心开发人员(A)知晓;
  2. 数据库加密防护,脱离数据库环境后无法打开,打开方式限定于仅核心运维人员(B)知晓;
  3. 网络防护,限定IP访问,使用堡垒机等手段,访问权限限定于仅核心运维人员(C)拥有;
  4. 以上,凑齐ABC才可能发生数据库泄露,此时发生撞库的概率就变的极低。

当然,如果代码中存在某个bug,也可能被高明的黑客利用,所谓道高一尺,魔高一丈,信息安全的攻防战没有绝对的赢家。

其他建议

  1. 使用密码控件,研发较困难,采购最直接;
  2. 不常登录地区/终端,进行短信验证码校验;
  3. 限制短信验证码失效时间;
  4. 限制短信验证码发送频率;
  5. 密码输入错误2次,进行图形验证码校验;
  6. 密码输入错误5次,锁定账号,并发短信通知客户。

 

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐