Algoritmo Luhn mod N - Luhn mod N algorithm
O algoritmo Luhn mod N é uma extensão do algoritmo Luhn (também conhecido como algoritmo mod 10) que permite trabalhar com sequências de valores em qualquer base . Isso pode ser útil quando um dígito de verificação é necessário para validar uma sequência de identificação composta de letras, uma combinação de letras e dígitos ou qualquer conjunto arbitrário de N caracteres.
Explicação informal
O algoritmo Luhn mod N gera um dígito de verificação (mais precisamente, um caractere de verificação) dentro do mesmo intervalo de caracteres válidos que a string de entrada. Por exemplo, se o algoritmo for aplicado a uma string de letras minúsculas ( a a z ), o caractere de verificação também será uma letra minúscula. Tirando essa distinção, ele se parece muito com o algoritmo original.
A ideia principal por trás da extensão é que o conjunto completo de caracteres de entrada válidos é mapeado para uma lista de pontos de código (ou seja, inteiros sequenciais começando com zero). O algoritmo processa a string de entrada convertendo cada caractere em seu ponto de código associado e, em seguida, executando os cálculos no mod N (onde N é o número de caracteres de entrada válidos). Finalmente, o ponto de código de verificação resultante é mapeado de volta para obter seu caractere de verificação correspondente.
Mapeando caracteres para pontos de código
Inicialmente, um mapeamento entre caracteres de entrada válidos e pontos de código deve ser criado. Por exemplo, considere que os caracteres válidos são as letras minúsculas de a a f . Portanto, um mapeamento adequado seria:
| Personagem | uma | b | c | d | e | f |
|---|---|---|---|---|---|---|
| Ponto de código | 0 | 1 | 2 | 3 | 4 | 5 |
Observe que a ordem dos personagens é completamente irrelevante. Este outro mapeamento também seria aceitável (embora possivelmente mais complicado de implementar):
| Personagem | c | e | uma | f | b | d |
|---|---|---|---|---|---|---|
| Ponto de código | 0 | 1 | 2 | 3 | 4 | 5 |
Também é possível misturar letras e dígitos (e possivelmente até outros caracteres). Por exemplo, este mapeamento seria apropriado para dígitos hexadecimais em minúsculas:
| Personagem | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | uma | b | c | d | e | f |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Ponto de código | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
Algoritmo em C #
Supondo que as seguintes funções sejam definidas:
int CodePointFromCharacter(char character) {...}
char CharacterFromCodePoint(int codePoint) {...}
int NumberOfValidInputCharacters() {...}
A função para gerar um caractere de verificação é:
char GenerateCheckCharacter(string input)
{
int factor = 2;
int sum = 0;
int n = NumberOfValidInputCharacters();
// Starting from the right and working leftwards is easier since
// the initial "factor" will always be "2".
for (int i = input.Length - 1; i >= 0; i--)
{
int codePoint = CodePointFromCharacter(input[i]);
int addend = factor * codePoint;
// Alternate the "factor" that each "codePoint" is multiplied by
factor = (factor == 2) ? 1 : 2;
// Sum the digits of the "addend" as expressed in base "n"
addend = IntegerValue(addend / n) + (addend % n);
sum += addend;
}
// Calculate the number that must be added to the "sum"
// to make it divisible by "n".
int remainder = sum % n;
int checkCodePoint = (n - remainder) % n;
return CharacterFromCodePoint(checkCodePoint);
}
E a função para validar uma string (com o caractere de verificação como o último caractere) é:
bool ValidateCheckCharacter(string input)
{
int factor = 1;
int sum = 0;
int n = NumberOfValidInputCharacters();
// Starting from the right, work leftwards
// Now, the initial "factor" will always be "1"
// since the last character is the check character.
for (int i = input.Length - 1; i >= 0; i--)
{
int codePoint = CodePointFromCharacter(input[i]);
int addend = factor * codePoint;
// Alternate the "factor" that each "codePoint" is multiplied by
factor = (factor == 2) ? 1 : 2;
// Sum the digits of the "addend" as expressed in base "n"
addend = IntegerValue(addend / n) + (addend % n);
sum += addend;
}
int remainder = sum % n;
return (remainder == 0);
}
Algoritmo em Java
Supondo que as seguintes funções sejam definidas:
int codePointFromCharacter(char character) {...}
char characterFromCodePoint(int codePoint) {...}
int numberOfValidInputCharacters() {...}
A função para gerar um caractere de verificação é:
char generateCheckCharacter(String input) {
int factor = 2;
int sum = 0;
int n = numberOfValidInputCharacters();
// Starting from the right and working leftwards is easier since
// the initial "factor" will always be "2".
for (int i = input.length() - 1; i >= 0; i--) {
int codePoint = codePointFromCharacter(input.charAt(i));
int addend = factor * codePoint;
// Alternate the "factor" that each "codePoint" is multiplied by
factor = (factor == 2) ? 1 : 2;
// Sum the digits of the "addend" as expressed in base "n"
addend = (addend / n) + (addend % n);
sum += addend;
}
// Calculate the number that must be added to the "sum"
// to make it divisible by "n".
int remainder = sum % n;
int checkCodePoint = (n - remainder) % n;
return characterFromCodePoint(checkCodePoint);
}
E a função para validar uma string (com o caractere de verificação como o último caractere) é:
boolean validateCheckCharacter(String input) {
int factor = 1;
int sum = 0;
int n = numberOfValidInputCharacters();
// Starting from the right, work leftwards
// Now, the initial "factor" will always be "1"
// since the last character is the check character.
for (int i = input.length() - 1; i >= 0; i--) {
int codePoint = codePointFromCharacter(input.charAt(i));
int addend = factor * codePoint;
// Alternate the "factor" that each "codePoint" is multiplied by
factor = (factor == 2) ? 1 : 2;
// Sum the digits of the "addend" as expressed in base "n"
addend = (addend / n) + (addend % n);
sum += addend;
}
int remainder = sum % n;
return (remainder == 0);
}
Algoritmo em JavaScript
Supondo que as seguintes funções sejam definidas:
const codePoints = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
//This can be any string of permitted characters
function numberOfValidInputCharacters() {
return codePoints.length;
}
function codePointFromCharacter(character) {
return codePoints.indexOf(character);
}
function characterFromCodePoint(codePoint) {
return codePoints.charAt(codePoint);
}
A função para gerar um caractere de verificação é:
function generateCheckCharacter(input) {
let factor = 2;
let sum = 0;
let n = numberOfValidInputCharacters();
// Starting from the right and working leftwards is easier since
// the initial "factor" will always be "2".
for (let i = input.length - 1; i >= 0; i--) {
let codePoint = codePointFromCharacter(input.charAt(i));
let addend = factor * codePoint;
// Alternate the "factor" that each "codePoint" is multiplied by
factor = (factor == 2) ? 1 : 2;
// Sum the digits of the "addend" as expressed in base "n"
addend = (Math.floor(addend / n)) + (addend % n);
sum += addend;
}
// Calculate the number that must be added to the "sum"
// to make it divisible by "n".
let remainder = sum % n;
let checkCodePoint = (n - remainder) % n;
return characterFromCodePoint(checkCodePoint);
}
E a função para validar uma string (com o caractere de verificação como o último caractere) é:
function validateCheckCharacter(input) {
let factor = 1;
let sum = 0;
let n = numberOfValidInputCharacters();
// Starting from the right, work leftwards
// Now, the initial "factor" will always be "1"
// since the last character is the check character.
for (let i = input.length - 1; i >= 0; i--) {
let codePoint = codePointFromCharacter(input.charAt(i));
let addend = factor * codePoint;
// Alternate the "factor" that each "codePoint" is multiplied by
factor = (factor == 2) ? 1 : 2;
// Sum the digits of the "addend" as expressed in base "n"
addend = (Math.floor(addend / n)) + (addend % n);
sum += addend;
}
let remainder = sum % n;
return (remainder == 0);
}
Exemplo
Geração
Considere o conjunto acima de caracteres de entrada válidos e a string de entrada de exemplo abcdef . Para gerar o caractere de verificação, comece com o último caractere na string e mova para a esquerda dobrando todos os outros pontos de código. Os "dígitos" dos pontos de código como escritos na base 6 (uma vez que existem 6 caracteres de entrada válidos) devem então ser somados:
| Personagem | uma | b | c | d | e | f |
|---|---|---|---|---|---|---|
| Ponto de código | 0 | 1 | 2 | 3 | 4 | 5 |
| em dobro | 2 | 6 (base 10) 10 (base 6) |
10 (base 10) 14 (base 6) |
|||
| Reduzir | 0 | 2 | 2 | 1 + 0 | 4 | 1 + 4 |
| Soma de dígitos | 0 | 2 | 2 | 1 | 4 | 5 |
A soma total dos dígitos é 14 (0 + 2 + 2 + 1 + 4 + 5). O número que deve ser adicionado para obter o próximo múltiplo de 6 (neste caso, 18 ) é 4 . Este é o ponto de código de verificação resultante. O caractere de verificação associado é e .
Validação
A string resultante abcdefe pode então ser validada usando um procedimento semelhante:
| Personagem | uma | b | c | d | e | f | e |
|---|---|---|---|---|---|---|---|
| Ponto de código | 0 | 1 | 2 | 3 | 4 | 5 | 4 |
| em dobro | 2 | 6 (base 10) 10 (base 6) |
10 (base 10) 14 (base 6) |
||||
| Reduzir | 0 | 2 | 2 | 1 + 0 | 4 | 1 + 4 | 4 |
| Soma de dígitos | 0 | 2 | 2 | 1 | 4 | 5 | 4 |
A soma total dos dígitos é 18 . Como é divisível por 6, o caractere de verificação é válido .
Implementação
O mapeamento de caracteres para pontos de código e vice-versa pode ser implementado de várias maneiras. A abordagem mais simples (semelhante ao algoritmo Luhn original) é usar aritmética de código ASCII. Por exemplo, dado um conjunto de entrada de 0 a 9 , o ponto de código pode ser calculado subtraindo o código ASCII para '0' do código ASCII do caractere desejado. A operação reversa fornecerá o mapeamento reverso. Intervalos adicionais de caracteres podem ser tratados usando declarações condicionais.
Conjuntos não sequenciais podem ser mapeados de ambas as maneiras usando uma instrução switch / case embutida. Uma abordagem mais flexível é usar algo semelhante a uma matriz associativa . Para que isso funcione, um par de matrizes é necessário para fornecer o mapeamento bidirecional.
Uma possibilidade adicional é usar uma matriz de caracteres onde os índices da matriz são os pontos de código associados a cada caractere. O mapeamento do caractere ao ponto de código pode então ser executado com uma pesquisa linear ou binária. Nesse caso, o mapeamento reverso é apenas uma pesquisa de array simples.
Fraqueza
Esta extensão compartilha a mesma fraqueza do algoritmo original, ou seja, não pode detectar a transposição da sequência <first-valid-character> <last-valid-character> para <last-valid-character> <first-valid-character> (ou vice-versa). Isso é equivalente à transposição de 09 para 90 (assumindo um conjunto de caracteres de entrada válidos de 0 a 9 na ordem). Em uma nota positiva, quanto maior o conjunto de caracteres de entrada válidos, menor o impacto da fraqueza.