Encrypt data before sending it to server in a way that makes it unreadable to anyone except the user who sent it I don't have any experience in cryptography and I've written some crypto code that seem to be working, but I'm not sure if it safe and if the tools and methods I used are right.

Objective


Make it possible to store data (JSON) on remote server without this server being able to read it in plaintext.


My Approach


Upon registration, don't send the real user password to the server, use Argon2 hash of the real password instead. In my understanding, it should be pretty hard for a server to infer the real password based on Argon2 hash and without salt.
Use sha256(email + password) to create deterministic salt. It allows users to log in from other devices and still pass password validation. In my understanding, deterministic salt doesn't make the resulting "API password" weaker.
Using the password and salt, create an "API password" (Argon2(password, salt))
Use API password to sign up. Server would only know the derived password and not the original one (entered and known by the user).
Create another Argon2 hash and use it as a key to AES-encrypt the JSON payload (AES-256/CBC/Pkcs).
Attach IV and random salt to encrypted payload. All of that is necessary to decrypt the payload later.
Push the resulting data to back end server.


Code

use argon2::{self, Config};
use sha2::{Digest, Sha256};

use crypto::{ symmetriccipher, buffer, aes, blockmodes };
use crypto::buffer::{ ReadBuffer, WriteBuffer, BufferResult };

use rand::{ Rng };

fn main() {
    // Step 1: Generating "password" that will be used for API sign up
    let email = "foo@bar.com";
    println!("Email: {}", email);
    let password = "foobar"; 
    // Never leaves client
    println!("Password: {}", password);
    let mut sha_hasher = Sha256::new();
    sha_hasher.input(format!("{}{}", email, password));
    let salt = sha_hasher.result();
    let argon_config = Config::default();
    let password_hash = argon2::hash_encoded(password.as_bytes(), &amp;salt, &amp;argon_config).unwrap();
    println!("Password hash (full): {}", password_hash);
    let password_hash_parts: Vec&lt;&amp;str&gt; = password_hash.split("$").collect();
    let password_hash = password_hash_parts[5];
    // will be used for API sign up
    println!("Password hash (short): {}", password_hash); 

    // Step 2: Encrypting some JSON client side
    let payload = "[{ name: \"Secret Name\" }]";
    println!("Original payload: {}", payload);
    let encrypted_payload = encrypt_payload(&amp;payload, &amp;password);
    let decrypted_payload = decrypt_payload(encrypted_payload, &amp;password);
    println!("Decrypted payload: {}", decrypted_payload);

    // Step 3: Pushing encrypted payload to server 
    // (omitted, out of scope of review)
}

fn encrypt_payload(payload: &amp;str, password: &amp;str) -&gt; Vec&lt;u8&gt; {
    let salt = rand::thread_rng().gen::&lt;[u8; 16]&gt;();
    let argon_config = Config::default();
    let key = argon2::hash_encoded(password.as_bytes(), &amp;salt, &amp;argon_config).unwrap();
    let iv = rand::thread_rng().gen::&lt;[u8; 16]&gt;();
    let encrypted_bytes = encrypt(payload.as_bytes(), key.as_bytes(), &amp;iv).ok().unwrap();
    let encrypted_bytes : Vec&lt;u8&gt; = salt.iter().chain(&amp;iv).chain(&amp;encrypted_bytes).cloned().collect();
    encrypted_bytes
}

fn decrypt_payload(encrypted_payload: Vec&lt;u8&gt;, password: &amp;str) -&gt; String {
    let salt = &amp;encrypted_payload[0..16];
    let iv = &amp;encrypted_payload[16..32];
    let encrypted_message = &amp;encrypted_payload[32..];
    let argon_config = Config::default();
    let key = argon2::hash_encoded(password.as_bytes(), &amp;salt, &amp;argon_config).unwrap();
    let decrypted_message = decrypt(&amp;encrypted_message[..], key.as_bytes(), &amp;iv).ok().unwrap();
    String::from_utf8(decrypted_message).unwrap()
}


Full code (Rust crate)

Here is the full code with encrypt/decrypt boilerplate based on this example:

crypto.tar.gz
a few comments, from high to low-level:

Usability

users are generally pretty terrible with passwords!  having them "lose" all their data if they forget their password often isn't something they expect.  i.e. they just want to do a "password reset" and get access to everything again.  I know this is at odds with what you (and lots of similar designs) are trying to do, but thought it useful to point out

Separate authentication from encryption

I'd be tempted to use a more conventional protocol for authentication, probably also separating out passwords used for authentication and encryption.  This would let you, e.g., incrementally increase the Argon2 complexity without invalidating their login (this is something you really need to allow for).  It would also let you use some sort of federated authentication system which might be nice

Crypto usage


CBC doesn't provide any authenticity.  i.e. you won't know if the right password has been used / some attacker is trying to decieve you.  I'd suggest using another block mode, or maybe a stream cipher like chacha20-poly1305
I'd probably just use the hash of the email as the salt, using password as well doesn't seem to help much.
your use of Argon2 in encrypt_payload seems to misunderstand its purpose as a general KDF.  You should just request the amount of output you want, e.g., 48 bytes of keying material from Argon2 and use 32 bytes of that as key and 16 bytes as IV.  that means you just need a single 128bit "key" per file rather than a "salt" and an "IV".  your usage isn't any more secure, 128bits is a big enough state space to stop a brute force attack while access to your storage server would give them the header anyway


API usage


argon2::hash_encoded is mostly designed for hashing passwords for authentication.  you almost certainly want to be using hash_raw to just get the keying material out
it might be useful if your design allowed the Config parameters over time to increase computation complexity.  you really want to record the parameter values used somewhere so the right ones can be used