encrypt.rs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. use aes_gcm::aead::generic_array::GenericArray;
  2. use aes_gcm::aead::Aead;
  3. use aes_gcm::{Aes256Gcm, KeyInit};
  4. use anyhow::Result;
  5. use base64::engine::general_purpose::STANDARD;
  6. use base64::Engine;
  7. use pbkdf2::hmac::Hmac;
  8. use pbkdf2::pbkdf2;
  9. use rand::distributions::Alphanumeric;
  10. use rand::Rng;
  11. use sha2::Sha256;
  12. /// The length of the salt in bytes.
  13. const SALT_LENGTH: usize = 16;
  14. /// The length of the derived encryption key in bytes.
  15. const KEY_LENGTH: usize = 32;
  16. /// The number of iterations for the PBKDF2 key derivation.
  17. const ITERATIONS: u32 = 1000;
  18. /// The length of the nonce for AES-GCM encryption.
  19. const NONCE_LENGTH: usize = 12;
  20. /// Delimiter used to concatenate the passphrase and salt.
  21. const CONCATENATED_DELIMITER: &str = "$";
  22. /// Generate a new encryption secret consisting of a passphrase and a salt.
  23. pub fn generate_encryption_secret() -> String {
  24. let passphrase = generate_random_passphrase();
  25. let salt = generate_random_salt();
  26. combine_passphrase_and_salt(&passphrase, &salt)
  27. }
  28. /// Encrypt a byte slice using AES-GCM.
  29. ///
  30. /// # Arguments
  31. /// * `data`: The data to encrypt.
  32. /// * `combined_passphrase_salt`: The concatenated passphrase and salt.
  33. pub fn encrypt_data<T: AsRef<[u8]>>(data: T, combined_passphrase_salt: &str) -> Result<Vec<u8>> {
  34. let (passphrase, salt) = split_passphrase_and_salt(combined_passphrase_salt)?;
  35. let key = derive_key(passphrase, &salt)?;
  36. let cipher = Aes256Gcm::new(GenericArray::from_slice(&key));
  37. let nonce: [u8; NONCE_LENGTH] = rand::thread_rng().gen();
  38. let ciphertext = cipher
  39. .encrypt(GenericArray::from_slice(&nonce), data.as_ref())
  40. .unwrap();
  41. Ok(nonce.into_iter().chain(ciphertext).collect())
  42. }
  43. /// Decrypt a byte slice using AES-GCM.
  44. ///
  45. /// # Arguments
  46. /// * `data`: The data to decrypt.
  47. /// * `combined_passphrase_salt`: The concatenated passphrase and salt.
  48. pub fn decrypt_data<T: AsRef<[u8]>>(data: T, combined_passphrase_salt: &str) -> Result<Vec<u8>> {
  49. if data.as_ref().len() <= NONCE_LENGTH {
  50. return Err(anyhow::anyhow!("Ciphertext too short to include nonce."));
  51. }
  52. let (passphrase, salt) = split_passphrase_and_salt(combined_passphrase_salt)?;
  53. let key = derive_key(passphrase, &salt)?;
  54. let cipher = Aes256Gcm::new(GenericArray::from_slice(&key));
  55. let (nonce, cipher_data) = data.as_ref().split_at(NONCE_LENGTH);
  56. cipher
  57. .decrypt(GenericArray::from_slice(nonce), cipher_data)
  58. .map_err(|e| anyhow::anyhow!("Decryption error: {:?}", e))
  59. }
  60. /// Encrypt a string using AES-GCM and return the result as a base64 encoded string.
  61. ///
  62. /// # Arguments
  63. /// * `data`: The string data to encrypt.
  64. /// * `combined_passphrase_salt`: The concatenated passphrase and salt.
  65. pub fn encrypt_text<T: AsRef<[u8]>>(data: T, combined_passphrase_salt: &str) -> Result<String> {
  66. let encrypted = encrypt_data(data.as_ref(), combined_passphrase_salt)?;
  67. Ok(STANDARD.encode(encrypted))
  68. }
  69. /// Decrypt a base64 encoded string using AES-GCM.
  70. ///
  71. /// # Arguments
  72. /// * `data`: The base64 encoded string to decrypt.
  73. /// * `combined_passphrase_salt`: The concatenated passphrase and salt.
  74. pub fn decrypt_text<T: AsRef<[u8]>>(data: T, combined_passphrase_salt: &str) -> Result<String> {
  75. let encrypted = STANDARD.decode(data)?;
  76. let decrypted = decrypt_data(encrypted, combined_passphrase_salt)?;
  77. Ok(String::from_utf8(decrypted)?)
  78. }
  79. /// Generates a random passphrase consisting of alphanumeric characters.
  80. ///
  81. /// This function creates a passphrase with both uppercase and lowercase letters
  82. /// as well as numbers. The passphrase is 30 characters in length.
  83. ///
  84. /// # Returns
  85. ///
  86. /// A `String` representing the generated passphrase.
  87. ///
  88. /// # Security Considerations
  89. ///
  90. /// The passphrase is derived from the `Alphanumeric` character set which includes 62 possible
  91. /// characters (26 lowercase letters, 26 uppercase letters, 10 numbers). This results in a total
  92. /// of `62^30` possible combinations, making it strong against brute force attacks.
  93. ///
  94. fn generate_random_passphrase() -> String {
  95. rand::thread_rng()
  96. .sample_iter(&Alphanumeric)
  97. .take(30) // e.g., 30 characters
  98. .map(char::from)
  99. .collect()
  100. }
  101. fn generate_random_salt() -> [u8; SALT_LENGTH] {
  102. let mut rng = rand::thread_rng();
  103. let salt: [u8; SALT_LENGTH] = rng.gen();
  104. salt
  105. }
  106. fn combine_passphrase_and_salt(passphrase: &str, salt: &[u8; SALT_LENGTH]) -> String {
  107. let salt_base64 = STANDARD.encode(salt);
  108. format!("{}{}{}", passphrase, CONCATENATED_DELIMITER, salt_base64)
  109. }
  110. fn split_passphrase_and_salt(combined: &str) -> Result<(&str, [u8; SALT_LENGTH]), anyhow::Error> {
  111. let parts: Vec<&str> = combined.split(CONCATENATED_DELIMITER).collect();
  112. if parts.len() != 2 {
  113. return Err(anyhow::anyhow!("Invalid combined format"));
  114. }
  115. let passphrase = parts[0];
  116. let salt = STANDARD.decode(parts[1])?;
  117. if salt.len() != SALT_LENGTH {
  118. return Err(anyhow::anyhow!("Incorrect salt length"));
  119. }
  120. let mut salt_array = [0u8; SALT_LENGTH];
  121. salt_array.copy_from_slice(&salt);
  122. Ok((passphrase, salt_array))
  123. }
  124. fn derive_key(passphrase: &str, salt: &[u8; SALT_LENGTH]) -> Result<[u8; KEY_LENGTH]> {
  125. let mut key = [0u8; KEY_LENGTH];
  126. pbkdf2::<Hmac<Sha256>>(passphrase.as_bytes(), salt, ITERATIONS, &mut key)?;
  127. Ok(key)
  128. }
  129. #[cfg(test)]
  130. mod tests {
  131. use super::*;
  132. #[test]
  133. fn encrypt_decrypt_test() {
  134. let secret = generate_encryption_secret();
  135. let data = b"hello world";
  136. let encrypted = encrypt_data(data, &secret).unwrap();
  137. let decrypted = decrypt_data(encrypted, &secret).unwrap();
  138. assert_eq!(data, decrypted.as_slice());
  139. let s = "123".to_string();
  140. let encrypted = encrypt_text(&s, &secret).unwrap();
  141. let decrypted_str = decrypt_text(encrypted, &secret).unwrap();
  142. assert_eq!(s, decrypted_str);
  143. }
  144. #[test]
  145. fn decrypt_with_invalid_secret_test() {
  146. let secret = generate_encryption_secret();
  147. let data = b"hello world";
  148. let encrypted = encrypt_data(data, &secret).unwrap();
  149. let decrypted = decrypt_data(encrypted, "invalid secret");
  150. assert!(decrypted.is_err())
  151. }
  152. }