Doctor Dok is an AI based medical data framework and patient's med vault. Parse any health related PDF/Image to JSON and then use Chat GPT / LLama to discuss it! WARNING: Don't decide on your health based on AI Chat - it's just for Research purposes.
Database ID - unique name of the current database used by the application; this could be for example Personal ID / Social Security number / PESEL (PL) or any other unique value - never sent plain to the server (only hash)
User Key - encryption key, unique, not stored on the server - used to access users's Master Key which is used to encrypt/decrypt data; data is only encrypted and decrypted Client Side thus we're in Zero Trust security
Master Key - encrypted using AES-GCM (encryption key = User Key)
Sharing Key - generated by user 6 digits (or more) key used to decrypt Master Key - but only for sharing purposes, subject to expiration date (for example valid just for 30min or one day)
We're using double leveled structure (User Key / Sharing Key) -> (Master Key) for letting the Users to manage/change Sharing and User Keys without the need to re-encrypt whole database.
Database Structure:
Table: keys - used for storing User Keys and Sharing Keys - therefore for API access authorization + for providing the client with encrypted Master Key for being used for data encryption and decryption:
databaseIdHash: Unique SHA-256 hash of the Database ID.
keyLocatorHash: Unique hash of the User Key or Sharing Key + Database Id using SHA256, unique within same Database ID. Hash is generated client side
keyHashParams: parameters - including salt - used for hashing keyHash; provided by client with generated hash; it's a JSON of:
{ "salt": "salt",
"time": 1, // the number of iterations
"mem": 1024, // used memory, in KiB
"hashLen": 24, // desired hash length
"parallelism": 1, // desired parallelism (it won't be computed in parallel, however)
}
keyHash: Unique hash of the User Key or Sharing Key using Argon2id, unique within same Database ID. Hash is generated client side
encryptedMasterKey: The master key encrypted using AES-GCM, secured with a User Key (which the server never receives in plain text). Master Key MUST be generated client side, encrypted and sent in encrypted form to the server.
expiryDate: Other fields may include expiration date and additional data.
Each databaseIdHash is associated with a at least single User Key and can have multiple Sharing Keys. Each Sharing Key record holds an encrypted copy of the Master Key.
Zero Trust
Because we never store plain User Keys, Shared Keys neither Master Keys - we're in Zero Trust security. The hashes (being sent to server) and the data encryption/decryption (done only on client) are only possible with given and stored inside browser's memory User Key and Master Keys.
UC01: Creating a New User Key or Database:
User provides us with:
Database Id - must be unique for the entire app as it's translated to database file name,
User Key / Password - automatically generated, subject to user change key.
Endpoint: /db/create
Data sent to server:
databaseIdHash: sha256(Database Id)
keyLocatorHash: sha256(User Key + Database Id + static salt)
keyHash: argon2Id(User Key)
encryptedMasterKey - encrypted by User Key key used to decrypt and encrypt users data
keyHashParams: hash params used for arg2id in JSON format:
{ "salt": "salt",
"time": 1, // the number of iterations
"mem": 1024, // used memory, in KiB
"hashLen": 24, // desired hash length
"parallelism": 1, // desired parallelism (it won't be computed in parallel, however)
}
masterDataKey - AES encrypted randomly generated key used for encrypting all subsequent data - key to encrypt this master key is always plain User Key or Sharing key which is never sent back to the server.
In case this is User Key - the first key in the database we are also generating and encrypting with AES-GCM the masterDataKey. If this is another key request - we're just sending the existing key along.
Hashes are calculated Client Side. No plain data sent to server.
For every API request we will set the Authorization header contains base64(JWT Access Token). The server verifies this as described below later in this text.
Server side:
Server creates new keys record storing:
databaseIdHash as sent from the client
keyLocatorHash - sha256 record locator with static salt,
keyHash - argon2Id hash - calculated client side and here stored for further usage,
keyHashParams - argon2Id params - used for further hashing.
UC02: Log In
The user inputs their Database Id.
The user inputs their User Key or Sharing Key.
Application is requesting /db/authorize-challenge with posting:
databaseIdHash: sha256(Database Id)
keyLocatorHash: sha256(User Key + Database Id + static salt) - this is just the record locator
Server sents back keyHashParams associated with the databasIdHash and keyLocatorHash' used as record locators insidekeys` table
Client calculates argon2id hash for given keyHashParams and User Key and sends it to the server as keyHash with a request to /db/authorize
Server checks if argon hash associated with the key located by keylocatorHash and databaseIdHash matches keyHash.
Note: After succesfull login, server responds with JWT Access Token and JWT Refresh Token. These details are stored in the browser's memory and sent with each API request.
In case of succesfull login, The browser receives an AES-encrypted Master Key for the data, which is decrypted only in the browser using the User Key or Sharing Key stored in memory browser and unavailable to the server.
Data is encrypted using AES-GCM with this master key.
Authorization algorithm (on the server):
Get the keys records (one or many) where databaseIdHash = + databaseIdHash + and keyLocatorHash = + keyLocatorHash (sent from client)
Iterate over the keys assigned to specific databaseHash and check if argon2id.check(keyHash, serverStoredKey.keyHash) are matched.
If so, send the encryptedMasterKey + JWT token to the client.
UC03: API Request Handling by the Server
Server verifies JWT Access token sent in the x-access-token header.
The server searches for the key in the database using the keyHash (hashed Database ID).
keyHash and databaseIdHash are stored within JWT Token described above.
Alternatively:
Server retrieves all keys for the tenant (there can be multiple if shared, or one if not). It iterates through and checks argon2id (of the submitted client key hash). If matched, the user is authorized
Client-Side Encryption and Decryption:
All data encryption and decryption are handled on the client side, ensuring data security.
The server cannot decrypt client data as it never receives the user's key—only the SHA-256 hash of the key, which is necessary to decrypt the master key used for data encryption.
Terms:
We're using double leveled structure (User Key / Sharing Key) -> (Master Key) for letting the Users to manage/change Sharing and User Keys without the need to re-encrypt whole database.
Database Structure:
databaseIdHash
: Unique SHA-256 hash of the Database ID.keyLocatorHash
: Unique hash of the User Key or Sharing Key + Database Id using SHA256, unique within same Database ID. Hash is generated client sidekeyHashParams
: parameters - including salt - used for hashingkeyHash
; provided by client with generated hash; it's a JSON of:keyHash
: Unique hash of the User Key or Sharing Key using Argon2id, unique within same Database ID. Hash is generated client sideencryptedMasterKey
: The master key encrypted using AES-GCM, secured with a User Key (which the server never receives in plain text). Master Key MUST be generated client side, encrypted and sent in encrypted form to the server.expiryDate
: Other fields may include expiration date and additional data.Each
databaseIdHash
is associated with a at least single User Key and can have multiple Sharing Keys. Each Sharing Key record holds an encrypted copy of the Master Key.Zero Trust Because we never store plain User Keys, Shared Keys neither Master Keys - we're in Zero Trust security. The hashes (being sent to server) and the data encryption/decryption (done only on client) are only possible with given and stored inside browser's memory User Key and Master Keys.
UC01: Creating a New User Key or Database:
User provides us with:
Endpoint:
/db/create
Data sent to server:
databaseIdHash
:sha256(Database Id)
keyLocatorHash
:sha256(User Key + Database Id + static salt)
keyHash
:argon2Id(User Key)
encryptedMasterKey
- encrypted by User Key key used to decrypt and encrypt users datakeyHashParams
: hash params used for arg2id in JSON format:masterDataKey
- AES encrypted randomly generated key used for encrypting all subsequent data - key to encrypt this master key is always plain User Key or Sharing key which is never sent back to the server.In case this is User Key - the first key in the database we are also generating and encrypting with
AES-GCM
themasterDataKey
. If this is another key request - we're just sending the existing key along.Hashes are calculated Client Side. No plain data sent to server.
For every API request we will set the
Authorization
header containsbase64(JWT Access Token)
. The server verifies this as described below later in this text.Server side:
keys
record storing:databaseIdHash
as sent from the clientkeyLocatorHash
-sha256
record locator with static salt,keyHash
-argon2Id
hash - calculated client side and here stored for further usage,keyHashParams
-argon2Id
params - used for further hashing.UC02: Log In
Database Id
.User Key
orSharing Key
./db/authorize-challenge
with posting:databaseIdHash
:sha256(Database Id)
keyLocatorHash
:sha256(User Key + Database Id + static salt)
- this is just the record locatorkeyHashParams
associated with thedatabasIdHash
andkeyLocatorHash' used as record locators inside
keys` tableargon2id
hash for givenkeyHashParams
andUser Key
and sends it to the server askeyHash
with a request to/db/authorize
keylocatorHash
anddatabaseIdHash
matcheskeyHash
.JWT Access Token
andJWT Refresh Token
. These details are stored in the browser's memory and sent with each API request.Master Key
for the data, which is decrypted only in the browser using theUser Key
orSharing Key
stored in memory browser and unavailable to the server.Authorization algorithm (on the server):
keys
records (one or many)where databaseIdHash =
+databaseIdHash
+and keyLocatorHash =
+keyLocatorHash
(sent from client)keys
assigned to specific databaseHash and check ifargon2id.check(keyHash, serverStoredKey.keyHash)
are matched.encryptedMasterKey
+ JWT token to the client.UC03: API Request Handling by the Server
x-access-token
header.keyHash
(hashed Database ID).keyHash
anddatabaseIdHash
are stored within JWT Token described above.Alternatively:
argon2id
(of the submitted client key hash). If matched, the user is authorizedClient-Side Encryption and Decryption: All data encryption and decryption are handled on the client side, ensuring data security.
The server cannot decrypt client data as it never receives the user's key—only the SHA-256 hash of the key, which is necessary to decrypt the master key used for data encryption.