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.
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. |
Both are {uid}-{kek}
.
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')
The server:
- Generates a unique crypto safe encryption key and a unique kek.
- Encrypts an empty (0 bytes long) binary with an encryption key.
- Inserts that binary into the
users
table getting theid
of the created row. That id isuid
. - Sets
sid = 0
. This is going to be a login token. - Encrypts the encryption key using kek. Uid and sid should be used in the AAD.
- Inserts a new row in the
sessions
table. - Genrateds the login token using kek, uid and sid and passes it to the user (email or HTML response).
The server:
- Ensures the sid equals
0
. - Get a row from
sessions.session
where the uid matches and sid=0. - Tries to get an encryption key: decrypt the session data using the kek, sid and uid from the token. If it's unsuccessful - abort.
- Tries to decrypt the
users.data
using the encryption key. If it's unsuccessful - abort. The decrypted data is unused. - Picks up a sid that was never yet used for this user. Generates a new kek.
- Ussues an auth token using kek, uid and sid.
- Sets that auth token as a HTTP-only cookie. Returns
sid
so it's stored in thelocalStorage
The server:
- Reads the cookie value and the
sid
GET/POST-parameter. - Get a row from
sessions.session
where the uid and sid matches. - Tries to get an encryption key: decrypt the session data using the kek, sid and uid from the token. If it's unsuccessful - abort.
- Tries to decrypt the
users.data
using the encryption key. If it's unsuccessful - abort. - Does whatever it need with the plaintext data and the encryption data.
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.
- 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.