Skip to content

Instantly share code, notes, and snippets.

@ddjerqq
Created September 11, 2022 21:28
Show Gist options
  • Select an option

  • Save ddjerqq/063421924558048fa9a804cdd719ed7c to your computer and use it in GitHub Desktop.

Select an option

Save ddjerqq/063421924558048fa9a804cdd719ed7c to your computer and use it in GitHub Desktop.
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