-
Notifications
You must be signed in to change notification settings - Fork 278
How does an encrypted library work?
- When you create an encrypted library, you'll provide a password for it. All the data in that library will be encrypted with the password before uploading to the server.
- The file encryption/decryption is performed in the client-side.
- Pay very much attention to key derivation algorithm and data encryption algorithm, they are a lot different though they were discussed together. Key derivation algorithm is
PBKDF2WithHmacSHA256
and data encryption algorithm isAES 256/CBC
(PKCS7Padding). - Unfortunately, Android SDK supports neither
PBKDF2
norAES 256/CBC
. So we use a third party library Bouncy Castle. - Bouncy Castle doesn't support PBKDF2WithHmacSHA256 by default, so use PKCS5S2ParametersGenerator with SHA256Digest instead. That depends on Bouncy Castle jars, which could be downloaded here.
- But this patch uses gradle dependencies, as you can find it in the build.gradle.
- If you use version 1.47 or higher of SpongyCastle, you can invoke PBKDF2WithHmacSHA256 directly. In versions of BC < 1.47, you could not specify SHA256 digest and it defaulted to SHA1.
As background knowledge
- When you create the library, a "magic token" is derived from the password and library id.
- This token is stored with the library on the server side.
- The magic token is generated by PBKDF2 algorithm with 1000 iterations of SHA256 hash.
- Generate a 32-byte long cryptographically strong random number. This will be used as the file encryption key ("file key").
- Encrypt the file key with the user provided password. We first use PBKDF2 algorithm (1000 iterations of SHA256) to derive a key/iv pair from the password, then use AES 256/CBC to encrypt the file key. The result is called the "encrypted file key". This encrypted file key will be stored on the server. When you need to access the data, you can decrypt the file key from the encrypted file key.
- All file data is encrypted by the file key with AES 256/CBC. We use PBKDF2 algorithm (1000 iterations of SHA256) to derive key/iv pair from the file key. After encryption, the data is uploaded to the server.
- The flexibility to use password to encrypt real secret key rather than encrypt data is you can change password later. When you change password, decrypt secret key from the old password, then encrypt by the new password, magic token should be also calculated.
- Internally " encrypted file key" is also called "random key".*
- When you view an encrypted library, the client needs to verify your password.
- The client use "magic token" to check whether your password is correct before you sync the library.
- Client side uses the same algorithm to compute local magic token, then compare with the one within the repo.
- If equals, client decrypt the "file key" from "random key" and derive "encKey" and "encIv" from the "file key". To avoid decrypt "random key" repeatedly, client should persist the "encKey" and "encIv" for each repo for later use.
- Persist these "encKey"s and "encIv"s in local storage.
Client side decryption simply reverses the encryption procedure on server side
- Get the encKey and encIv from local storage, no expired time restriction.
- file data is encrypted/decrypted by the encKey and encIv with AES/CBC/PKCS7Padding
Firstly, chunk file into 2M blocks. Blockid is sha1 of the block content. Internally file blocks (chunks) were managed by FileBlocks.
Then get file server link from seahub
Seahub server URL: /api2/repos/<repo_id>/upload-blks-link/
Then upload all blocks to file server at the same time.
File server URL: http://server-address:8082/upload-blks-api/<token>
Parameters to upload blocks:
- parent_dir: parent directory path
- file_name: file name
- file_size: file size in bytes
- replace: whether overwrite file with the same name
The client sends the blocks with a POST request to the file server. The content of the request is in multipart form-data format.
------SeafileAndroidBound$_$
Content-Disposition: form-data; name="parent_dir"
/
------SeafileAndroidBound$_$
Content-Disposition: form-data; name="file_name"
IMG_20160114_163406.jpg
------SeafileAndroidBound$_$
Content-Disposition: form-data; name="file_size"
2262770
------SeafileAndroidBound$_$--
response: {
"id": fileid
}
Getting block list of a file from Seahub:
url: /api2/repos/<repo_id>/file/?p=<filepath>&op=downloadblks
The response contains the file_id, block id list. Then the client downloads each block separately from file server.
First get file server link for downloading a block:
url: /api2/repos/<repo_id>/files/<file_id>/blks/<blk_id>/download-link/
Then download the block from file server Parameters to generate token:
- op: 'downloadblks'
- obj_id: SHA1 id of the file object
- use_onetime: True
File server URL: http://server-address:8082/blks/<token>/<block_id>
Then assemble all blocks into the file in order.
exactly the same with file uploading
related to file_id