Created
September 11, 2022 21:28
-
-
Save ddjerqq/063421924558048fa9a804cdd719ed7c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from __future__ import annotations | |
| import random | |
| import string | |
| import time | |
| from base64 import b64encode as b64e | |
| from base64 import b64decode as b64d | |
| from datetime import datetime | |
| __all__ = ( | |
| "Snowflake", | |
| "Token", | |
| "MfaToken" | |
| ) | |
| HMAC_CHARS = string.digits + string.ascii_letters + "-_" | |
| DISCORD_EPOCH = 142007040000 | |
| class Snowflake(int): | |
| """ | |
| Snowflakes | |
| Discord utilizes Twitter's snowflake format for uniquely identifiable descriptors (IDs). | |
| These IDs are guaranteed to be unique across all of Discord, except in some unique scenarios in which child objects share their parent's ID. | |
| Because Snowflake IDs are up to 64 bits in size (e.g. an uint64), they are always returned as strings in the HTTP API to prevent integer overflows in some languages. | |
| See Gateway ETF/JSON for more information regarding Gateway encoding. | |
| Snowflake ID Broken Down in Binary: | |
| 111111111111111111111111111111111111111111 11111 11111 111111111111 | |
| 64 22 17 12 0 | |
| Snowflake ID Format Structure (Left to Right): | |
| FIELD | BITS | NUMBER OF BITS | DESCRIPTION | RETRIEVAL | |
| Timestamp | 63 to 22 | 42 bits | Milliseconds since Discord Epoch, the first second of 2015 or 1420070400000. | (snowflake >> 22) + 1420070400000 | |
| Internal worker ID | 21 to 17 | 5 bits | | (snowflake & 0x3E0000) >> 17 | |
| Internal process ID | 16 to 12 | 5 bits | | (snowflake & 0x1F000) >> 12 | |
| Increment | 11 to 0 | 12 bits | For every ID that is generated on that process, this number is incremented | snowflake & 0xFFF | |
| source: https://discord.com/developers/docs/reference#snowflakes | |
| """ | |
| def __new__(cls) -> int: | |
| id = time.time_ns() // 10_000_000 | |
| id -= DISCORD_EPOCH | |
| id <<= 5 | |
| # internal worker id simulation | |
| worker_id = random.randrange(0, 32) # 5 bits | |
| id += worker_id | |
| id <<= 5 | |
| # internal process id simulation | |
| id += random.randrange(0, 32) # 5 bits | |
| id <<= 12 | |
| # for every ID that is generated on a process, this number is incremented | |
| id += random.randrange(0, 4096) # 12 bits | |
| return id | |
| @classmethod | |
| def created_at(cls, id: int) -> datetime: | |
| ts = ((id >> 22) + DISCORD_EPOCH) // 100 | |
| return datetime.fromtimestamp(ts) | |
| class Token: | |
| """ | |
| the discord token | |
| Example token: | |
| NjAzNzM2Njk1MTcwNjYyNDAx.XTj4lw.b73JFh51C9hsnM2COJ3zOTWIZV0 | |
| ^ ^ ^ | |
| head body tail | |
| head: this is the token owner's id, base64 encoded. | |
| body: this is the tokens timestamp, as integer bytes. this is the timestamp when the token was created. it changes with the password. | |
| tail: this part cannot be guessed. (thank god). it is an HMAC hash of the users credentials, along some discord salts. | |
| """ | |
| def __init__(self, *, id: int | None = None) -> None: | |
| self.id = id or Snowflake() | |
| @property | |
| def head(self) -> str: | |
| payload = str(self.id).encode() | |
| return b64e(payload).decode() | |
| @property | |
| def body(self) -> str: | |
| timestamp = int(Snowflake.created_at(self.id).timestamp()) | |
| mid_bytes = timestamp.to_bytes(4, "big") | |
| mid = b64e(mid_bytes).decode("ascii") | |
| return mid[:-2] | |
| @property | |
| def tail(self) -> str: | |
| return "".join(random.choices(HMAC_CHARS, k=38)) | |
| def __str__(self) -> str: | |
| return f"{self.head}.{self.body}.{self.tail}" | |
| @classmethod | |
| def from_snowflake(cls, id: int) -> Token: | |
| token = cls() | |
| token.id = id | |
| return token | |
| @property | |
| def creation_date(self) -> datetime: | |
| return Snowflake.created_at(self.id) | |
| @property | |
| def token_creation_date(self) -> datetime: | |
| body = b64d(self.body + "==") | |
| timestamp = int.from_bytes(body, "big") | |
| return datetime.fromtimestamp(timestamp) | |
| @classmethod | |
| def analyze(cls, token: str) -> tuple[int, datetime, datetime, str]: | |
| """ | |
| analyze a token | |
| :param token: the token which you want to analyze | |
| :return: (id, account_creation_date, token_creation_date, hmac_hash) | |
| """ | |
| head, body, tail = token.split(".") | |
| id = int(b64e(head.encode()).decode()) | |
| account_creation_date = Snowflake.created_at(id) | |
| token_creation_date = datetime.fromtimestamp( | |
| int.from_bytes(b64e(body + "=="), "big") | |
| ) | |
| return id, account_creation_date, token_creation_date, tail | |
| class MfaToken: | |
| """ | |
| the mfa discord token | |
| Example token: | |
| mfa.V_Bk6jI9YYJ5bZElogg7VttTGzkRGWDT0N5rT-7UU7K9DNUE01OccLEfgVDKL0o1pJ3cAJvXW9AZtZ_R5H9C | |
| ^ ^ | |
| | body | |
| mfa | |
| mfa: multi-factor authentication indicator | |
| body: 84 chars HMAC hash of the user's credentials, along discord salts. | |
| """ | |
| def __init__(self, *, id: int | None = None) -> None: | |
| self.id = id or Snowflake() | |
| def __str__(self) -> str: | |
| hmac = "".join(random.choices(HMAC_CHARS, k=84)) | |
| return f"mfa.{hmac}" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment