還有什麼能比處理表格中的信用卡資料更枯燥的呢?恩,如果你打算對卡號進行加密就不會那麼枯燥了,當然這需要應對一些挑戰。然而,它不過只是一個數字文字框,資料會儲存在一個資料庫裡——沒什麼特別,也用不到什麼高深的技術。我有一些待處理資料,需要從中找到 ABN——一種沒有什麼意思的資料。澳大利亞人肯定都知道ABN,對其他人而言,它代表的是政府為每個公司分配的11位澳大利亞商業編號(Australian Business Number )。這不是什麼祕密(你可以在網上找到),所以你甚至不必為其加密,因為沒有人會因此感到興奮。當然,如果事情僅僅如此,那就不值得寫一篇部落格了。對了,正如你想象的那樣,事情並不像它們看上去那麼平淡無奇。
關於信用卡號一些有意思的事情
在 CrowdHired,我們並沒有和信用卡打過很多交道,但ABN完全是另外一回事,因為企業客戶是我們系統的使用者(順便說一下,正如你猜測的,過去幾個月裡我為一家創業公司工作。我真的應該談一談如何創業,那肯定會是一個有意思的故事)。對於任何資料,你都希望儘可能對使用者輸入進行驗證。當我打算對ABN進行驗證時,我發現了一些有意思的特性,信用卡號也具有同樣的特性。正如你知道的那樣,信用卡和ABN號碼都是可自我驗證的資料。
時至今日,我已經做web開發很多年了,但對於處理這些資料沒有任何經驗。所以自然地,作為一名好奇的開發者,我做了一些深入的調查。結果是,這種能夠自我驗證的資料非常普遍,其他一些廣為人知的例子有ISBN、UPC和VIN。其中大多數都是用了基於校驗資料位演算法的一個變種進行驗證和生成。可能這些演算法中最有名的就是信用卡採用的Luhn演算法。所以我們用信用卡作為示例。
驗證和生成信用卡號碼(及其他基於校驗資料位的數字)
例如我們有下面信用卡號碼:
1 |
4870696871788604 |
它有16個數字(維薩和萬事達卡通常是16位,Amex是15位)。信用卡號可以分成下列部分:
1 2 |
發行編號| 賬號 | 校驗資料 487069 | 687178860 | 4 |
你可以找到很多關於信用卡號的結構分析,但我們想要做的是應用Luhn演算法來檢驗信用卡號是否有效。接下來要這麼處理:
1. 從後往前,每隔一個數字對資料加倍
1 2 |
4 | 8 | 7 | 0 | 6 | 9 | 6 | 8 | 7 | 1 | 7 | 8 | 8 | 6 | 0 | 4 8 | 8 |14 | 0 |12 | 9 |12 | 8 |14 | 1 |14 | 8 |16 | 6 |00 | 4 |
2.如果需要加倍的數字有兩位,將這兩個數字相加
1 2 3 |
4 | 8 | 7 | 0 | 6 | 9 | 6 | 8 | 7 | 1 | 7 | 8 | 8 | 6 | 0 | 4 8 | 8 |14 | 0 |12 | 9 |12 | 8 |14 | 1 |14 | 8 |16 | 6 |00 | 4 8 | 8 | 5 | 0 | 3 | 9 | 3 | 8 | 5 | 1 | 5 | 8 | 7 | 6 | 0 | 4 |
3.將所有的數字相加得到結果
1 |
8+8+5+0+3+9+3+8+5+1+5+8+7+6+0+4 = 80 |
4.如果相加之和和可以被10整除,那麼就是一個有效的信用卡號碼。舉例的信用卡號就是有效的。
下面你可以看到我們是如何使用同樣的演算法來生成一個有效的信用卡號。我們所要做的就是把校驗位值設定成X並且執行所有相似的步驟。在最後一步,我們只要將我們的校驗位置為可以將所有數字之和可以被10整除。讓我們在之前的信用卡號上稍微做一點修改(我們只要將校驗位置為1,這樣得到的就是一個無效的信用卡號)。
1 2 3 4 5 6 |
4 | 8 | 7 | 0 | 6 | 9 | 6 | 8 | 7 | 1 | 7 | 8 | 8 | 6 | 1 | X 8 | 8 |14 | 0 |12 | 9 |12 | 8 |14 | 1 |14 | 8 |16 | 6 | 2 | X 8 | 8 | 5 | 0 | 3 | 9 | 3 | 8 | 5 | 1 | 5 | 8 | 7 | 6 | 2 | X 8+8+5+0+3+9+3+8+5+1+5+8+7+6+2+X = 78+X X = (78%10 == 0) ? 0 : 10 - 78%10 X=2 |
正如你看到的,無論其他15個數字是什麼,我們總能夠在0到9之前找到生成有效信用卡號碼的校驗數字。
當然,並不是每個子驗證數字都是採用 Luhn演算法。大多數不採用對10取餘數生成校驗位,像 IBAN一類的資料,校驗位實際上由兩個數字組成。並且,大多數奇怪的自我驗證資料都和我第一次知道的ABN一樣。因為,以我的經歷而言,我不能指出ABN的校驗位應該是什麼。
ABN的奇怪之處
澳大利亞肯定不願意使用基於校驗位的演算法。澳大利亞稅收檔案資料TFN(Tax File Number)和澳大利亞公司資料ACN(Australian Company Number)就是兩個例子,但是ABN似乎與之不同。乍看上去,ABN驗證演算法似乎與之類似,只是在最後使用了一個更大的數字進行取模操作(對89取餘數,mod(89)
)
• 從(左邊)第一個數字開始逐個減一,得到一個新的11位數
• 對生成的新資料的每個數字乘以它的權重因子
• 將11個乘積加在一起
• 對乘積綜合除以89,取餘數
• 如果餘數為0,那麼該ABN有效
事實上,這裡有一些用來驗證ABN的ruby程式碼,這是我從 Ruby ABN gem中抽取出來的(並且很好地結合進了Rails3 ActiveRecord驗證子,這樣我們可以任意地呼叫 validates_abn_format_of
)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
def is_integer?(number) Integer(number) true rescue false end def abn_valid?(number) raw_number = number number = number.to_s.tr ' ','' return false unless is_integer?(number) && number.length == 11 weights = [10, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19] sum = 0 (0..10).each do |i| c = number[i,1] digit = c.to_i - (i.zero? ? 1 : 0) sum += weights[i] * digit end sum % 89 == 0 ? true : false end |
但是,儘管驗證ABN資料很容易,但是生成卻又是另外一回事了。正如我們看到的,基於校驗位的演算法,生成和驗證資料的過程是一樣的,只有在取模步驟中我們需要選擇不同的數字來驗證餘數是否為0。但是,像ABN這樣的資料,沒有明顯的校驗位(也許我可能比較愚笨,所以如果你發現有明顯的ABN校驗位請不吝賜教),如果有效地生成一個有效的資料呢?事實上,為什麼想要生成這些資料呢,僅僅驗證資料的有效性還不夠用嗎?
恩,以 CrowdHired為例,我們試圖生成一個很深的物件樹,所以我們構建了一段維護基礎架構的程式碼來允許我們建立偽資料來供開發使用(我們會在晚些時候討論另一個有意思的事情)。在我們開始利用ABN資料的自我驗證特性之前我們僅僅生成了任意的11為數字組成的數值作為偽ABN資料,但是一旦驗證開始,我們就發現不能再這麼幹了。作為高效的開發者,我們(儘管我們這麼稱呼自己)使用一些真正的ABN(用我們掌握的那些),將他們放到一列陣列中,然後隨機地從中選取。但這種方式冒犯了開發者心中的上帝(換句話說觸犯了我們的自尊——所以無論如何,我決定在週六花上幾個小時來編寫程式生成一些真正隨機且有效的ABN資料)。下面是我的程式碼(現在這段程式碼成為偽資料生成指令碼的核心部分):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
def random_abn weights = [10,1,3,5,7,9,11,13,15,17,19] reversed_weights = weights.reverse initial_numbers = [] final_numbers = [] 9.times {initial_numbers << rand(9)+1} initial_numbers = [rand(8)+1, rand(7)+2] + initial_numbers products = [] weights.each_with_index do |weight, index| products << weight * initial_numbers[index] end product_sum = products.inject(0){|sum, value| sum + value} remainder = product_sum % 89 if remainder == 0 final_numbers = initial_numbers else current_remainder = remainder reversed_numbers = initial_numbers.reverse reversed_weights.each_with_index do |weight, index| next if weight > current_remainder if reversed_numbers[index] > 0 reversed_numbers[index] -= 1 current_remainder -= weight if current_remainder < reversed_weights[index+1] redo end end end final_numbers = reversed_numbers.reverse end final_numbers[0] += 1 final_numbers.join end |
1.首先,我們隨機成成11個0到9之間的資料,來組成我們未來的ABN(他們實際上並非都在0到9之間,後面很快會對此進行說明)
1 |
7 5 8 9 8 7 3 4 1 5 3 |
2.然後,我們對該資料執行驗證步驟
• 對這些資料乘以他們的權重得到帶權重的乘積
1 7x10=70 5x1=5 8x3=24 9x5=45 8x7=56 7x9=63 3x11=33 4x13=52 1x15=15 5x17=85 3x19=57• 將所有的乘積相加
1 70+5+24+45+56+63+33+52+15+85+57 = 505• 對89取模得到餘數
1 505 mod 89 = 60
3.因為我們對89取模,所以最壞的情況得到的餘數是88(儘管加入我們幸運地得到餘數是0也就是直接得到了一個有效的ABN),我們現在可以使用帶有權重的數字乘積來“進行變換”,與餘數相減直到我們得到的結果是0。
我們從最後一位數字開始(權重是19),我們對這個數字減1,這就意味著我們從餘數中減去了19。依次對下一個數字進行同樣的操作,知道餘數的變成0。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
初始值 | 改變後的數值 | 餘數 ------------------------------- 7x10=70 | 7x10=70 | 0 5x1=5 | 5x1=5 | 0 8x3=24 | 8x3=24 | 0 9x5=45 | 9x5=45 | 0 8x7=56 | 8x7=56 | 0 7x9=63 | 6x9=63 | 0 3x11=33 | 3x11=33 | 9 4x13=52 | 4x13=52 | 9 1x15=15 | 0x15=0 | 9 5x17=85 | 4x17=68 | 24 3x19=57 | 2x19=38 | 41 |
4.結果產生了我們的新數值
1 |
7 5 8 9 8 6 3 4 0 4 2 |
5.現在我們只要對每個資料的第一個數字加1(根據ABN的驗證步驟)這樣我們就得到了有效的ABN資料
1 |
85898634042 |
這些步驟之間有一些細微的差別。
• 我們生成的初始資料裡沒有0。因為我們“進行變化”的時候是通過對每個數字減一,那麼就必須確保我們能夠對他們減1(否則事情會變得更加複雜)。所以我們確保資料可以隨機的在1和9之間選擇,而不是0到9。
• 即使我們所有的初始資料都保證至少為1,我們任然可能會對一些餘數“進行變化”時失敗,最簡單的例子就是當我們的餘數是2時。唯一可以使用進行變化的數字權重為1(例如,ABN的第二個數字)。如果這個數字初始生成為1,我們只能進行一次變換並且得到的餘數為1,這樣我們就不能再進行任何的處理了。事實上,確切的場景還有餘數為86, 77, 66, 53, 38, 21。客服這個問題最簡單的辦法就是保證生成的數值至少為2.這樣我們至少可以進行兩次變化,這樣我們的問題餘數都會被覆蓋到。
• 最後,儘管我們在最後一步對每個資料的第一位加1,我們需要確保這個資料不能為9,所以我們需要確保生成的數字在1到8之間。
即使注意到了所有這些區別,這個演算法還是不能夠生成所有可能的ABN,但是對應我們的需求已經可以提供儘可能多的有效ABN。這個演算法花費了我們1個小時(我們沒有提到我忘記於是不能為0這個小bug,這個問題給我們的隨機資料生成器帶來了很大的悲劇:))但是這確實是一個有意思的小練習——就我而言在這個上面花費的時間為有所值。要知道,所有的這些關於子驗證資料的學習以及演算法程式設計的樂趣都是由表格裡一段最常見的資料引起的。這也就說明了,無論你在哪裡在做什麼都可以學習和成長,你需要做的只是能夠發現這些機會而已。
原文:Alan Skorkin 編譯:伯樂線上 – 唐尤華
【如需轉載,請標註並保留原文連結、譯文連結和譯者等資訊,謝謝合作!】