PIN block演算法
前言
金融加密演算法專輯寫到現在,已經是第3篇了,前2篇 ofollow,noindex">身份證號碼的編碼規則及校驗 、 銀行卡號的編碼規則及校驗 ,大家可以看到筆者喜歡“引經據典”,“經典”主要包括國際標準和國家標準,很多國際標準都有對應的國家標準,可以中英文對照互相印證進行學習,對於沒有國家標準對應的國際標準,只能靠著筆者拙劣的英文水平勉強學習,如有錯誤請不吝指教。其實,文中提及的“經典”也只是大家工作學習中的一小部分,筆者查閱到的也未必是最新修訂的版本,不過是想要告訴大家,有理有據才值得信服,源起於理論,行動於程式碼,知其然並知其所以然,是程式員應具備的素養之一。
筆者目前使用的jdk版本是1.6.0_29,Eclipse版本是Juno Release,Build id 20120614-1722。如無特殊說明,本文所有的Java程式碼都是基於此。
修訂記錄
版本號 | 修訂日期 | 修訂說明 |
---|---|---|
V0.1 | 2018/09/07 | 初稿 |
V1.0 | 2018/09/20 | 釋出 |
參考資料
- 維基百科 https://en.wikipedia.org/wiki/Personal_identification_number
- 維基百科 https://en.wikipedia.org/wiki/Rainbow_table
- ISO 9564-1-2017 Financial services - Personal Identification Number (PIN) management and security - Part 1: Basic principles and requirements for PINs in card-based systems
- ANS X9.8-2003 BANKING - PERSONAL IDENTIFICATION NUMBER MANAGEMENT AND SECURITY - Part 1: PIN protection principles and techniques for online PIN verification in ATM & POS systems
- Q/CUP 006.4-2015 中國銀聯股份有限公司企業標準 中國銀聯銀行卡交換系統技術規範(國際卷) 第4部分 資料安全傳輸控制規範
- 中國銀聯銀行卡交換系統技術規範 2016年4月生效版本升級公告及實施指南
PIN
Personal Identification Number的縮寫,ISO 9564中定義:string of numeric digits established as a shared secret between the cardholder and the issuer, for subsequent use to validate authorized card usage。
個人識別號碼,作為持卡人和發行者之間的共享祕密而建立的數字字串,用於隨後驗證授權卡的使用。從定義理解,專指 持卡人
的密碼,至於其他的密碼,比如你的郵箱密碼,嚴格來說並不能稱之為PIN。
PIN規定為4~12位數字,越長越安全,但是從易用的角度考慮不應該超過6位,所以目前的銀行卡密碼都是6位。但是6位不是唯一的,銀行卡密碼完全可以支援更少或更多位數。
關於PIN的長度,Wiki上給我們講了一個小故事:ATM的發明者John Shepherd-Barron最初設想是使用6位長度的密碼,但是他的妻子只能記住4位數字,所以國外很多地方是使用4位長度PIN的。毛主席教育我們:婦女能頂半邊天。女人是偉大的,女人總在不經意間改變歷史。
PAN
Primary Account Number的縮寫,ISO 9564中定義:assigned number, composed of an issuer identification number, an individual account identification and an accompanying check digit as specified in ISO/IEC 7812-1, which identifies the card issuer and cardholder。
主賬戶號碼,發行者分配的號碼,由發行者識別號碼、個人賬戶標識和ISO/IEC 7812-1中規定的校驗數字組成,用於識別髮卡機構和持卡人。詳見 銀行卡號的編碼規則及校驗 。
PIN block
ISO 9564標準中定義了5種格式,Format 0,Format 1, Format 2, Format 3,Format 4。
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(); }
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(); }
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 為例:

程式碼示例:
/** * 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'); }
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(); }
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程式碼示例。
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加密金鑰時使用。
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計算方式。
- 網際網路支付密碼的長度必須在6到20個字元以內;
- 網際網路支付密碼均為ASCII碼字元,既可以為字元,也可以為數字,或其他符號;
- 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(); }
不安全的PIN
ISO 9564-1標準建議客戶在設定或更換密碼時遵循以下原則:
- 不包括客戶資訊,比如:姓氏、手機號碼、生日等;
- 不包括賬號中連續的數字;
- 不包括三個及以上的相同數字;
- 不包括三個及以上遞增或遞減的連續數字;
- 不包括歷史上重要的日期。
除了這些,筆者從自身專案經驗中總結出以下幾點供大家參考(主要針對6位數字的密碼):
- 兩個及以上的相同數字;
- AABBCC格式,比如:112233
- AAABBB格式,比如:111222
- AAAABB格式,比如:111122
- AAAAAB格式,比如:111112
- AAAAAA格式,比如:111111
- 兩個及以上遞增或遞減的連續數字;
- ABABAB格式,比如:121212、212121
- ABCABC格式,比如:123123、321321
- ABCDAB格式,比如:123412、432121
- ABCDEA格式,比如:123451、543211
- ABCDEF格式,比如:123456、654321
- 賬號中任意連續的6個數字;
- 身份證號中任意連續的6個數字;
- 手機號中任意連續的6個數字;
- 不與之前設定密碼相同;
- 建立弱密碼錶,位於弱密碼錶中的都不安全。
網際網路支付密碼一般會建議:包含大寫字母、小寫字母、數字、特殊符號中三種或三種以上的不小於8位的支付密碼。
彩虹表
簡單來說,彩虹表就是密碼的合集,你的密碼在人家的彩虹表裡,就容易被破解;彩虹表很大,大到你的密碼很容易出現在其中,但是彩虹表又很小,掌握了彩虹表的原理,你的密碼就可以消失的無影無蹤。
彩虹表如果展開內容較多,後續會開闢新篇專題交流。本篇主要從以下幾點來探討彩虹表破解的防範措施:
儲存密碼明文是大忌
前幾年,儲存密碼明文的事件被多次曝光,不斷重新整理著IT從業人員的安全意識下限,好在經過多年的網路安全普及與國家對相關行業軟體系統的嚴格監管,至少筆者所在的金融軟體行業領域,儲存密碼明文的系統已經被淘汰在歷史的洪流中了。
公開的雜湊演算法不安全
彩虹表正是針對公開的雜湊演算法的破解之法,儘管雜湊演算法是不可逆的,但是破解它其實更加簡單,隨著科技的發展,窮舉的速度越來越快,有了彩虹表的加持,更加勢如破竹。
雜湊演算法,越來越淪為完整性校驗的工具。
為雜湊演算法加鹽是個好主意
沒錯,我為自己帶鹽。給密碼加鹽後再進行雜湊,即使被破解,也是加鹽的密碼,原始密碼也是相對安全的。PIN block也可以算作一種加鹽方法,只不過PIN block的鹽包含在賬號中,在不知道賬號的前提下,得到了PIN block確實很難破解出密碼。
足夠聰明可以自定義加密演算法
使用自定義的加密演算法,彩虹表就失去了用武之地,在加密演算法和金鑰不洩露的前提下,破解基本是不可能的。但是,世界上聰明人那麼多,切忌自以為是,須知人外有人,要時刻保持警惕。
謹防資料庫洩露
如果資料庫中密碼資料洩露,聰明的黑客總能探索到破解之法,因此對資料庫要做足夠的安全防護。
如果可能
- 密碼加密演算法不可逆,演算法不公開,限定於僅核心開發人員(A)知曉;
- 資料庫加密防護,脫離資料庫環境後無法開啟,開啟方式限定於僅核心運維人員(B)知曉;
- 網路防護,限定IP訪問,使用堡壘機等手段,訪問許可權限定於僅核心運維人員(C)擁有;
- 以上,湊齊ABC才可能發生資料庫洩露,此時發生撞庫的概率就變的極低。
當然,如果程式碼中存在某個bug,也可能被高明的黑客利用,所謂道高一尺,魔高一丈,資訊保安的攻防戰沒有絕對的贏家。
其他建議
- 使用密碼控制元件,研發較困難,採購最直接;
- 不常登入地區/終端,進行簡訊驗證碼校驗;
- 限制簡訊驗證碼失效時間;
- 限制簡訊驗證碼傳送頻率;
- 密碼輸入錯誤2次,進行圖形驗證碼校驗;
- 密碼輸入錯誤5次,鎖定賬號,併發簡訊通知客戶。
最後
安全是每個金融軟體系統的底線。