Browse Source

feat(api): switch to 164 bit tokens with base58 encoding

Peter Thomassen 2 years ago
parent
commit
0aed954842
2 changed files with 32 additions and 12 deletions
  1. 7 1
      api/desecapi/models/tokens.py
  2. 25 11
      docs/auth/tokens.rst

+ 7 - 1
api/desecapi/models/tokens.py

@@ -18,6 +18,11 @@ from django_prometheus.models import ExportModelOperationsMixin
 from netfields import CidrAddressField, NetManager
 
 
+# No 0OIl characters, non-alphanumeric only (select by double-click no line-break)
+# https://github.com/bitcoin/bitcoin/blob/master/src/base58.h
+ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
+
+
 class Token(ExportModelOperationsMixin("Token"), rest_framework.authtoken.models.Token):
     @staticmethod
     def _allowed_subnets_default():
@@ -73,7 +78,8 @@ class Token(ExportModelOperationsMixin("Token"), rest_framework.authtoken.models
         return True
 
     def generate_key(self):
-        self.plain = secrets.token_urlsafe(21)
+        # Entropy: len(ALPHABET) == 58, log_2(58) * 28 = 164.02
+        self.plain = "".join(secrets.choice(ALPHABET) for _ in range(28))
         self.key = Token.make_hash(self.plain)
         return self.key
 

+ 25 - 11
docs/auth/tokens.rst

@@ -396,14 +396,28 @@ Security Considerations
 This section is for purely informational. Token length and encoding may change
 in the future.
 
-Any token secret is generated from 168 bits of randomness at the server and stored in
-hashed format (PBKDF2-HMAC-SHA256). Guessing the secret correctly or reversing
-the hash is hence practically impossible.
-
-The token's secret value is represented by 28 characters using a URL-safe variant of
-base64 encoding. It comprises only the characters ``A-Z``, ``a-z``, ``0-9``, ``-``,
-and ``_``. (Base64 padding is not needed as the string length is a multiple of 4.)
-
-Old versions of the API encoded 20-byte token secrets in 40 characters with hexadecimal
-representation. Such tokens are not issued anymore, but remain valid until
-invalidated by the user.
+Any token secret is generated from 164 bits of randomness at the server and
+stored in hashed format (PBKDF2-HMAC-SHA256).
+Guessing the secret correctly or reversing the hash is considered practically
+impossible.
+
+The token's secret value is represented by 28 characters using a URL-safe
+base58 encoding.
+It is based on a case-sensitive alphanumeric alphabet excluding the characters
+``lIO0`` (hence comprising only the symbols ``a-k``, ``m-z``, ``A-H``,
+``J-N``, ``P-Z``, and ``1-9``).
+This encoding is optimized for maximum clarity and usability:
+Exclusion of certain letters minimizes visual ambiguity, while the restriction
+to alphanumeric symbols allows easy selection (double-click) and input, and
+helps avoid line breaks during display.
+
+Before December 2022, tokens encoded at 21-byte secret using 28 characters in
+a URL-safe variant of base64 encoding, comprising only of the characters
+``A-Z``, ``a-z``, ``0-9``, ``-``, and ``_``.
+(Base64 padding was not needed as the string length is a multiple of 4.)
+
+Before September 2018, tokens encoded a 20-byte secret using 40 hexadecimal
+characters.
+
+Legacy tokens are not issued anymore, but remain valid until invalidated by
+the user.