Статьи

Даже скучные данные формы могут быть интересными (для разработчика)


Что может быть скучнее, чем сбор данных кредитной карты в форме? Ну, на самом деле это не так скучно, поскольку вы можете зашифровать именно эти данные, что создает свои собственные проблемы. Тем не менее, это все еще текстовое поле, которое принимает цифры, которые вы храните в базе данных — whoopty doo — не совсем ракетная операция. Что ж, у меня есть данные, которые бьют по кредитной карте за чистоту жизни —
ABN . Если вы австралиец, вы знаете все об этом. Для всех остальных это означает «Австралийский бизнес-номер», который представляет собой 11-значный номер, предоставляемый правительством каждой компании. Это не секрет (
вы можете посмотреть их онлайн), так что вам даже не нужно его шифровать — сложно быть взволнованным. Конечно, если бы это был конец истории, это не было бы большим количеством поста в блоге, так что, как вы можете себе представить,
все не так скучно, как кажется .

Интересная вещь о номере кредитной карты …

В CrowdHired мы не склонны иметь дело с номерами кредитных карт, но ABN — это совсем другой вопрос — поскольку компании являются одним из двух типов пользователей, которых мы имеем в системе ( кстати — как вы, возможно, и поняли — я последние несколько месяцев я работаю для стартапа, я должен рассказать о том, как это произошло , это интересная история ). Как и любой фрагмент данных, вы хотите проверить пользовательский ввод, если можете. Когда я начал изучать ABN, я обнаружил, что у них есть интересная черта, которая характерна для номеров кредитных карт. Видите ли, как кредитные карты, так и номера ABN являются номерами для самопроверки .

Я занимаюсь веб-разработкой уже много лет, но понятия не имел, что это так. Так естественно — будучи любопытным разработчиком, которым я являюсь — мне пришлось копать немного дальше. Оказывается, что эти типы чисел довольно распространены, с другими хорошо известными примерами являются номера ISBN , UPC  и VIN . Большинство из них используют разновидность алгоритма на основе контрольных цифр для проверки и генерации . Вероятно, наиболее известным из этих алгоритмов является алгоритм Луна,  которым пользуются кредитные карты. Итак, мы будем использовать кредитную карту в качестве примера.

Проверка и генерация кредитных карт

Допустим, у нас есть следующий номер кредитной карты:

4870696871788604

Это 16 цифр ( Visa и MasterCard обычно 16, но Amex 15 ). Этот номер разбит следующим образом:

Issuer Number | Account Number | Check Digit
487069        | 687178860      | 4

Вы можете прочитать больше об анатомии кредитной карты , но все, что мы хотим сделать, это применить алгоритм Луна, чтобы проверить, действительна ли эта кредитная карта. Это выглядит примерно так:

 

1. Начиная сзади, удваивайте каждую вторую цифру

  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. Если сдвоенные числа образуют двузначное число, добавьте две цифры

  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. Суммируйте все цифры этого нового номера

  8+8+5+0+3+9+3+8+5+1+5+8+7+6+0+4 = 80

 

4. Если число делится на 10, это действительный номер кредитной карты. Что в нашем случае так и есть.

Вы можете увидеть, как мы можем использовать тот же алгоритм для создания действительного номера кредитной карты. Все, что нам нужно сделать, это установить значение контрольной цифры на X и затем выполнить все те же шаги. На последнем этапе мы просто выбираем нашу контрольную цифру таким образом, чтобы убедиться, что сумма всех цифр делится на 10. Давайте сделаем это для слегка измененной версии номера нашей предыдущей кредитной карты ( мы просто устанавливаем цифру перед контрольная цифра в 1 делает номер кредитной карты недействительным ).

  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, которая сделает действительным номер кредитной карты .

Конечно, не каждый самопроверяющийся номер использует алгоритм Луна, большинство не используют mod (10), чтобы определить, какой должна быть контрольная цифра, а для некоторых чисел, таких как IBAN , контрольная цифра фактически состоит из 2 цифр. И, тем не менее, самый любопытный номер для проверки — первый, о котором я узнал — ABN. Это потому, что, по жизни, я не мог понять, какой может быть контрольная цифра ABN .

Загадочный случай ABN

любознательный

Австралия, конечно, не прочь использовать алгоритмы на основе контрольных цифр. Номер налоговой декларации Австралии ( TFN ) и номер австралийской компании ( ACN ) — это только два примера, но ABN, кажется, отличается. На первый взгляд, алгоритм валидации ABN  почти такой же, он просто имеет больший, чем обычно, шаг « mod » в конце (mod (89)).

  • Вычтите 1 из первой (левой) цифры, чтобы получить новое одиннадцатизначное число
  • Умножьте каждую из цифр в этом новом номере на весовой коэффициент
  • Суммируйте полученные 11 продуктов
  • Разделите итог на 89, отметив остаток
  • Если остаток равен нулю, число является действительным

Фактически, вот некоторый код ruby ​​для проверки ABN, который я присвоил из гема Ruby ABN  ( а затем свернул его в хороший Rails 3, валидатор ActiveRecord, чтобы мы могли сделать validates_abn_format_of во всех наших моделях ? ):

  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 очень просто, их создание — это совсем другое дело . Как мы уже видели, при использовании алгоритма на основе контрольных цифр генерация числа аналогична проверке числа, за исключением того, что мы выбираем цифру таким образом, чтобы наш шаг «mod» оценивался в ноль. Но с таким номером, как ABN, где нет явной контрольной цифры ( возможно, у меня просто дурак, так что если вы видите очевидную контрольную цифру с ABN, дайте мне знать ), как вы легко можете сгенерировать действительный номер? На самом деле, почему вы хотите генерировать эти числа в первую очередь, не будучи в состоянии проверить их достаточно ?

Что ж, в случае CrowdHired мы склонны создавать довольно глубокие деревья объектов, поэтому мы создаем некоторый некоторый инфраструктурный код, позволяющий нам создавать поддельные данные для использования во время разработки ( еще одна интересная вещь, о которой мы поговорим позже ) , Прежде чем мы начали использовать самопроверяющиеся свойства ABN, мы просто сгенерировали любое старое 11-значное число в качестве фальшивых данных для полей ABN, но как только проверки начали вводиться, это больше не было возможностью. Будучи прагматичными разработчиками, которыми мы являемся ( даже если мы сами так говорим ), мы взяли несколько настоящих ABN ( например, наши собственные).) забросил их в массив и случайно выбрал оттуда. Но это оскорбило богов разработчиков или мою гордость разработчиков — в зависимости от того, что случилось, поэтому в одну из суббот я решил потратить пару часов на создание действительно случайных ABN, которые все еще действовали. Вот код, который я придумал ( теперь он является гордой частью нашего скрипта генерации поддельных данных ):

  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, но об этом в ближайшее время )

  7 5 8 9 8 7 3 4 1 5 3

2. Затем мы выполняем шаги проверки для этого номера

  • умножьте цифры на их веса, чтобы получить весовые продукты 

      7x10=70
      5x1=5
      8x3=24
      9x5=45
      8x7=56
      7x9=63
      3x11=33
      4x13=52
      1x15=15
      5x17=85
      3x19=57
    
  • суммировать продукты
    70+5+24+45+56+63+33+52+15+85+57 = 505
  • разделите на 89, чтобы получить остаток
    505 mod 89 = 60

3. Так как мы делаем мод (89), в худшем случае мы уйдем на 88 ( хотя, если мы получим 0 в качестве остатка, мы сразу же скинули действительный ABN ), мы теперь используем продукты с весовыми цифрами, чтобы « дать изменение». «, вычитая из остатка, пока мы идем, пока не достигнем нуля.

Мы начинаем с последней цифры, где вес равен 19. Мы вычитаем 1 из этой цифры, что означает, что мы можем вычесть 19 из нашего остатка. Затем мы переходим к следующей цифре, пока остаток не достигнет нуля

  Initial | Change  | Remainder
  -------------------------------
  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. Это дает нам наш новый номер

7 5 8 9 8 6 3 4 0 4 2

5. Теперь нам просто нужно добавить 1 к самому первому номеру ( согласно шагам проверки ABN ), и у нас есть действительный ABN

85898634042

У этих шагов есть пара нюансов.

  • Ни одна из наших начальных сгенерированных цифр не может быть нулевой . Поскольку мы « даем изменение », вычитая 1 из каждой цифры, нам нужно убедиться, что мы действительно можем вычесть 1 (в противном случае все становится намного сложнее). Таким образом, мы гарантируем, что наши цифры генерируются случайным образом от 1 до 9, а не от 0 до 9.
  • Даже если все наши начальные цифры по крайней мере равны 1, мы все равно не можем « дать изменение » для некоторых из оставшихся , самый простой пример этого — когда наш остаток равен 2. Единственная цифра, которую можно использовать, чтобы дать изменение, это цифра с вес 1 ( т.е. вторая цифра ABN ). Если эта цифра изначально сгенерирована как 1, мы можем дать изменение только один раз, и в этот момент у нас останется 1, и мы ничего не можем с этим поделать. Фактически этот точный сценарий может произойти для нескольких других остатков, а именно 86, 77, 66, 53, 38, 21. Самый простой способ преодолеть эту проблему — убедиться, что цифра, имеющая вес 1, всегда генерируется случайным образом. иметь значение как минимум 2. Таким образом, мы можем использовать его, чтобы дважды вносить изменения, и наши проблемные остатки покрываются.
  • И наконец, поскольку мы должны добавить 1 к самой первой цифре в качестве нашего последнего шага, нам нужно убедиться, что эта цифра уже не равна 9 , поэтому мы генерируем эту цифру в диапазоне от 1 до 8.

Учитывая эти нюансы, этот алгоритм не будет генерировать все возможные ABN, но он даст вам большой процент возможных ABN, что достаточно для наших нужд. Это заняло около часа ( мы не будем упоминать небольшую ошибку, когда я забыл, что остаток может быть нулевым с самого начала, что вызвало много горя у нашего генератора случайных данных ? ), но это было забавное небольшое упражнение — время, проведенное, насколько мне известно. И, если подумать, все эти знания о самопроверкаемых числах и забавном алгоритмическом кодировании были вызваны попыткой собрать самые обыденные данные в форме. Это просто говорит о том, что вы можете учиться и расти независимо от того, где вы находитесь и чем занимаетесь, вам просто нужно увидеть возможности для того, что они есть.