Skip to content

Instantly share code, notes, and snippets.

@subzey

subzey/readme.md Secret

Last active February 6, 2021 00:44
Show Gist options
  • Save subzey/dd2a68c1a88b87c3ee7e160da6b91148 to your computer and use it in GitHub Desktop.
Save subzey/dd2a68c1a88b87c3ee7e160da6b91148 to your computer and use it in GitHub Desktop.
An alternative auth scheme

An alternative auth scheme

Glossary

Encryption key. A symmetric cryptography key to encrypt the data. E.g. AES-128

Key Encryption Key, KeK. A symmetric cryptography key used to encrypt some other key.

Auth token. The string that is used to indentify and authenticate user and its session. E.g. a cookie. The auth token conot be used for login.

Login token. The string that is used to log in a user and give them an auth token. The login token cannot be used as an auth token.

User identifier, Uid. An integer value that identifies the user.

Session identifier, Sid.

DB layout

users

column type index desc
id uint64 🔑 Primary key User id
data blob Binary, some arbitray data encrypted with an encryption key

sessions

column type index desc
id 🤷 whatever 🔑 Primary key, surrogate A surrogate key
uid uint64 🔑 Foreign key users.id, 🗃️ Composite unique uid & sid User id
sid uint32 🗃️ Composite unique uid & sid Session id
session blob Binary, an encryption key encrypted with kek. Maybe something else.

Auth and login token layouts

Both are {uid}-{kek}.

Key encryption key and sessions

The data in the database is stored with encryption. The encryption key is picked once for user dureing the login process, but it's never stored as plaintext.

Each user can have several sessions. Each session has its own KeK, so invalidating one token won't spoil the other sessions. The KeK is used to retrieve the encryption key and ulimately encrypt and decrypt the data.

The session data (session.session) containing encryption key should be encrypted with a symmetric AEAD cipher, like AES-128-GCM, so it accepts the additional authenticated data ("AAD").

The user id and session id should be in the AAD, so the token cannot be forged: changing any of uid, or key would render the KeK invalid.

The sid parameter should be send separately via JS code to avoid XSS. It acts as a CSRF token.

Ex.:

session_data = aes128gcm.encrypt(plainext=encryption_key, key=kek, aad=uid + '-' + 'sid')
encryption_key = aes128gcm.decrypt(ciphertext=session_data, key=kek, aad=uid + '-' + 'sid')

Signing up

The server:

  1. Generates a unique crypto safe encryption key and a unique kek.
  2. Encrypts an empty (0 bytes long) binary with an encryption key.
  3. Inserts that binary into the users table getting the id of the created row. That id is uid.
  4. Sets sid = 0. This is going to be a login token.
  5. Encrypts the encryption key using kek. Uid and sid should be used in the AAD.
  6. Inserts a new row in the sessions table.
  7. Genrateds the login token using kek, uid and sid and passes it to the user (email or HTML response).

Log in

The server:

  1. Ensures the sid equals 0.
  2. Get a row from sessions.session where the uid matches and sid=0.
  3. Tries to get an encryption key: decrypt the session data using the kek, sid and uid from the token. If it's unsuccessful - abort.
  4. Tries to decrypt the users.data using the encryption key. If it's unsuccessful - abort. The decrypted data is unused.
  5. Picks up a sid that was never yet used for this user. Generates a new kek.
  6. Ussues an auth token using kek, uid and sid.
  7. Sets that auth token as a HTTP-only cookie. Returns sid so it's stored in the localStorage

Auth

The server:

  1. Reads the cookie value and the sid GET/POST-parameter.
  2. Get a row from sessions.session where the uid and sid matches.
  3. Tries to get an encryption key: decrypt the session data using the kek, sid and uid from the token. If it's unsuccessful - abort.
  4. Tries to decrypt the users.data using the encryption key. If it's unsuccessful - abort.
  5. Does whatever it need with the plaintext data and the encryption data.

Token reset

First, the server should Auth the user. Then server essentially just repeats the login process. Except it doesn't generate a new uid.

All old auth tokens becomes useless.


Possible Attack Vectors (mostly copy-paste)

  • User uses one password on all websites, and it leaked. This attack does not work on our method, as we generate the key ourselves.
  • Our servers were hacked. The encryption key is not stored as a plaintext, it needs a token only a user owns.
  • XSS attack on the web client. JS won’t have access to the auth token. CSRF cannot be made without getting the sid from the localStorage.
  • MitM attack between client and server. Just use HTTPS
  • Trojan on the client machine. Here we can only hope on apps isolation in the OS, but the full key is likely to be leaked.
  • The user’s device is stolen. The encryption key can be changed effectively dropping the sessions and the old login token. A user can terminate all sessions on other devices without changing the encryption key (full reset).
  • A family member gets access while the user is sleeping. There's no way to reuse the session. Each new session gets a new record in a DB so won't be unnoticed
  • Malware code on the server, which sends emails with the key. We assume that the “no password manager” way has some compromises.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment