Jak stworzyć generator

0

Witam mam pytanie jak stworzyć swój generator tokenów? Czy tak się da?

2

Biorąc pod uwagę, że ktoś kiedyś stworzył… to tak, da się.

Jak chcesz gotowe algorytmy, to wyszukaj sobie w sieci TOTP (Time-based One-Time Password), HOTP (HMAC-based One-Time Password) czy OCRA (OATH Challenge-Response Algorithm).

Jak chcesz ukuć coś swojego, to najprostsza idea jest taka, żeby mieć jakiś sekret, jakąś sól i jakąś funkcję skrótu, i liczyć ją z tego sekretu i soli. Adaptujesz ten schemat wg potrzeb — np. jak chcesz mieć kupony zniżkowe, to sekret trzymasz tylko na serwerze, a sól to niech będzie generowany losowy ciąg n znaków; dajesz użytkownikom n + hasz(n + sekret), a potem na serwerze odcinasz pierwszych n znaków otrzymanego kodu, liczysz z niego ten hasz i patrzysz, czy się zgadza. Albo jak chcesz czasowe tokeny, to niech sekret będzie wspólny dla użytkownika i serwera, a sól to czas.

0

Szkic rozwiązania. Prawie gotowiec, ale święta i w ogóle, to nawet Althorion potrafi przemówić ludzkim głosem:

from hashlib import blake2b
from random import randint

COUNTER_LENGTH_IN_CHARS = 6  # arbitrary, gives 16 ** COUNTER_LENGTH_IN_CHARS possible tokens
HASH_LENGTH_IN_BYTES = 6  # arbitrary <= 64 == blake2b.MAX_DIGEST_SIZE
assert HASH_LENGTH_IN_BYTES <= blake2b.MAX_DIGEST_SIZE
HASH_LENGTH_IN_CHARS = HASH_LENGTH_IN_BYTES * 2
TOKEN_LENGTH_IN_CHARS = COUNTER_LENGTH_IN_CHARS + HASH_LENGTH_IN_CHARS
SECRET_HASH_KEY = b"much secret, very hash, WOW!"  # probably want something with higher entropy than that
assert len(SECRET_HASH_KEY) <= blake2b.MAX_KEY_SIZE


def generate_token(secret: bytes = SECRET_HASH_KEY) -> str:
    salt_value = randint(0, 16 ** COUNTER_LENGTH_IN_CHARS - 1)
    salt_string = f"{salt_value:06x}"
    h = blake2b(digest_size=HASH_LENGTH_IN_BYTES, key=secret)
    h.update(bytes(salt_string, encoding="ascii"))
    return f"{salt_string}{h.hexdigest()}"


def validate_token(token: str, secret: bytes = SECRET_HASH_KEY) -> bool:
    if len(token) != TOKEN_LENGTH_IN_CHARS:
        return False
    salt_string = token[:COUNTER_LENGTH_IN_CHARS]
    expected_hash = token[COUNTER_LENGTH_IN_CHARS:]
    h = blake2b(digest_size=HASH_LENGTH_IN_BYTES, key=secret)
    h.update(bytes(salt_string, encoding="ascii"))
    return expected_hash == h.hexdigest()


if __name__ == "__main__":
    assert validate_token("cedabf13e7fec65ec3")  # known good token
    assert validate_token("a3705e4692be865b7c") is False  # known bad token

    N_OF_TESTS = 10 ** 6
    # random good tokens validate correctly
    assert all(map(validate_token, [generate_token() for _ in range(N_OF_TESTS)]))
    # can theoretically result in a false positive, but with the default values the chances for that are
    # about as "good" as for winning a lottery
    from random import choices
    assert (any(map(validate_token, ["".join(choices("0123456789abcdef", k=TOKEN_LENGTH_IN_CHARS)) for _ in range(N_OF_TESTS)]))
        is False
    )

1 użytkowników online, w tym zalogowanych: 0, gości: 1