Prechádzať zdrojové kódy

docs: documentation for encryption functions (#3243)

Nathan.fooo 1 rok pred
rodič
commit
30155924a9

+ 73 - 19
frontend/rust-lib/flowy-encrypt/src/encrypt.rs

@@ -10,19 +10,34 @@ use rand::distributions::Alphanumeric;
 use rand::Rng;
 use sha2::Sha256;
 
+/// The length of the salt in bytes.
 const SALT_LENGTH: usize = 16;
+
+/// The length of the derived encryption key in bytes.
 const KEY_LENGTH: usize = 32;
+
+/// The number of iterations for the PBKDF2 key derivation.
 const ITERATIONS: u32 = 1000;
+
+/// The length of the nonce for AES-GCM encryption.
 const NONCE_LENGTH: usize = 12;
+
+/// Delimiter used to concatenate the passphrase and salt.
 const CONCATENATED_DELIMITER: &str = "$";
 
-pub fn generate_encrypt_secret() -> String {
-  let passphrase = generate_passphrase();
-  let salt = generate_salt();
-  concatenate_passphrase_and_salt(&passphrase, &salt)
+/// Generate a new encryption secret consisting of a passphrase and a salt.
+pub fn generate_encryption_secret() -> String {
+  let passphrase = generate_random_passphrase();
+  let salt = generate_random_salt();
+  combine_passphrase_and_salt(&passphrase, &salt)
 }
 
-pub fn encrypt_bytes<T: AsRef<[u8]>>(data: T, combined_passphrase_salt: &str) -> Result<Vec<u8>> {
+/// Encrypt a byte slice using AES-GCM.
+///
+/// # Arguments
+/// * `data`: The data to encrypt.
+/// * `combined_passphrase_salt`: The concatenated passphrase and salt.
+pub fn encrypt_data<T: AsRef<[u8]>>(data: T, combined_passphrase_salt: &str) -> Result<Vec<u8>> {
   let (passphrase, salt) = split_passphrase_and_salt(combined_passphrase_salt)?;
   let key = derive_key(passphrase, &salt)?;
   let cipher = Aes256Gcm::new(GenericArray::from_slice(&key));
@@ -34,7 +49,12 @@ pub fn encrypt_bytes<T: AsRef<[u8]>>(data: T, combined_passphrase_salt: &str) ->
   Ok(nonce.into_iter().chain(ciphertext).collect())
 }
 
-pub fn decrypt_bytes<T: AsRef<[u8]>>(data: T, combined_passphrase_salt: &str) -> Result<Vec<u8>> {
+/// Decrypt a byte slice using AES-GCM.
+///
+/// # Arguments
+/// * `data`: The data to decrypt.
+/// * `combined_passphrase_salt`: The concatenated passphrase and salt.
+pub fn decrypt_data<T: AsRef<[u8]>>(data: T, combined_passphrase_salt: &str) -> Result<Vec<u8>> {
   if data.as_ref().len() <= NONCE_LENGTH {
     return Err(anyhow::anyhow!("Ciphertext too short to include nonce."));
   }
@@ -47,18 +67,43 @@ pub fn decrypt_bytes<T: AsRef<[u8]>>(data: T, combined_passphrase_salt: &str) ->
     .map_err(|e| anyhow::anyhow!("Decryption error: {:?}", e))
 }
 
-pub fn encrypt_string<T: AsRef<[u8]>>(data: T, combined_passphrase_salt: &str) -> Result<String> {
-  let encrypted = encrypt_bytes(data.as_ref(), combined_passphrase_salt)?;
+/// Encrypt a string using AES-GCM and return the result as a base64 encoded string.
+///
+/// # Arguments
+/// * `data`: The string data to encrypt.
+/// * `combined_passphrase_salt`: The concatenated passphrase and salt.
+pub fn encrypt_text<T: AsRef<[u8]>>(data: T, combined_passphrase_salt: &str) -> Result<String> {
+  let encrypted = encrypt_data(data.as_ref(), combined_passphrase_salt)?;
   Ok(STANDARD.encode(encrypted))
 }
 
-pub fn decrypt_string<T: AsRef<[u8]>>(data: T, combined_passphrase_salt: &str) -> Result<String> {
+/// Decrypt a base64 encoded string using AES-GCM.
+///
+/// # Arguments
+/// * `data`: The base64 encoded string to decrypt.
+/// * `combined_passphrase_salt`: The concatenated passphrase and salt.
+pub fn decrypt_text<T: AsRef<[u8]>>(data: T, combined_passphrase_salt: &str) -> Result<String> {
   let encrypted = STANDARD.decode(data)?;
-  let decrypted = decrypt_bytes(encrypted, combined_passphrase_salt)?;
+  let decrypted = decrypt_data(encrypted, combined_passphrase_salt)?;
   Ok(String::from_utf8(decrypted)?)
 }
 
-fn generate_passphrase() -> String {
+/// Generates a random passphrase consisting of alphanumeric characters.
+///
+/// This function creates a passphrase with both uppercase and lowercase letters
+/// as well as numbers. The passphrase is 30 characters in length.
+///
+/// # Returns
+///
+/// A `String` representing the generated passphrase.
+///
+/// # Security Considerations
+///
+///   The passphrase is derived from the `Alphanumeric` character set which includes 62 possible
+///   characters (26 lowercase letters, 26 uppercase letters, 10 numbers). This results in a total
+///   of `62^30` possible combinations, making it strong against brute force attacks.
+///
+fn generate_random_passphrase() -> String {
   rand::thread_rng()
         .sample_iter(&Alphanumeric)
         .take(30) // e.g., 30 characters
@@ -66,13 +111,13 @@ fn generate_passphrase() -> String {
         .collect()
 }
 
-fn generate_salt() -> [u8; SALT_LENGTH] {
+fn generate_random_salt() -> [u8; SALT_LENGTH] {
   let mut rng = rand::thread_rng();
   let salt: [u8; SALT_LENGTH] = rng.gen();
   salt
 }
 
-fn concatenate_passphrase_and_salt(passphrase: &str, salt: &[u8; SALT_LENGTH]) -> String {
+fn combine_passphrase_and_salt(passphrase: &str, salt: &[u8; SALT_LENGTH]) -> String {
   let salt_base64 = STANDARD.encode(salt);
   format!("{}{}{}", passphrase, CONCATENATED_DELIMITER, salt_base64)
 }
@@ -103,16 +148,25 @@ mod tests {
   use super::*;
 
   #[test]
-  fn test_encrypt_decrypt() {
-    let secret = generate_encrypt_secret();
+  fn encrypt_decrypt_test() {
+    let secret = generate_encryption_secret();
     let data = b"hello world";
-    let encrypted = encrypt_bytes(data, &secret).unwrap();
-    let decrypted = decrypt_bytes(encrypted, &secret).unwrap();
+    let encrypted = encrypt_data(data, &secret).unwrap();
+    let decrypted = decrypt_data(encrypted, &secret).unwrap();
     assert_eq!(data, decrypted.as_slice());
 
     let s = "123".to_string();
-    let encrypted = encrypt_string(&s, &secret).unwrap();
-    let decrypted_str = decrypt_string(encrypted, &secret).unwrap();
+    let encrypted = encrypt_text(&s, &secret).unwrap();
+    let decrypted_str = decrypt_text(encrypted, &secret).unwrap();
     assert_eq!(s, decrypted_str);
   }
+
+  #[test]
+  fn decrypt_with_invalid_secret_test() {
+    let secret = generate_encryption_secret();
+    let data = b"hello world";
+    let encrypted = encrypt_data(data, &secret).unwrap();
+    let decrypted = decrypt_data(encrypted, "invalid secret");
+    assert!(decrypted.is_err())
+  }
 }

+ 3 - 3
frontend/rust-lib/flowy-server/src/supabase/api/util.rs

@@ -3,7 +3,7 @@ use anyhow::Result;
 use reqwest::{Response, StatusCode};
 use serde_json::Value;
 
-use flowy_encrypt::{decrypt_bytes, encrypt_bytes};
+use flowy_encrypt::{decrypt_data, encrypt_data};
 use flowy_error::{ErrorCode, FlowyError};
 use lib_infra::future::{to_fut, Fut};
 
@@ -148,7 +148,7 @@ impl SupabaseBinaryColumnEncoder {
     let value = match encryption_secret {
       None => hex::encode(value),
       Some(encryption_secret) => {
-        let encrypt_data = encrypt_bytes(value, encryption_secret)?;
+        let encrypt_data = encrypt_data(value, encryption_secret)?;
         hex::encode(encrypt_data)
       },
     };
@@ -191,7 +191,7 @@ impl SupabaseBinaryColumnDecoder {
         )),
         Some(encryption_secret) => {
           let encrypt_data = D::decode(s)?;
-          decrypt_bytes(encrypt_data, encryption_secret)
+          decrypt_data(encrypt_data, encryption_secret)
         },
       }
     }

+ 3 - 3
frontend/rust-lib/flowy-server/tests/supabase_test/user_test.rs

@@ -1,6 +1,6 @@
 use uuid::Uuid;
 
-use flowy_encrypt::{encrypt_string, generate_encrypt_secret};
+use flowy_encrypt::{encrypt_text, generate_encryption_secret};
 use flowy_user_deps::entities::*;
 use lib_infra::box_any::BoxAny;
 
@@ -126,8 +126,8 @@ async fn user_encryption_sign_test() {
   let user: SignUpResponse = user_service.sign_up(BoxAny::new(params)).await.unwrap();
 
   // generate encryption sign
-  let secret = generate_encrypt_secret();
-  let sign = encrypt_string(user.user_id.to_string(), &secret).unwrap();
+  let secret = generate_encryption_secret();
+  let sign = encrypt_text(user.user_id.to_string(), &secret).unwrap();
 
   user_service
     .update_user(

+ 2 - 2
frontend/rust-lib/flowy-test/tests/user/supabase_test/auth_test.rs

@@ -2,7 +2,7 @@ use std::collections::HashMap;
 
 use nanoid::nanoid;
 
-use flowy_encrypt::decrypt_string;
+use flowy_encrypt::decrypt_text;
 use flowy_server::supabase::define::{USER_EMAIL, USER_UUID};
 use flowy_test::event_builder::EventBuilder;
 use flowy_test::FlowyCoreTest;
@@ -51,7 +51,7 @@ async fn third_party_sign_up_with_encrypt_test() {
     let user_profile = test.get_user_profile().await.unwrap();
     assert!(!user_profile.encryption_sign.is_empty());
 
-    let decryption_sign = decrypt_string(user_profile.encryption_sign, &secret).unwrap();
+    let decryption_sign = decrypt_text(user_profile.encryption_sign, &secret).unwrap();
     assert_eq!(decryption_sign, user_profile.id.to_string());
   }
 }

+ 2 - 2
frontend/rust-lib/flowy-user/src/services/cloud_config.rs

@@ -1,6 +1,6 @@
 use std::sync::Arc;
 
-use flowy_encrypt::generate_encrypt_secret;
+use flowy_encrypt::generate_encryption_secret;
 use flowy_error::FlowyResult;
 use flowy_sqlite::kv::StorePreferences;
 use flowy_user_deps::cloud::UserCloudConfig;
@@ -8,7 +8,7 @@ use flowy_user_deps::cloud::UserCloudConfig;
 const CLOUD_CONFIG_KEY: &str = "af_user_cloud_config";
 
 fn generate_cloud_config(uid: i64, store_preference: &Arc<StorePreferences>) -> UserCloudConfig {
-  let config = UserCloudConfig::new(generate_encrypt_secret());
+  let config = UserCloudConfig::new(generate_encryption_secret());
   let key = cache_key_for_cloud_config(uid);
   store_preference.set_object(&key, config.clone()).unwrap();
   config

+ 3 - 3
frontend/rust-lib/flowy-user/src/services/user_encryption.rs

@@ -1,4 +1,4 @@
-use flowy_encrypt::{decrypt_string, encrypt_string};
+use flowy_encrypt::{decrypt_text, encrypt_text};
 use flowy_error::{ErrorCode, FlowyError, FlowyResult};
 use flowy_user_deps::entities::{EncryptionType, UpdateUserProfileParams, UserCredentials};
 
@@ -24,7 +24,7 @@ impl UserManager {
   }
 
   pub fn generate_encryption_sign(&self, uid: i64, encrypt_secret: &str) -> FlowyResult<String> {
-    let encrypt_sign = encrypt_string(uid.to_string(), encrypt_secret)?;
+    let encrypt_sign = encrypt_text(uid.to_string(), encrypt_secret)?;
     Ok(encrypt_sign)
   }
 
@@ -51,7 +51,7 @@ impl UserManager {
     encrypt_sign: &str,
     encryption_secret: &str,
   ) -> FlowyResult<()> {
-    let decrypt_str = decrypt_string(encrypt_sign, encryption_secret)
+    let decrypt_str = decrypt_text(encrypt_sign, encryption_secret)
       .map_err(|_| FlowyError::new(ErrorCode::InvalidEncryptSecret, "Invalid decryption secret"))?;
     if uid.to_string() == decrypt_str {
       Ok(())