Design Document
Baicheng Chen, Jonathan Lin Project Specs
Block Diagram

Data Structures
Keystore
For each user:
uuid: PKEEncKey // generate uuid with 'username||pke_ek'
uuid: DSVerifyKey // generate uuid with 'username||ds_vk'
Datastore
// Encapsulated the structs below for Authenticated Encryption
type AEPayload struct {
Data []byte // could be encrypted or not
Tag []byte // either sig or hmac
Tag_type string // either 'sig' or 'hmac'
}
// X Confidentiality
// O Integrity / Authentication: DSSign() - ds_sk
type Auth struct {
Username string
Password_H [16]byte // hash(argon(password))
Salt_pwd [16]byte // argon salt for password
Salt_mk [16]byte // argon salt for master key
}
// O Confidentiality: SymEnc() - sym_ek
// O Integrity / Authentication: HMACEval() - sym_hk
type UserInfo struct {
Files_id uuid.UUID // -> UserFiles
PKE_ek PKEDecKey
DS_sk DSSignKey
}
// O Confidentiality: SymEnc() - sym_ek
// O Integrity / Authentication: HMACEval() - sym_hk
type UserFiles map[string]uuid.UUID {
Filename1: User_file_id, // -> UserFile
Filename2: UserFile,
...
}
// O Confidentiality: SymEnc() - sym_ek
// O Integrity / Authentication: HMACEval() - sym_hk
type UserFile struct {
Owner uuid.UUID // For verifying integrity of FileCreds
File_creds_id uuid.UUID // -> FileCreds
}
// X Confidentiality:
// O Integrity / Authentication: DSSign() - ds_sk:owner/sender/self
type FileCreds struct {
File_id uuid.UUID
Esk_sym_ek_file [16]bytes
Esk_sym_hk_file [16]bytes
Shared_users uuid.UUID // -> SharedUsers
}
// O Confidentiality: Hybrid: SymEnc() - sym_ek_filexxx -> PKEEnc() - pke_ek
// O Integrity / Authentication: HMACEval() - sym_hk_filexxx
type SharedUsers map[string]uuid.UUID {
Username1: File_creds1, // -> FileCreds
Username2: File_creds2,
...
}
// O Confidentiality: Hybrid: SymEnc() - sym_ek_filexxx -> PKEEnc() - pke_ek
// O Integrity / Authentication: HMACEval() - sym_hk_filexxx
type File struct {
Owner uuid.UUID
Last_updated int64 // Update on AppendToFile(), StoreFile(), Revoke()
Content_start uuid.UUID // -> FileContent
Content_end uuid.UUID // -> FileContent
// Nonce []byte // random nonce, update after revocation
}
// O Confidentiality: Hybrid: SymEnc() - sym_ek_filexxx -> PKEEnc() - pke_ek
// O Integrity / Authentication: HMACEval() - sym_hk_filexxx
type FileContent struct{
Content []byte
Next uuid.UUID // -> FileContent
}
// O Confidentiality: RSA: PKEEnc() - PKEEncKey:recipient
// O Integrity / Authentication: DSSign() - ds_sk:sender
type Invitation struct {
Owner uuid.UUID // -> To get ds_vk:owner. Needed in cases of revocation (of third party) before acceptance
File_creds_id uuid.UUID // -> FileCreds
}
Users and User Authentication
User Authentication: How will you authenticate users?
How does EvanBot create a new user? When they try to log in, how will you check if the username and password are correct? How do you ensure that a user cannot login with an incorrect password?
Either InitUser() (signup) or GetUser() (login) is called, providing username and password.
Auth is stored in Datastore with uuid.FromBytes(Hash([]byte(username))[:16]) as UUID
Signup
- Verify that the
AuthUUIDforusernamedoes not exist inDatastore, otherwise abort/error. - Create a new
Authstruct - Compute argon hash of
passwordwith a random salt, store asAuth.Password_Hand salt asAuth.Salt_pwd - Compute second argon hash of
passwordwith a different random salt, store hash[:16]locally asmaster_keyand salt asAuth.Salt_mk. - Use
HashKDF()withmaster_keyto derivesym_ek,sym_hkand store locally. - Create a
UserInfostruct - Generate
(pke_ek, pke_dk),(ds_sk, ds_vk)withuserlibfunctions and upload public keys toKeystore, store private keys inUserInfo. - Create an empty
UserFiles(encrypted, hmac-ed) and upload toDatastore, store itsUUIDtoUserInfo.Files_id - Upload
Auth(un-encrypted, signed) andUserInfo(encrypted, hmac-ed) toDatastore.
Login
- Use the
usernamederivedUUIDto retrieveAuthfromDatastore. Verify signature withds_vkfromKeystore. Abort/error if integrity compromised orUUIDdoesn’t exist. - Compute argon hash of
passwordwith saltAuth.Salt_pwdand verify it matchesAuth.Password_H, abort/error if false. - Login successful, proceed to further operations (omitted here).
File Operations
File Storage and Retrieval: How does a user store and retrieve files?
EvanBot is logged in and stores a file. How does the file get stored in Datastore? What key(s) and UUIDs do you use? How do you access the file at a later time?
Storing files
StoreFile(filename, content) is called.
- Check if
filenameexists inUserFiles.
If exists: follow procedure for accessing files to step 3, then skip to step 5 of storing files . Else continue:
- Create new structs
UserFile,FileCreds,File,FileContent, and generate randomUUIDs for them withuuid.New(). UserFile: SetOwnertoAuthUUIDof self,File_creds_idto correctUUID.FileCreds: SetFile_idto correctUUID. Generate random symmetric keyssym_ek_file,sym_hk_fileand encrypt them withpke_ek, then store toEsk_sym_ek_file,Esk_sym_hk_file.FileContent: SetContentto file’s content,Nextto randomly generatedUUID.File: SetOwnertoAuthUUIDof owner,Last_updatedto current Unix time in milliseconds,Content_starttoUUIDofFileContentandContent_endtoFileContent.Next- Set
UserFiles[filename]toUUIDofUserFile - Hybrid encrypt
File,FileContentwith (sym_ek_file,pke_ek) and hmac withsym_hk_file. Upload the two structs - Encrypt
UserFilewithsym_ekand hmac withsym_hk. Upload the struct. - Sign
FileCredswithds_skand upload the struct.
Accessing files
- Retrieve
UserFilefrom itsUUIDinUserFiles[filename]. Abort/Error if not found. - Retrieve
FileCredsand verify its signature (has to matchds_skof owner/sender/self). Abort/error if integrity compromised. - Decrypt
Esk_sym_ek_file,Esk_sym_hk_filewithpke_dkto obtainsym_ek_file,sym_hk_file - Retrive
File, verify hmac withsym_hk_fileand decrypt withsym_ek_file - Retrive
Content_startand verify hmac + hybrid decrypt in the same way, and repeat for allNextFileContents to build the whole file.
Efficient Append: What is the total bandwidth used in a call to append?
List out every piece of data that you need to upload (DatastoreSet) or download (DatastoreGet) from Datastore in a call to append, and the size of each piece of data. Is the total a constant, or does it scale?
A call of User.AppendToFile(filename string, content []byte) entails:
- Download
UserFile,FileCreds,File(steps for accessing files) - Create new
FileContent - Write to
FileContent.Contentand store new randomUUIDFileContent.Next - Update
File.Last_updatedand setFile.Content_endto the newFileContent.Next - Upload
FileContenttoFile.Content_endand uploadFile
Examining the UserFile, FileCreds, File, FileContent sructs, the bandwidth only scales with content size and has constant overhead.
How will you ensure that multiple User objects for the same user always see the latest changes reflected?
EvanBot logs in on their laptop and phone, creating two User objects. If EvanBot makes a change on their laptop (e.g. storing a file), how do you ensure that EvanBot will see the change reflected on their phone?
Store a timestamp Last_retrieved (at local) of the time that EvaBot retrieved the file. Then during any read/write operations, compare it to File.Last_updated on Datastore. Re-load file when Last_retrieved is older.
Sharing and Revocation
File Sharing: What gets created on CreateInvitation, and what changes on AcceptInvitation?
CodaBot (not the file owner) wants to share the file with PintoBot. What is the sharing process like when a non-owner shares? (Same questions as above; your answers might be the same or different depending on your design.)
CreateInvitation
CreateInvitation(filename, recipientUsername):
- Create a new
Invitationand newFileCredswith randomUUIDs. - Encrypt
sym_ek_fileandsym_hk_filewith the recipient’spke_ek, store inFileCreds - Store the
File_idpointing toFile - Store the new
FileCredsand OwnerUUIDinInvitation - Encrypt
Invitationwith the recipient’spke_ekand sign bothInvitation,FileCredswith sender’sds_sk(UUIDderived fromrecipientUsername). Upload the two structs. - In the sender’s own
UserFiles[filename]->UserFile->FileCreds.Shared_users(create if nil), add theUUIDof the newFileCredstoSharedUsers[recipient name]. - Encrypt and hmac
SharedUsersthe same way as when storing files then upload.
AcceptInvitation
AcceptInvitation(senderUsername, invitationPtr, filename):
- Retrieve the
InvitationatinvitationPtr, verify signature withds_skof sender (UUIDderived fromsenderUsername). Abort/error if integrity compromised. - Decrypt with recipient’s own
pke_dk. - Create a new
UserFilewith randomUUID, populate with contents ofInvitationand create corresponding entry inUserFiles
Sharing proccess of non-owner
Exactly the same as how the owner does the CreateInvitation process.
File Revocation: What values need to be updated when revoking?
How do you ensure that a Revoked User Adversary cannot read or modify the file without being detected, even if they can directly access Datastore and remember values computed earlier? How do you ensure that a Revoked User Adversary cannot learn about when future updates are happening?
A file owner calling RevokeAccess(filename string, recipientUsername string) entails:
- Access the file
- Generate new
sym_ek_file,sym_hk_filekeys - Re-encrypt the file in the same way as “storing files”
- Move
Fileto a new randomUUIDand delete the old location. Also updateFile.Last_updated. - Traverse
FileCreds->Shared_usersto find therecipientUsername. Traverse from that node recursively to delete the node and all childrenFileCredsnodes fromDatastore. - Update all the other remaining
FileCredsnodes (including owner’s) with the new keys (encrypted with respectivepke_ek) and newUUIDofFile
For example, when UserA calls RevokeAccess(file, UserC), they will traverse the tree to delete FileCreds, SharedUsers of users C, E, F and update FileCreds for users B, D, G
Helper Functions
Helper functions: As you come up with a design, think about any helper functions you might write in addition to the cryptographic functions included here.
Having helper functions can simplify your code. Consider authenticated encryption, hybrid encryption, etc.
Hybrid Encryption
func HybridEnc(ek userlib.PKEEncKey, key []byte, iv []byte, plaintext []byte) (esk []byte, ciphertext []byte, err error)
- Encrypt plaintext using the symmetric key
- Encrypt the symmetric key using the provided public key
- Return the encrypted symmetric key and the ciphertext
func HybridDec(dk userlib.PKEDecKey, esk []byte, ciphertext []byte) (plaintext []byte, err error)
- Decrypt the symmetric key using the provided private key
- Decrypt the ciphertext using the decrypted symmetric key
- Return the decrypted plaintext
Encrypt-then-MAC (Authenticated Encryption)
func EncThenMac(plaintext []byte, ek []byte, hk byte[]) (payload []byte, err error)
- Encrypt the plaintext with key
ek - Compute
HMACEval()on the ciphertext with keyhk - Return hmac joined with ciphertext as
payload