Generowanie unikalnych, trudnych do odgadnięcia kodów "kuponów"

Moja aplikacja Rails musi generować elektroniczne kupony dla użytkowników. Każdy podany kupon powinien mieć unikalny kod kuponu, który można zrealizować w naszym systemie.

Na przykład kupon na darmowe burrito. User A otrzymuje kupon na darmowe burrito, a następnie User B otrzymuje kupon na darmowe burrito. 2 kupony powinny mieć unikalne kody kuponów.

Jaki jest najlepszy sposób na wygenerowanie takiego kodu, który nie jest łatwo sfałszowany? Nie chcę, aby użytkownicy mieli wysoki wskaźnik skuteczności pisania w losowych liczbach i odkupienia kuponów innych narodów.

Myślę, że myślenie o tym jak o Karcie Podarunkowej z unikalnym numerem na odwrocie jest tym, czego szukam.
Author: Bob Gilmore, 2014-03-11

7 answers

Kod musi być nie do odgadnięcia, ponieważ jedyną weryfikacją, którą możesz wykonać przed przyznaniem użytkownikowi nagrody, jest sprawdzenie, czy wprowadzony kod istnieje na twojej liście "wydanych" kodów.

  • Oznacza to, że liczba wszystkich możliwych kodów w tym formacie jest znacznie większa niż liczba kodów, które chcesz wydać. W zależności od tego, jak łatwo jest po prostu wypróbować kody( pomyśl o skrypcie próbującym wielokrotnie), możesz potrzebować wszystkich możliwych kodów, aby przewyższyć liczbę wydanych / align = "center" bgcolor = "# e0ffe0 " / cesarz chin / / align = center / Brzmi to wysoko, ale jest możliwe w stosunkowo krótkich strunach.

  • Oznacza to również, że kody, których używasz, muszą być wybrane tak losowo, jak to możliwe we wszystkich możliwych kodach. Jest to konieczne, aby użytkownicy nie zorientowali się, że większość ważnych kodów zaczyna się od "AAA" na przykład. Bardziej wyrafinowani użytkownicy mogą zauważyć, że Twoje "losowe" kody używają hakowalnego generatora liczb losowych (domyślne Ruby rand() jest szybkie i statystycznie dobre dla losowych danych, ale jest hackable w ten sposób, więc nie używać go).

Punktem wyjścia dla takiego bezpiecznego kodu byłoby wyjście z kryptograficznego PRNG. Ruby posiada bibliotekę securerandom, której możesz użyć do uzyskania surowego kodu takiego jak:

require 'securerandom'
SecureRandom.hex
# => "78c231af76a14ef9952406add6da5d42"

Ten kod jest wystarczająco długi, aby pokryć dowolną realistyczną liczbę bonów (miliony każdy dla wszystkich na planecie), bez żadnej sensownej szansy powtórzenia lub jest łatwy do odgadnięcia. Jednak trochę niezręcznie jest pisać z Kopia fizyczna.

Gdy już wiesz, jak wygenerować losowy, praktycznie nie do wykrycia kod, Twoim następnym problemem jest zrozumienie doświadczenia użytkownika i podjęcie decyzji, jak bardzo możesz realnie zagrozić bezpieczeństwu w imię użyteczności. Musisz pamiętać o wartości dla użytkownika końcowego, a tym samym o tym, jak bardzo ktoś może próbować uzyskać poprawny kod. Nie mogę ci na to odpowiedzieć, ale mogę powiedzieć kilka ogólnych uwag na temat użyteczności: {]}

  • Unikaj wieloznacznych znaków. W druku, czasem trudno dostrzec różnicę między 1, I i l na przykład. Często rozumiemy, co to ma być z kontekstu, ale losowy ciąg znaków nie ma tego kontekstu. To byłoby złe doświadczenie użytkownika, aby spróbować kilka odmian kodu przez testowanie 0 vs O, 5 vs S itd.

  • Użyj małych lub wielkich liter, ale nie obu. Wrażliwość na wielkość liter nie będzie rozumiana lub następuje po pewnym % wieku użytkownika użytkowników.

  • Akceptuj zmiany przy dopasowywaniu kodów. Zezwalaj na spacje i myślniki. Może nawet pozwolić 0 i O oznaczać to samo. Najlepiej jest to zrobić przetwarzając tekst wejściowy tak, aby był w odpowiednim przypadku, znaki separatora pasków itp.

  • W druku oddziel kod na kilka małych części, łatwiej będzie użytkownikowi znaleźć swoje miejsce w łańcuchu i wpisać kilka znaków na raz.

  • Nie rób kodu zbyt długo. Ja bym zaproponuj 12 znaków, w 3 grupach po 4.

  • Oto jeden z ciekawych - możesz zeskanować kod w poszukiwaniu możliwych niegrzecznych słów lub uniknąć znaków, które mogłyby je wygenerować. Jeśli twój kod zawierał tylko znaki K, U, F, C, wtedy istnieje duża szansa na obrażenie użytkownika. Zazwyczaj nie jest to problemem, ponieważ użytkownicy nie widzą większości kodów zabezpieczających komputer, ale te będą drukowane!

Złożenie tego wszystkiego do kupy, w ten sposób mogę wygenerować użyteczny kod:

# 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"

Istnieją 20**12 poprawne kody w tym formacie, co oznacza, że możesz wydać miliard własnych kodów, i istnieje jedna na cztery miliony szans na to, że użytkownik po prostu zgadnie poprawny. W kręgach kryptograficznych byłoby to bardzo złe (ten kod jest niepewny przed szybkim lokalnym atakiem), ale dla formularza internetowego oferującego darmowe burrito zarejestrowanym użytkownikom, a gdzie można zauważyć, że ktoś próbuje 4 miliony razy ze skryptem, jest to ok.

 39
Author: Neil Slater,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-12 10:21:57

Ostatnio napisałem coupon-code gem , który robi dokładnie to samo. Algorytm zapożyczony z algorytmu:: COUPONCODE CPAN module.

Kod kuponu powinien być nie tylko unikalny, ale także łatwy do odczytania i wpisania, gdy nadal jest bezpieczny. Wyjaśnienie i rozwiązanie Neila jest świetne. Ten klejnot zapewnia wygodny sposób na zrobienie tego i funkcję walidacji bonusu.

>> require 'coupon_code'
>> code = CouponCode.generate
=> "1K7Q-CTFM-LMTC"
>> CouponCode.validate(code)
=> "1K7Q-CTFM-LMTC"
>> CouponCode.validate('1K7Q-CTFM-LMTO') # Invalid code
=> nil
 7
Author: baxang,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-22 05:11:28

Kluczem do tworzenia niezauważalnych kodów kuponów jest duża przestrzeń możliwych kodów, z których tylko niewielka część jest faktycznie ważna. Weźmy na przykład 8 znaków ciągów alfanumerycznych:

Alfanumeryczny = 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ - 63 znaki

W tym przypadku istnieją 63^8 = 248155780267521 możliwe kody. Oznacza to, że jeśli wydasz miliard kodów, prawdopodobieństwo odgadnięcia kodu wyniesie 10^9/63^8 = 0.000004... - 4 na milion.

Nie uniemożliwia jednak uruchomienia skryptu, który utrzymuje próbuję, dopóki nie wykombinuje poprawnego kodu. Aby zablokować taki atak brute force, musisz policzyć próby na użytkownika i zablokować ponad pewien limit.

Jeśli szukasz biblioteki, która umożliwia pełne dostosowanie wyjściowych kodów kuponów (długość, charset, prefiks, przyrostek i wzór) spójrz na voucher-code-generator-js - bibliotekę napisaną w języku JavaScript. Przykład użycia:

voucher_codes.generate({
    length: 8,
    count: 1000,
});

Wygeneruje 1000 losowych unikalnych kodów, każdy po 8 znaków długa.

Inny przykład:

voucher_codes.generate({
    pattern: "###-###-###",
    count: 1000,
});

Wygeneruje 1000 losowych unikalnych kodów zgodnie z podanym wzorem.

Kod źródłowy jest stosunkowo prosty. Założę się, że możesz łatwo przepisać go do dowolnego innego języka, jeśli JS nie jest Twoim ulubionym;)

Jeśli potrzebujesz kompleksowego rozwiązania do zarządzania kodami kuponów (w tym zapobiegania atakom brute force), możesz być zainteresowany Voucherify .

 4
Author: Voucherify,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-13 13:28:34

Go with something like:

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

EDIT: Oto lepsza ODPOWIEDŹ

 1
Author: Abram,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:16:48

Możesz np. użyć losowej liczby i sprawdzić, czy nie została wygenerowana wcześniej, przechowując wszystkie ważne kody w bazie danych.

 0
Author: MrSmith42,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-11 18:24:10

Rysowanie liczb losowych za pomocą sprawdzonego generatora ( http://en.wikipedia.org/wiki/List_of_pseudorandom_number_generators).

Załóżmy, że dostarczasz 333 kupony dziennie i są one ważne przez 30 dni. Więc musisz przechowywać 10000 numerów i upewnić się, że fałszerz nie może znaleźć przez przypadek.

Jeśli twoje liczby mają 10 cyfr znaczących (~32 bity ,~ 8 cyfr szesnastkowych), prawdopodobieństwo takiego zdarzenia wynosi jeden na milion. Oczywiście możesz użyć więcej.

 0
Author: Yves Daoust,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-11 21:22:42

Miałem podobny przypadek użycia, w którym musiałem wygenerować unikalny / Nie powtarzający się kod dla każdego obiektu stworzonego w systemie (w tym pytaniu jest to kupon). Miałem następujące wymagania:

  • chciałem, aby długość kodu była jak najkrótsza.
  • zdałem sobie sprawę, że długość kodu będzie przynajmniej tak długa, jak liczba cyfr, które określają liczbę możliwych obiektów. Dla np. jeśli wygenerujesz 9999 kuponów, kod będzie zasadniczo muszą być co najmniej 4 cyfry długości.
  • nie powinny być sekwencyjne / łatwo zgadywalne.

Zbadałem kilka metod generowania kluczy, w tym te, które są oparte na znacznikach czasu i odkryłem, że większość metod generuje długie kody. Postanowiłem więc zastosować własną logikę w następujący sposób.

  • tworzę tabelę db, w której tworzę tylko jeden rekord, który utrzymuje liczbę obiektów utworzonych do tej pory w systemie.
  • Następnie przedrostek i przyrostek tej liczby z jednym znakiem każdy losowo wybrany z [a-zA-Z0-9]. Ten krok zapewnia, że nawet jeśli liczby są sekwencyjne, nie jest możliwe odgadnięcie kodu, chyba że prefiks i sufiks zostaną odgadnięte. Na podstawie zestawu znaków [a-zA-Z0 - 9], będzie 3782 (62*61) możliwości kodu. Powyższy zestaw znaków działa dla mnie, ale możesz używać dowolnie wybranego zestawu znaków. Niektóre sugestie znajdują się na najlepszej odpowiedzi dla tego wątku.
  • Po każdym utworzeniu nowego obiektu liczba obiektów wynosi zwiększony o jeden w db.

W tym podejściu liczba znaków kodu będzie określona przez:

number of characters of ( count of objects in the system so far ) + 2

Więc po uruchomieniu liczba znaków będzie 3, Po osiągnięciu 10 obiektów będzie 4, Po osiągnięciu 100 obiektów będzie 5, po 1000 będzie 6 i tak dalej. W ten sposób system będzie skalowany samodzielnie w zależności od zastosowania.

To podejście sprawdziło się lepiej niż w przypadku, gdy najpierw generowany jest kod, a następnie sprawdzanie, czy kod jest już istniejące w db. W takim przypadku możesz generować kody, aż znajdziesz kod, który nie jest jeszcze wygenerowany.

 0
Author: Aneesh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-13 07:22:12