ruby on rails - promocionales - Generación de códigos de cupones únicos y difíciles de adivinar
generador de cupones (7)
Dibuje números aleatorios usando un generador probado ( http://en.wikipedia.org/wiki/List_of_pseudorandom_number_generators ).
Suponga que entrega 333 cupones por día y estos son válidos por 30 días. Así que tienes que almacenar 10000 números y asegurarte de que un falsificador no pueda encontrar uno por casualidad.
Si sus números tienen 10 dígitos significativos (~ 32 bits, ~ 8 dígitos hexadecimales), la probabilidad de tal evento es de uno más de un millón. Por supuesto que puedes usar más.
La aplicación My Rails necesita generar cupones electrónicos para los usuarios. Cada cupón dado debe tener un código de cupón único que se puede canjear en nuestro sistema.
Por ejemplo, un cupón para un burrito gratis. User A
recibe un cupón para un burrito gratis y luego el User B
recibe un cupón para un burrito gratis. Los 2 cupones deben tener códigos de cupón únicos.
¿Cuál es la mejor manera de generar un código como este que no es fácil de falsificar? No quiero que los usuarios tengan una alta tasa de éxito al escribir números aleatorios y canjear cupones de otras personas.
Supongo que pensar en ello como una tarjeta de regalo con un número único en la parte posterior es lo que estoy buscando.
El código debe ser indiscutible, porque la única verificación que puede realizar antes de otorgarle al usuario su recompensa es verificar si el código que ingresaron existe en su lista de códigos "emitidos".
Eso significa que el número de todos los códigos posibles en ese formato es mucho mayor que el número de códigos que desea emitir. Dependiendo de lo fácil que sea simplemente probar códigos (piense en un script que lo intenta varias veces), entonces es posible que necesite todos los códigos posibles para superar en número a los códigos emitidos por un factor de un millón o un billón o más. Esto suena alto, pero es posible en cadenas relativamente cortas.
También significa que los códigos que utiliza deben elegirse lo más aleatoriamente posible dentro de todos los códigos posibles. Esto es necesario para evitar que los usuarios descubran que la mayoría de los códigos válidos comienzan con "AAA", por ejemplo. Los usuarios más sofisticados podrían detectar que sus códigos "aleatorios" usan un generador de números aleatorios hackeable (el
rand()
predeterminado de Ruby es rápido y estadísticamente bueno para datos aleatorios, pero es hackeable de esta manera, así que no lo use).
El punto de partida para tal código seguro sería el resultado de un PRNG criptográfico. Ruby tiene la biblioteca securerandom
, que puedes usar para obtener un código en bruto como este:
require ''securerandom''
SecureRandom.hex
# => "78c231af76a14ef9952406add6da5d42"
Este código es lo suficientemente largo para cubrir cualquier número realista de cupones (millones para cada uno en el planeta), sin ninguna posibilidad significativa de repetición o de ser fácil de adivinar. Sin embargo, es un poco incómodo escribir desde una copia física.
Una vez que sepa cómo generar un código aleatorio y prácticamente imposible de adivinar, su siguiente problema es comprender la experiencia del usuario y decidir cuánto puede comprometer de manera realista la seguridad en nombre de la usabilidad. Debe tener en cuenta el valor para el usuario final y, por lo tanto, qué tan difícil puede ser que alguien intente obtener un código válido. No puedo responder eso por ti, pero puedo hacer algunos puntos generales sobre la usabilidad:
Evita los personajes ambiguos. En la impresión, a veces es difícil ver la diferencia entre
1
,I
yl
por ejemplo. A menudo entendemos lo que se supone que es del contexto, pero una cadena aleatoria de caracteres no tiene este contexto. Sería una mala experiencia para el usuario tener que probar varias variaciones de un código probando0
vsO
,5
vsS
etc.Use letras minúsculas o mayúsculas, pero no ambas. La sensibilidad a las mayúsculas y minúsculas no será comprendida o seguida por algún porcentaje de edad de sus usuarios.
Acepta variaciones cuando coinciden códigos. Permitir espacios y guiones. Tal vez incluso permita que
0
yO
signifiquen lo mismo. Esto se hace mejor procesando el texto de entrada, por lo que es en el caso correcto, separar caracteres, etc.En la impresión, separe el código en unas pocas partes pequeñas, será más fácil para el usuario encontrar su lugar en la cadena y escribir algunos caracteres a la vez.
No hagas el código demasiado largo. Sugeriría 12 caracteres, en 3 grupos de 4.
Aquí hay una interesante: es posible que desee escanear el código en busca de posibles palabras groseras, o evitar los caracteres que las generarían. Si su código contuviera solo los caracteres
K
,U
,F
,C
, entonces habría una alta probabilidad de ofender a un usuario. Esto no suele ser una preocupación porque los usuarios no ven la mayoría de los códigos de seguridad de la computadora, ¡pero estos estarán impresos!
Poniendo todo junto, así es como podría generar un código utilizable:
# Random, unguessable number as a base20 string
# .reverse ensures we don''t use first character (which may not take all values)
raw_string = SecureRandom.random_number( 2**80 ).to_s( 20 ).reverse
# e.g. "3ecg4f2f3d2ei0236gi"
# Convert Ruby base 20 to better characters for user experience
long_code = raw_string.tr( ''0123456789abcdefghij'', ''234679QWERTYUPADFGHX'' )
# e.g. "6AUF7D4D6P4AH246QFH"
# Format the code for printing
short_code = long_code[0..3] + ''-'' + long_code[4..7] + ''-'' + long_code[8..11]
# e.g. "6AUF-7D4D-6P4A"
Hay 20**12
códigos válidos en este formato, lo que significa que puede emitir mil millones de sus propios códigos, y habría una posibilidad entre cuatro millones de que un usuario simplemente adivine uno correcto. En los círculos de criptografía, eso sería muy malo (este código es inseguro contra un ataque local rápido), pero para un formulario web que ofrece burritos gratuitos a los usuarios registrados, y donde notaría que alguien lo intenta cuatro millones de veces con un script, está bien .
Ir con algo como:
class Coupon < ActiveRecord::Base
before_save generate_token
validates_uniqueness_of :token
def generate_token
self.token = "#{current_user.id}#{SecureRandom.urlsafe_base64(3)}"
end
end
EDITAR: Aquí hay una mejor respuesta
La clave para crear códigos de cupón no presumibles es un gran espacio de códigos posibles con solo una pequeña fracción de ellos siendo realmente válidos. Tomemos, por ejemplo, 8 caracteres de cadenas alfanuméricas:
alfanumérico = 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
- 63 caracteres
En este caso hay 63^8 = 248155780267521
códigos posibles. Significa que si emite mil millones de códigos, la probabilidad de adivinar un código será 10^9/63^8 = 0.000004...
- 4 en un millón.
Sin embargo, no evita que uno ejecute un script que sigue intentando hasta que descubre un código válido. Para bloquear un ataque de fuerza bruta de este tipo, deberá contar los intentos por usuario y prohibirlos por encima de algún límite.
Si está buscando una biblioteca que permita la personalización completa de los códigos de cupón de salida (longitud, conjunto de caracteres, prefijo, sufijo y patrón), consulte el voucher-code-generator-js , una biblioteca escrita en JavaScript. Ejemplo de uso:
voucher_codes.generate({
length: 8,
count: 1000,
});
Generará 1000 códigos únicos al azar, cada uno de 8 caracteres.
Otro ejemplo:
voucher_codes.generate({
pattern: "###-###-###",
count: 1000,
});
Generará 1000 códigos únicos aleatorios siguiendo un patrón dado.
El código fuente es relativamente simple. Apuesto a que puedes reescribirlo fácilmente en cualquier otro idioma si JS no es tu favorito;)
Si necesita una solución integral para la administración de códigos de cupones (incluida la prevención de ataques de fuerza bruta), puede estar interesado en Voucherify .
Puede, por ejemplo, usar un número aleatorio y verificar si no se generó antes, almacenando todos los códigos válidos en una base de datos.
Recientemente escribí una gema de código de cupón que hace exactamente lo mismo. El algoritmo tomado de Algorithm :: CouponCode CPAN module.
Un código de cupón no solo debe ser único, sino que también debe ser fácil de leer y escribir mientras esté seguro. La explicación y solución de Neil es genial. Esta gema proporciona una forma conveniente de hacerlo y una característica de validación de bonificación.
>> require ''coupon_code''
>> code = CouponCode.generate
=> "1K7Q-CTFM-LMTC"
>> CouponCode.validate(code)
=> "1K7Q-CTFM-LMTC"
>> CouponCode.validate(''1K7Q-CTFM-LMTO'') # Invalid code
=> nil
Tuve un caso de uso similar en el que tuve que generar un código único / no repetitivo para cada objeto creado en el sistema (en esta pregunta, es un cupón). Tenía los siguientes requisitos:
- Quería que la longitud del código fuera lo más corta posible.
- Lo que me di cuenta es que la longitud del código será al menos tan larga como el número de dígitos que determinan el recuento del número de objetos posibles. Por ejemplo. Si genera 9999 cupones, el código tendrá que tener al menos 4 dígitos de longitud.
- No debe ser secuencial / fácil de adivinar.
Exploré varios métodos para generar claves, incluidas las que se basan en la marca de tiempo, y descubrí que la mayoría de los métodos generan códigos largos. Entonces, decidí emplear mi propia lógica de la siguiente manera.
- Creo una tabla db donde solo creo un registro, que mantiene el recuento del número de objetos creados hasta ahora en el sistema.
- Luego prefijo y coloco este número con un carácter, cada uno seleccionado al azar de [a-zA-Z0-9]. Este paso garantiza que, aunque los números son secuenciales, no es posible adivinar el código a menos que se adivinen el prefijo y el sufijo. Basado en el conjunto de caracteres [a-zA-Z0-9], habría 3782 (62 * 61) posibilidades para un código. El juego de caracteres anterior funciona para mí, pero usted es libre de usar un juego de caracteres de su elección. Algunas sugerencias se encuentran en la mejor respuesta para este hilo.
- Cada vez que se crea un nuevo objeto, el número de objetos aumenta en uno en la db.
En este enfoque, el número de caracteres del código estará determinado por:
number of characters of ( count of objects in the system so far ) + 2
Entonces, cuando comiences, el número de caracteres será 3, cuando alcances 10 objetos, será 4, cuando alcances 100 objetos, será 5, para 1000 será 6 y así sucesivamente. De esta manera, el sistema se escalará por sí solo dependiendo del uso.
Este enfoque funcionó mejor que el caso en el que primero se genera un código y luego se comprueba si el código ya existe en la db. En ese caso, sigue generando códigos hasta que encuentre un código que aún no se ha generado.