Backend without traditional Authentication mechanism, use cryptography instead

46 views Asked by At

Context: I have a mobile app, and I don't have, and will not, implement a traditional authentication flow (email/pw, social login). Instead, I would like to leverage asymmetric key encryption.

Requirements: For the sake of simplicity, let's imagine a simple backend with one User model, which only has one field: favorite_number.

  • a user can CRUD a User model on the backend.
  • a user cannot CRUD another user's User model.

Potential solution:

  • Alice generates a private/public key pair on her mobile device. She keeps the private key securely on-device.
  • Alice makes a network request (let's say a RESTful POST request) to the backend, with the following payload:
{
  "public_key": {Alice's public key},
  "favorite_number": 42,
  "signature": sign_with_private_key(42)
}

where sign_with_private_key(42) is the signature of the message 42, signed with Alice's private key.

  • The backend receives the above payload, verifies that the signature matches the public key and the favorite_number, and saves the following information in its DB (assume SQL below):
| public_key | favorite_number | signature |
| ---------- | --------------- | --------- |
| 0x...      | 42              | 0x..      |
  • Alice wants to read her favorite number:
    • Alice sends GET /api/get_nonce?public_key={Alice's public key} to the backend
    • The backend looks in the DB the row with Alice's public key, and returns its signature column ONLY
    • Alice decrypts the signature with her local private key, it returns 42.
  • Bob wants to read Alice's favorite number, he calls the same endpoint GET /api/get_nonce?public_key={Alice's public key}, get's Alice's favorite_number signature, but cannot decrypt it.
  • Bob wants to modify Alice's favorite number, but cannot, because he can't compute the signature of the favorite_number message.

Caveats (which I can live with):

  • If Alice loses her phone or uninstalls the app, all her account information on the backend will be lost.

Additional assumptions:

  • The DB cannot be directly accessed by other party than the backend (and DB administrator) itself. (clarification request by @kelalaka). But that's independent of authentication.

Question: Is this authentication scheme viable? Do you see any big security loopholes?

1

There are 1 answers

6
Rob Napier On BEST ANSWER

This feels a bit over-complicated. If Alice has a keypair, then Alice can simply sign requests, and the signature is the authentication. There's no particular reason to sign individual pieces of data. Just sign the requests themselves. For example:

{
  "request": {"message_id":123,"public_key":"...","favorite_number":42},
  "signature": signature_of('{"message_id":123,"public_key":"...","favorite_number":42}')
}

It is critical that you sign everything that is part of the request.

Note that requests need to be single-use, otherwise this is not secure. By single-use, I mean that a timestamp should be in the request and the same request should not be useable more than once. You can also use a message counter (especially since you only have one device that can connect). So the server always rejects messages ids equal to or smaller than the last id sent for this user.

Your approach is vulnerable to reuse of the value upon another key. For example, I could reuse "42" and assign it to some other thing like "hated_number." Or I could replay this message and reset Alice's favorite number to 42 after she has changed it to something else. Signing the entire request is a much better approach and avoids a number of these problems (as long as a request can't be reused).

If Alice wishes to protect the data from the administrator, then she should encrypt the data with a symmetric key, but that's independent of authentication.

A simpler way to implement this, provided the transport is trusted, is to let Alice generate a random 256-bit identifier, and simply use that as the authentication. A 256-bit identifier will always be sufficiently sparse that it is unguessable (guessing the identifier is precisely the same as guessing an AES-256 key). With that, simply knowing the identifier is sufficient to authenticate a request. This only works if the transport is trusted, but that's the same as any static credential (username+password, token, etc). By a trusted transport, I mean HTTPS with pinned certs, for example, or any similarly encrypted and authenticated transport.