From 9221ed0d64f183e02684df8a5eab1e785aab86d4 Mon Sep 17 00:00:00 2001 From: EETagent Date: Sun, 30 Oct 2022 00:49:43 +0200 Subject: [PATCH 1/5] feat: ultra fast AES encryption --- core/Cargo.toml | 1 + core/src/crypto.rs | 155 +++++++++++++++++++++++++++++++++++++------ core/src/mutation.rs | 4 +- 3 files changed, 137 insertions(+), 23 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index 9d32d30..24feda7 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -26,6 +26,7 @@ infer = "^0.9" # crypto rand = "^0.8" +aes-gcm = { version = "^0.10", features = ["std"] } argon2 = { version = "^0.4", features = ["std"] } age = { version = "^0.9", features = ["async"] } secrecy = { version = "^0.8" } diff --git a/core/src/crypto.rs b/core/src/crypto.rs index 9c90d90..4717922 100644 --- a/core/src/crypto.rs +++ b/core/src/crypto.rs @@ -1,3 +1,5 @@ +use aes_gcm::aead::Aead; +use aes_gcm::{KeyInit}; use argon2::{ Argon2, PasswordHasher as ArgonPasswordHasher, PasswordVerifier as ArgonPasswordVerifier, }; @@ -74,7 +76,56 @@ pub async fn verify_password<'a>( Ok(result?) } +fn convert_key_aes256(key: &str) -> Vec { + const REQUIRED_KEY_BYTES: usize = 32; + //const REQUIRED_NONCE_BYTES: usize = 32; + + let key_len = key.as_bytes().len(); + let multiplied_key: String = key.repeat((REQUIRED_KEY_BYTES / key_len) + 1); + + let key = multiplied_key.as_bytes().to_vec(); + + key +} + pub async fn encrypt_password( + password_plain_text: String, + key: String, +) -> Result> { + let hash = tokio::task::spawn_blocking(move || { + let aes_key_nonce = convert_key_aes256(&key); + + let nonce = aes_gcm::Nonce::from_slice(&aes_key_nonce[..12]); + + let cipher = aes_gcm::Aes256Gcm::new_from_slice(&aes_key_nonce[..32]).unwrap(); + + let res = cipher.encrypt(nonce, password_plain_text.as_bytes()); + res + }) + .await??; + + Ok(base64::encode(hash)) +} + +pub async fn decrypt_password(password_cipher_text: String, key: String) -> Result> { + let input = base64::decode(password_cipher_text).unwrap(); + let plain = tokio::task::spawn_blocking(move || { + let aes_key_nonce = convert_key_aes256(&key); + + let nonce = aes_gcm::Nonce::from_slice(&aes_key_nonce[..12]); + let cipher = aes_gcm::Aes256Gcm::new_from_slice(&aes_key_nonce[..32]).unwrap(); + + let res = cipher.decrypt(nonce, &*input); + + res + }).await??; + + Ok(String::from_utf8(plain).unwrap()) + +} + +#[deprecated(note = "Too slow, use AES instead")] +pub async fn encrypt_password_age( password_plain_text: &str, key: &str, ) -> Result { @@ -94,7 +145,8 @@ pub async fn encrypt_password( Ok(base64::encode(encrypt_buffer)) } -pub async fn decrypt_password( +#[deprecated(note = "Too slow, use AES instead")] +pub async fn decrypt_password_age( password_encrypted: &str, key: &str, ) -> Result> { @@ -297,12 +349,21 @@ mod tests { assert!(result); } + #[test] + fn test_convert_key_aes256() { + const KEY: &str = "test"; + + let key = super::convert_key_aes256(KEY); + + assert!(key.len() >= 32); + } + #[tokio::test] async fn test_encrypt_password_is_valid_base64() { const PASSWORD: &str = "test"; - const KEY: &str = "test"; + const KEY: &str = "testtesttesttesttesttest"; - let encrypted = super::encrypt_password(PASSWORD, KEY).await.unwrap(); + let encrypted = super::encrypt_password(PASSWORD.to_string(), KEY.to_string()).await.unwrap(); assert!(base64::decode(encrypted).is_ok()); } @@ -312,9 +373,33 @@ mod tests { const PASSWORD: &str = "test"; const KEY: &str = "test"; - let encrypted = super::encrypt_password(PASSWORD, KEY).await.unwrap(); + let encrypted = super::encrypt_password(PASSWORD.to_string(), KEY.to_string()).await.unwrap(); - let decrypted = super::decrypt_password(&encrypted, KEY).await.unwrap(); + let decrypted = super::decrypt_password(encrypted, KEY.to_string()).await.unwrap(); + + assert_eq!(PASSWORD, decrypted); + } + + #[tokio::test] + async fn test_encrypt_password_age_is_valid_base64() { + const PASSWORD: &str = "test"; + const KEY: &str = "testtesttesttesttesttest"; + + #[allow(deprecated)] + let encrypted = super::encrypt_password_age(PASSWORD, KEY).await.unwrap(); + + assert!(base64::decode(encrypted).is_ok()); + } + + #[tokio::test] + async fn test_encrypt_decrypt_age_password() { + const PASSWORD: &str = "test"; + const KEY: &str = "test"; + + #[allow(deprecated)] + let encrypted = super::encrypt_password_age(PASSWORD, KEY).await.unwrap(); + #[allow(deprecated)] + let decrypted = super::decrypt_password_age(&encrypted, KEY).await.unwrap(); assert_eq!(PASSWORD, decrypted); } @@ -404,30 +489,52 @@ mod tests { let mut plain_file = async_tempfile::TempFile::new().await.unwrap(); let mut encrypted_file = async_tempfile::TempFile::new().await.unwrap(); - tokio::io::AsyncWriteExt::write_all(&mut plain_file, PASSWORD.as_bytes()).await.unwrap(); - encrypted_file.sync_all().await.unwrap(); - - assert_eq!(tokio::fs::read_to_string(&plain_file.file_path()).await.unwrap(), PASSWORD); - - super::encrypt_file_with_recipients(&plain_file.file_path(), &encrypted_file.file_path(), vec![PUBLIC_KEY]) + tokio::io::AsyncWriteExt::write_all(&mut plain_file, PASSWORD.as_bytes()) .await .unwrap(); encrypted_file.sync_all().await.unwrap(); + assert_eq!( + tokio::fs::read_to_string(&plain_file.file_path()) + .await + .unwrap(), + PASSWORD + ); + + super::encrypt_file_with_recipients( + &plain_file.file_path(), + &encrypted_file.file_path(), + vec![PUBLIC_KEY], + ) + .await + .unwrap(); + encrypted_file.sync_all().await.unwrap(); + let mut buffer = [0; 21]; - tokio::io::AsyncReadExt::read(&mut encrypted_file, &mut buffer).await.unwrap(); + tokio::io::AsyncReadExt::read(&mut encrypted_file, &mut buffer) + .await + .unwrap(); assert_eq!(&buffer, b"age-encryption.org/v1"); let decrypted_file = async_tempfile::TempFile::new().await.unwrap(); - super::decrypt_file_with_private_key(&encrypted_file.file_path(), &decrypted_file.file_path(), PRIVATE_KEY) - .await - .unwrap(); + super::decrypt_file_with_private_key( + &encrypted_file.file_path(), + &decrypted_file.file_path(), + PRIVATE_KEY, + ) + .await + .unwrap(); decrypted_file.sync_all().await.unwrap(); - assert_eq!(tokio::fs::read_to_string(&decrypted_file.file_path()).await.unwrap(), PASSWORD); + assert_eq!( + tokio::fs::read_to_string(&decrypted_file.file_path()) + .await + .unwrap(), + PASSWORD + ); } #[tokio::test] @@ -441,15 +548,21 @@ mod tests { let mut plain_file = async_tempfile::TempFile::new().await.unwrap(); let encrypted_file = async_tempfile::TempFile::new().await.unwrap(); - tokio::io::AsyncWriteExt::write_all(&mut plain_file, PASSWORD.as_bytes()).await.unwrap(); + tokio::io::AsyncWriteExt::write_all(&mut plain_file, PASSWORD.as_bytes()) + .await + .unwrap(); let plain_buffer = tokio::fs::read(&plain_file.file_path()).await.unwrap(); assert_eq!(String::from_utf8(plain_buffer.clone()).unwrap(), PASSWORD); - super::encrypt_file_with_recipients(&plain_file.file_path(), &encrypted_file.file_path(), vec![PUBLIC_KEY]) - .await - .unwrap(); + super::encrypt_file_with_recipients( + &plain_file.file_path(), + &encrypted_file.file_path(), + vec![PUBLIC_KEY], + ) + .await + .unwrap(); let decrypted_buffer = super::decrypt_file_with_private_key_as_buffer(encrypted_file.file_path(), PRIVATE_KEY) @@ -463,4 +576,4 @@ mod tests { PASSWORD ); } -} \ No newline at end of file +} diff --git a/core/src/mutation.rs b/core/src/mutation.rs index 7e88f47..d127342 100644 --- a/core/src/mutation.rs +++ b/core/src/mutation.rs @@ -14,7 +14,7 @@ impl Mutation { // TODO: unwrap pro testing.. let hashed_password = hash_password(plain_text_password.to_string()).await.unwrap(); let (pubkey, priv_key_plain_text) = crypto::create_identity(); - let encrypted_priv_key = crypto::encrypt_password(&priv_key_plain_text, &plain_text_password.to_string()).await.unwrap(); + let encrypted_priv_key = crypto::encrypt_password_age(&priv_key_plain_text, &plain_text_password.to_string()).await.unwrap(); candidate::ActiveModel { @@ -101,7 +101,7 @@ mod tests { let encrypted_message = crypto::encrypt_password_with_recipients(&secret_message, vec![&candidate.public_key]).await.unwrap(); - let private_key_plain_text = crypto::decrypt_password(&candidate.private_key, &plain_text_password).await.unwrap(); + let private_key_plain_text = crypto::decrypt_password_age(&candidate.private_key, &plain_text_password).await.unwrap(); let decrypted_message = crypto::decrypt_password_with_private_key(&encrypted_message, &private_key_plain_text).await.unwrap(); From 764f762bc537d0979c1754f002d3177d3a333eaa Mon Sep 17 00:00:00 2001 From: EETagent Date: Sun, 30 Oct 2022 01:09:13 +0200 Subject: [PATCH 2/5] refactoring: formatting & aes key test improvement --- core/src/crypto.rs | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/core/src/crypto.rs b/core/src/crypto.rs index 4717922..2ecc8d4 100644 --- a/core/src/crypto.rs +++ b/core/src/crypto.rs @@ -1,5 +1,5 @@ use aes_gcm::aead::Aead; -use aes_gcm::{KeyInit}; +use aes_gcm::KeyInit; use argon2::{ Argon2, PasswordHasher as ArgonPasswordHasher, PasswordVerifier as ArgonPasswordVerifier, }; @@ -53,7 +53,7 @@ pub async fn hash_password( return Ok(hash_string); } -pub async fn verify_password<'a>( +pub async fn verify_password( password_plaint_text: String, hash: String, ) -> Result> { @@ -107,7 +107,10 @@ pub async fn encrypt_password( Ok(base64::encode(hash)) } -pub async fn decrypt_password(password_cipher_text: String, key: String) -> Result> { +pub async fn decrypt_password( + password_cipher_text: String, + key: String, +) -> Result> { let input = base64::decode(password_cipher_text).unwrap(); let plain = tokio::task::spawn_blocking(move || { let aes_key_nonce = convert_key_aes256(&key); @@ -118,10 +121,10 @@ pub async fn decrypt_password(password_cipher_text: String, key: String) -> Resu let res = cipher.decrypt(nonce, &*input); res - }).await??; + }) + .await??; Ok(String::from_utf8(plain).unwrap()) - } #[deprecated(note = "Too slow, use AES instead")] @@ -351,11 +354,16 @@ mod tests { #[test] fn test_convert_key_aes256() { - const KEY: &str = "test"; + let key_1 = super::convert_key_aes256("a"); + assert!(key_1.len() >= 32); - let key = super::convert_key_aes256(KEY); + let key_2 = super::convert_key_aes256( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + ); + assert!(key_2.len() >= 32); - assert!(key.len() >= 32); + let key_3 = super::convert_key_aes256(&super::random_8_char_string()); + assert!(key_3.len() >= 32); } #[tokio::test] @@ -363,7 +371,9 @@ mod tests { const PASSWORD: &str = "test"; const KEY: &str = "testtesttesttesttesttest"; - let encrypted = super::encrypt_password(PASSWORD.to_string(), KEY.to_string()).await.unwrap(); + let encrypted = super::encrypt_password(PASSWORD.to_string(), KEY.to_string()) + .await + .unwrap(); assert!(base64::decode(encrypted).is_ok()); } @@ -373,9 +383,13 @@ mod tests { const PASSWORD: &str = "test"; const KEY: &str = "test"; - let encrypted = super::encrypt_password(PASSWORD.to_string(), KEY.to_string()).await.unwrap(); + let encrypted = super::encrypt_password(PASSWORD.to_string(), KEY.to_string()) + .await + .unwrap(); - let decrypted = super::decrypt_password(encrypted, KEY.to_string()).await.unwrap(); + let decrypted = super::decrypt_password(encrypted, KEY.to_string()) + .await + .unwrap(); assert_eq!(PASSWORD, decrypted); } From 33ee9a50a2c3e16d903b596dd2bcccd17b1d7322 Mon Sep 17 00:00:00 2001 From: EETagent Date: Sun, 30 Oct 2022 01:28:57 +0200 Subject: [PATCH 3/5] feat: use more secure AES implementation --- core/Cargo.toml | 2 +- core/src/crypto.rs | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index 24feda7..22a29bc 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -26,7 +26,7 @@ infer = "^0.9" # crypto rand = "^0.8" -aes-gcm = { version = "^0.10", features = ["std"] } +aes-gcm-siv = { version = "^0.11", features = ["std"] } argon2 = { version = "^0.4", features = ["std"] } age = { version = "^0.9", features = ["async"] } secrecy = { version = "^0.8" } diff --git a/core/src/crypto.rs b/core/src/crypto.rs index 2ecc8d4..603a9a4 100644 --- a/core/src/crypto.rs +++ b/core/src/crypto.rs @@ -1,5 +1,5 @@ -use aes_gcm::aead::Aead; -use aes_gcm::KeyInit; +use aes_gcm_siv::aead::Aead; +use aes_gcm_siv::KeyInit; use argon2::{ Argon2, PasswordHasher as ArgonPasswordHasher, PasswordVerifier as ArgonPasswordVerifier, }; @@ -95,9 +95,9 @@ pub async fn encrypt_password( let hash = tokio::task::spawn_blocking(move || { let aes_key_nonce = convert_key_aes256(&key); - let nonce = aes_gcm::Nonce::from_slice(&aes_key_nonce[..12]); + let nonce = aes_gcm_siv::Nonce::from_slice(&aes_key_nonce[..12]); - let cipher = aes_gcm::Aes256Gcm::new_from_slice(&aes_key_nonce[..32]).unwrap(); + let cipher = aes_gcm_siv::Aes256GcmSiv::new_from_slice(&aes_key_nonce[..32]).unwrap(); let res = cipher.encrypt(nonce, password_plain_text.as_bytes()); res @@ -115,8 +115,8 @@ pub async fn decrypt_password( let plain = tokio::task::spawn_blocking(move || { let aes_key_nonce = convert_key_aes256(&key); - let nonce = aes_gcm::Nonce::from_slice(&aes_key_nonce[..12]); - let cipher = aes_gcm::Aes256Gcm::new_from_slice(&aes_key_nonce[..32]).unwrap(); + let nonce = aes_gcm_siv::Nonce::from_slice(&aes_key_nonce[..12]); + let cipher = aes_gcm_siv::Aes256GcmSiv::new_from_slice(&aes_key_nonce[..32]).unwrap(); let res = cipher.decrypt(nonce, &*input); From b5617146973f92eea81f74135313df48b750aa7d Mon Sep 17 00:00:00 2001 From: EETagent Date: Sun, 30 Oct 2022 08:20:36 +0100 Subject: [PATCH 4/5] fix: aes256 key convert early return --- core/src/crypto.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/src/crypto.rs b/core/src/crypto.rs index 603a9a4..07a8522 100644 --- a/core/src/crypto.rs +++ b/core/src/crypto.rs @@ -78,9 +78,14 @@ pub async fn verify_password( fn convert_key_aes256(key: &str) -> Vec { const REQUIRED_KEY_BYTES: usize = 32; - //const REQUIRED_NONCE_BYTES: usize = 32; + //const REQUIRED_NONCE_BYTES: usize = 12; let key_len = key.as_bytes().len(); + + if key_len >= REQUIRED_KEY_BYTES { + return key.as_bytes().to_vec(); + } + let multiplied_key: String = key.repeat((REQUIRED_KEY_BYTES / key_len) + 1); let key = multiplied_key.as_bytes().to_vec(); From 0e154ae3f94b70f4ba9d7dcbba076b3d9ed0dc05 Mon Sep 17 00:00:00 2001 From: EETagent Date: Sun, 30 Oct 2022 12:36:09 +0100 Subject: [PATCH 5/5] feat: return migration & now with tokio --- migration/Cargo.toml | 4 +++- migration/src/main.rs | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 migration/src/main.rs diff --git a/migration/Cargo.toml b/migration/Cargo.toml index 794151d..673784b 100644 --- a/migration/Cargo.toml +++ b/migration/Cargo.toml @@ -9,10 +9,12 @@ name = "migration" path = "src/lib.rs" [dependencies] +tokio = { version = "^1.21", features = ["macros"] } + serde = { version = "^1.0", features = ["derive"] } chrono = "^0.4" portfolio-entity = { path = "../entity" } [dependencies.sea-orm-migration] version = "^0.10" -features = [ "runtime-tokio-native-tls", "sqlx-postgres", "sqlx-sqlite"] +features = [ "runtime-tokio-native-tls", "sqlx-postgres", "sqlx-sqlite"] \ No newline at end of file diff --git a/migration/src/main.rs b/migration/src/main.rs new file mode 100644 index 0000000..f054dea --- /dev/null +++ b/migration/src/main.rs @@ -0,0 +1,6 @@ +use sea_orm_migration::prelude::*; + +#[tokio::main] +async fn main() { + cli::run_cli(migration::Migrator).await; +}