hushfile is a file sharing service with clientside encryption. Idea and initial code by Thomas Steen Rasmussen / Tykling, 2013.
This code is released under 2-clause BSD license, so you are free to use this code to run your own hushfile service, modify the code, or whatever you like.
The hushfile server is pretty simple, most of the hard work is done by the clients. The server basically takes requests and replies with HTTP status codes and json blobs. The server has an upload function which returns a fileid, the rest of the operations require an existing fileid.
The following describes the client workflow. The details are probably only relevant if you are curious or if you are implementing a new client.
A client has two basic jobs: uploading and downloading. This section describes how a client operates, based on the hushfile/hushfile-web client. If you are implementing a new client read the API file for details.
Initialize client by calling the serverinfo API function to display the servers max file size (if any) and max retention time (if any) to the user.
A file is selected by the user.
The client must check the file size against the maxsize value returned by the serverinfo API function, and display an error if it is too large.
The client should decide whether or not to use chunking, and a chunksize. The serverinfo API function returns a maxchunksize value which should be taken into consideration.
The client must generate an encryption password. If the user wants the file to be deletable a deletepassword should also be generated. It should be possible but difficult for a user to choose custom passwords. Long automatically generated alphanumeric password are the most secure.
The client must generate a json object containing four fields if the file should be deletable:
{"filename": "secretfile.txt", "mimetype": "text/plain", "filesize": "532", "deletepassword": "vK36ocTGHaz8OYcjHX5voD8j3MgsGkg8JAXAefqe"}
If the file should not be deletable the deletepassword
field should be omitted.
The client must encrypt the metadata json with the encryption password.
The client must encrypt the first chunk of the file. If the servers permitted chunksize and the clients desired chunksize are both equal to or larger than than the total filesize there will only be one chunk.
The client must do a HTTP post to https://servername/api/upload
with five fields: cryptofile
, metadata
, deletepassword
, chunknumber
and finishupload
. The number of the first chunk is 0
. The finishupload
field must be set to true if this is the only chunk, and false if there are additional chunks. The metadata must be uploaded with the first chunk.
The client will get a HTTP 200 response with a JSON body which contains six fields:
{"fileid": "51928de7aba77", "status": "OK", "chunks": "1", "totalsize": "12345", "finished": false, "uploadpassword": "abc123DEF456"}
If the upload is finished uploadpassword
will be empty, otherwise it contains the password neccesary to upload new chunks.
If any chunks remain the client must encrypt and upload each one with a HTTP post to https://servername/api/upload
with five fields: fileid
, cryptofile
, chunknumber
, uploadpassword
and finishupload
. This can be done in parallel if desired.
When the last chunk is uploaded the finishupload
field should be set to true, or the upload can be marked as finished by using the seperate finishupload
API call.
For each chunk uploaded successfully the client will get a HTTP 200 response with a JSON body like this:
{"fileid": "51928de7aba77", "status": "OK", "chunks": "3", "totalsize": "1234567", "finished": false}
The client must finally present a link to the user, in the format https://servername/fileid#password
A hushfile URL is supplied by the user
The client checks if the fileid is a valid and finished upload on the server, by querying https://servername/api/exists?fileid=51928de7aba77
If the fileid is valid and the upload is finished the server will reply with a HTTP 200 with a JSON body like so: {"fileid": "51928de7aba77", "exists": true, "chunks": 2, "totalsize": 123432, "finished": true}
If the fileid is valid but the upload has not been finished the server will reply with a "HTTP 412 Precondition Failed" with a JSON body like so: {"fileid": "51928de7aba77", "exists": true, "chunks": 2, "totalsize": 123432, "finished": false}
If the fileid is invalid the server will reply with a HTTP 404 with a JSON body like so: {"fileid": "51928de7aba77", "exists": false, "chunks": 0, "totalsize": 0, "finished": false}
If a password was not supplied, the client must ask the user for a password.
The client must then download the metadata from the server by querying https://servername/api/metadata?fileid=51928de7aba77
The client must then decrypt the metadata using the supplied password.
If the data cannot be decrypted or the decrypted data is not valid JSON, the client must display an error to the user and ask for a new password, and try decrypting again.
The decrypted JSON metadata contains four fields and could look like this: {"filename": "secretfile.txt", "mimetype": "text/plain", "filesize": "532", "deletepassword": "vK36ocTGHaz8OYcjHX5voD8j3MgsGkg8JAXAefqe"}
The client must now get the IP address of the uploader by sending a request to https://servername/api/ip?fileid=51928de7aba77
The client must present the metadata and IP to the user, and give the user two options, download and delete.
If the user wants to delete the file, the client can do that by sending a request to https://servername/api/delete?fileid=51928de7aba77&deletepassword=vK36ocTGHaz8OYcjHX5voD8j3MgsGkg8JAXAefqe
.
If the deletion is successful the server responds with a HTTP 200 and a JSON object like this: {"fileid": "51928de7aba77", "deleted": true}
If the deletion fails because the deletepassword is incorrect, the server responds with a HTTP 401 and a JSON object like this: {"fileid": "51928de7aba77", "deleted": false}
If the client wants to download the file, the client can get the data for a given chunk by sending a request to https://servername/api/file?fileid=51928de7aba77&chunknumber=N
- if there is only one chunk the number is 0
The client must then decrypt the file with the same password as the metadata was decrypted with.
Repeat for every chunk, put the chunks together when all of them are downloaded.
Finally the client can prompt the user to save the file somewhere.
The client should still have an active delete button after download, so a user can delete a file after downloading.
The following components are used extensively in the code:
-
http://code.google.com/p/crypto-js/ Crypto-JS is used to perform encryption and decryption.
-
http://twitter.github.io/bootstrap/ Twitter Bootstrap is used for styling
-
http://fortawesome.github.io/Font-Awesome/ Font-Awesome is a great icon collection for use with Twitter Bootstrap