c# - promocionales - Generación de código de cupón
código cashback g2a 2018 (5)
Me gustaría generar códigos de cupones, por ejemplo, AYB4ZZ2
. Sin embargo, también me gustaría poder marcar los cupones usados y limitar su número global, digamos N
El enfoque ingenuo sería algo así como "generar N
códigos alfanuméricos únicos, ponerlos en la base de datos y realizar una búsqueda de db en cada operación de cupón".
Sin embargo, hasta donde yo sé, también podemos intentar encontrar una función MakeCoupon(n)
, que convierte el número dado en una cadena tipo cupón con una longitud predefinida.
Por lo que yo entiendo, MakeCoupon
debe MakeCoupon
los siguientes requisitos:
Ser bijective Es inverso
MakeNumber(coupon)
debe ser efectivamente computable.La salida para
MakeCoupon(n)
debe ser alfanumérica y debe tener una longitud pequeña y constante , de modo que se pueda llamar legible para humanos . Por ejemplo, el resumen deSHA1
no pasaría este requisito.Unicidad práctica. Los resultados de
MakeCoupon(n)
para cada naturaln <= N
deben ser totalmente únicos o únicos en los mismos términos que, por ejemplo,MD5
es único (con la misma probabilidad de colisión extremadamente pequeña).(este es difícil de definir) No debería ser obvio cómo enumerar todos los cupones restantes de un único código de cupón, digamos que
MakeCoupon(n)
yMakeCoupon(n + 1)
deberían ser visualmente diferentes.Por ejemplo,
MakeCoupon(n),
que simplemente generan
relleno con ceros, no cumpliría este requisito, porque000001
y000002
realidad no difieren "visualmente".
P:
¿Existe alguna función o generador de funciones que cumpla con los siguientes requisitos? Mis intentos de búsqueda solo me llevan a [CPAN]
CouponCode, pero no cumple el requisito de que la función correspondiente sea bijective.
Aunque me atrape esta respuesta, siento que debo responder: realmente espero que escuchen lo que digo, ya que proviene de una gran cantidad de experiencia dolorosa.
Si bien esta tarea es muy desafiante desde el punto de vista académico, y los ingenieros de software tienden a desafiar su intelecto frente a la resolución de problemas , debo brindarle alguna orientación sobre esto, si me lo permite. No existe una tienda minorista en el mundo que tenga algún tipo de éxito de todos modos, que no mantenga un buen seguimiento de todas y cada una de las entidades que se generan; de cada pieza de inventario a cada cupón o tarjeta de regalo que envían esas puertas. Simplemente no es un buen administrador si lo es, porque no es si la gente te va a engañar, sino cuándo, y si tienes todos los elementos posibles en tu arsenal estarás listo.
Ahora, hablemos sobre el proceso por el cual se usa el cupón en su escenario.
Cuando el cliente canjea el cupón va a haber algún tipo de sistema de POS en frente, ¿verdad? Y eso incluso puede ser un negocio en línea donde pueden simplemente ingresar su código de cupón frente a un registro donde el cajero escanea un código de barras correcto (supongo que eso es lo que estamos tratando aquí) . Y ahora, como vendedor, está diciendo que si tiene un código de cupón válido, le daré algún tipo de descuento y como nuestro objetivo era generar códigos de cupones que fueran reversibles, no necesitamos una base de datos. para verificar ese código, ¡podemos simplemente invertirlo correctamente! Quiero decir que solo son matemáticas ¿verdad? Bueno, sí y no.
Sí, tienes razón, son solo matemáticas. De hecho, ese es también el problema porque también lo está el descifrado de SSL . Pero, supongo que todos nos damos cuenta de que la matemática utilizada en SSL es un poco más compleja que cualquier otra que se use aquí y la clave es sustancialmente mayor.
No le corresponde a usted, ni es prudente que intente idear algún tipo de plan que esté seguro de que a nadie le importa lo suficiente como para romperlo, especialmente cuando se trata de dinero . Estás haciendo tu vida muy difícil tratando de resolver un problema que realmente no deberías tratar de resolver porque necesitas protegerte de aquellos que usan los códigos de cupón.
Por lo tanto, este problema es innecesariamente complicado y podría resolverse así.
// insert a record into the database for the coupon
// thus generating an auto-incrementing key
var id = [some code to insert into database and get back the key]
// base64 encode the resulting key value
var couponCode = Convert.ToBase64String(id);
// truncate the coupon code if you like
// update the database with the coupon code
- Crea una tabla de cupones que tenga una clave de incremento automático.
- Inserte en esa tabla y obtenga la clave de auto incremento.
- Base64 codifica esa identificación en un código de cupón.
- Trunque esa cadena si lo desea.
- Guarde esa cadena en la base de datos con el cupón recién insertado.
Básicamente puedes dividir tu operación en partes:
- De alguna manera, "cifra" tu número inicial
n
, de modo que dos números consecutivos arrojen (muy) diferentes resultados - Construya su código "legible por humanos" a partir del resultado del paso 1
Para el paso 1, sugeriría usar un simple cifrado de bloques (por ejemplo, un cifrado de Feistel con una función de ronda de su elección). Ver también esta pregunta .
Las cifras de Feistel funcionan en varias rondas . Durante cada ronda, se aplica una función redonda a la mitad de la entrada, el resultado se xor
con la otra mitad y las dos mitades se intercambian. Lo bueno de los cifradores de Feistel es que la función de ronda no tiene que ser bidireccional (la entrada a la función de ronda se mantiene sin modificar después de cada ronda, por lo que el resultado de la función de ronda se puede reconstruir durante el descifrado). Por lo tanto, puedes elegir cualquier operación loca que te guste :). También los cifrados de Feistel son simétricos, lo que cumple su primer requisito.
Un pequeño ejemplo en C #
const int BITCOUNT = 30;
const int BITMASK = (1 << BITCOUNT/2) - 1;
static uint roundFunction(uint number) {
return (((number ^ 47894) + 25) << 1) & BITMASK;
}
static uint crypt(uint number) {
uint left = number >> (BITCOUNT/2);
uint right = number & BITMASK;
for (int round = 0; round < 10; ++round) {
left = left ^ roundFunction(right);
uint temp = left; left = right; right = temp;
}
return left | (right << (BITCOUNT/2));
}
(Tenga en cuenta que después de la última ronda no hay intercambio, en el código simplemente se deshace el intercambio en la construcción del resultado)
Además de cumplir tus requisitos 3 y 4 (la función es total , por lo que para diferentes entradas obtienes diferentes salidas y la entrada está "totalmente codificada" de acuerdo con tu definición informal) también es su propia inversa (cumpliendo implícitamente el requisito 1), es decir crypt(crypt(x))==x
para cada x
en el dominio de entrada ( 0..2^30-1
en esta implementación). También es barato en términos de requisitos de rendimiento.
Para el paso 2 simplemente codifica el resultado a alguna base de tu elección. Por ejemplo, para codificar un número de 30 bits, puede usar 6 "dígitos" de un alfabeto de 32 caracteres (para que pueda codificar 6 * 5 = 30 bits).
Un ejemplo para este paso en C #:
const string ALPHABET= "AG8FOLE2WVTCPY5ZH3NIUDBXSMQK7946";
static string couponCode(uint number) {
StringBuilder b = new StringBuilder();
for (int i=0; i<6; ++i) {
b.Append(ALPHABET[(int)number&((1 << 5)-1)]);
number = number >> 5;
}
return b.ToString();
}
static uint codeFromCoupon(string coupon) {
uint n = 0;
for (int i = 0; i < 6; ++i)
n = n | (((uint)ALPHABET.IndexOf(coupon[i])) << (5 * i));
return n;
}
Para las entradas 0 - 9 esto produce los siguientes códigos de cupón
0 => 5VZNKB
1 => HL766Z
2 => TMGSEY
3 => P28L4W
4 => EM5EWD
5 => WIACCZ
6 => 8DEPDA
7 => OQE33A
8 => 4SEQ5A
9 => AVAXS5
Tenga en cuenta que este enfoque tiene dos "secretos" internos diferentes: en primer lugar, la función de ronda junto con el número de rondas utilizadas y, en segundo lugar, el alfabeto que utiliza para codificar el resultado cifrado. ¡Pero también tenga en cuenta que la implementación mostrada no es de ninguna manera segura en un sentido criptográfico!
También tenga en cuenta que la función que se muestra es una función biyectiva total , en el sentido de que cada código de 6 caracteres posible (con caracteres fuera de su alfabeto) arrojará un número único. Para evitar que alguien ingrese solo un código aleatorio, debe definir algún tipo de restricciones en el número de entrada. Por ejemplo, solo emitir cupones para los primeros 10.000 números. Entonces, la probabilidad de que algún código de cupón aleatorio sea válido sería 10000/2 ^ 30 = 0.00001 (requeriría aproximadamente 50000 intentos de encontrar un código de cupón correcto). Si necesita más "seguridad", puede aumentar el tamaño del bit / longitud del código de cupón (ver a continuación).
EDITAR: Cambiar la longitud del código del cupón
Cambiar la longitud del código de cupón resultante requiere algo de matemática: el primer paso (cifrado) solo funciona en una cadena de bits con un conteo de bits par (esto es necesario para que el cifrado de Feistel funcione).
En el segundo paso, el número de bits que se pueden codificar utilizando un alfabeto determinado depende del "tamaño" del alfabeto elegido y la longitud del código del cupón. Esta "entropía", dada en bits, no es, en general, un número entero, mucho menos un número entero par. Por ejemplo:
Un código de 5 dígitos con un alfabeto de 30 caracteres da como resultado 30 ^ 5 códigos posibles que significa ld (30 ^ 5) = 24.53 bits / Código de cupón.
Para un código de cuatro dígitos, hay una solución simple: dado un alfabeto de 32 caracteres, puede codificar * ld (32 ^ 4) = 5 * 4 = 20 * Bits. Así que puede establecer el BITCOUNT
en 20 y cambiar el ciclo for
en la segunda parte del código para ejecutar hasta 4
(en lugar de 6
)
Generar un código de cinco dígitos es un poco más complicado y, a veces, "debilita" el algoritmo: puede establecer el BITCOUNT
en 24 y generar un código de 5 dígitos a partir de un alfabeto de 30 caracteres (eliminar dos caracteres de la cadena ALPHABET
y dejar for
ejecutar bucle hasta 5
).
Pero esto no generará todos los códigos de 5 dígitos posibles: con 24 bits, solo puede obtener 16,777,216 valores posibles de la etapa de encriptación, los códigos de 5 dígitos podrían codificar 24,300,000 números posibles, por lo que algunos códigos posibles nunca se generarán. Más específicamente, la última posición del código nunca contendrá algunos caracteres del alfabeto. Esto puede verse como un inconveniente, porque reduce el conjunto de códigos válidos de una manera obvia.
Al decodificar un código de cupón, primero deberá ejecutar la función codeFromCoupon
y luego verificar, si se establece el bit 25 del resultado. Esto marcaría un código no válido que puede rechazar de inmediato. Tenga en cuenta que, en la práctica, esto incluso podría ser una ventaja, ya que permite una comprobación rápida (por ejemplo, en el lado del cliente) de la validez de un código sin revelar todos los aspectos internos del algoritmo.
Si el bit 25 no está configurado, llamarás a la función crypt
y obtendrás el número original.
Lo que quiere se llama cifrado de preservación de formato .
Sin pérdida de generalidad, codificando en la base 36 podemos suponer que estamos hablando de enteros en 0..M-1
lugar de cadenas de símbolos. M
probablemente debería ser un poder de 2.
Después de elegir una clave secreta y especificar M
, FPE le otorga una permutación pseudoaleatoria de 0..M-1
junto con su decrypt
inverso.
string GenerateCoupon(int n) {
Debug.Assert(0 <= n && n < N);
return Base36.Encode(encrypt(n));
}
boolean IsCoupon(string code) {
return decrypt(Base36.Decode(code)) < N;
}
Si su FPE es seguro, este esquema es seguro: ningún atacante puede generar otros códigos de cupón con probabilidad superior a O (N / M) dado el conocimiento de muchos cupones arbitrariamente, incluso si logra adivinar el número asociado con cada cupón que él conoce .
Este sigue siendo un campo relativamente nuevo, por lo que hay pocas implementaciones de dichos esquemas de cifrado. Esta pregunta crypto.SE solo menciona Botan , una biblioteca C ++ con enlaces Perl / Python, pero no C #.
Advertencia: además del hecho de que aún no existen estándares aceptados para FPE, debe considerar la posibilidad de un error en la implementación. Si hay mucho dinero en juego, debe sopesar ese riesgo contra el beneficio relativamente pequeño de evitar una base de datos.
Puede usar un sistema de números base 36. Suponga que quiere 6 caracteres en la salida de coupen.
pseudo código para MakeCoupon
MakeCoupon (n) {
Tener una matriz de bytes de tamaño fijo, digamos 6. Inicializar todos los valores a 0. convertir el número a base - 36 y almacenar los ''dígitos'' en una matriz (usando operaciones de división y modificación de enteros) Ahora, para cada ''dígito'' encontrar el código ascii correspondiente suponiendo que los dígitos comienzan desde 0..9, A..Z Con esta salida de convección seis dígitos como una cadena.
}
Ahora el cálculo del número de vuelta es el reverso de esta operación.
Esto funcionaría con números muy grandes (35 ^ 6) con 6 caracteres permitidos.
Elija una función criptográfica
c
. Hay algunos requisitos en c, pero por ahora tomemos SHA1.elige una clave secreta
k
.
Su función de generación de código de cupón podría ser, para el número n
:
- Concatenar n y k como
"n"+"k"
(esto se conoce como salazón en la administración de contraseñas) - calcular c ("n" + "k")
- el resultado de SHA1 es de 160bits, codifíquelos (por ejemplo, con base64) como una cadena ASCII
- si el resultado es demasiado largo (como dijiste que es el caso de SHA1), trunque para mantener solo las primeras 10 letras y nombra esta cadena
- su código de cupón es
printf "%09d%s" ns
, es decir, la concatenación den
rellenosn
los hash truncadoss
.
Sí, es trivial adivinar el número del código del cupón (pero vea abajo). Pero es difícil generar otro código válido.
Sus requisitos están satisfechos:
- Para calcular la función inversa, solo lea los primeros 9 dígitos del código
- La longitud es siempre 19 (9 dígitos de n, más 10 letras de hash)
- Es único, ya que los primeros 9 dígitos son únicos. Los últimos 10 caracteres también lo son, con alta probabilidad.
- No es obvio cómo generar el hash, incluso si uno adivina que usó SHA1.
Algunos comentarios:
- Si le preocupa que leer
n
sea demasiado obvio, puede ofuscarlo ligeramente, como la codificación base64, y alternar en el código los caracteres den
ys
. - Supongo que no necesitará más de mil millones de códigos, por lo tanto, la impresión de n en 9 dígitos, pero puede, por supuesto, ajustar los parámetros 9 y 10 a la longitud deseada del código de cupón.
- SHA1 es solo una opción, puede usar otra función criptográfica como el cifrado de clave privada, pero debe verificar que esta función se mantenga fuerte cuando se trunca y cuando se proporciona el texto claro.
- Esto no es óptimo en la longitud del código, pero tiene la ventaja de la simplicidad y las bibliotecas ampliamente disponibles.