From 7c6a5ab4c5e7a81c7442061029b9230748a6639d Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Thu, 18 Apr 2024 07:39:27 +0100 Subject: refactor: bump rust-nostr to v0.30 use ncryptsec bump nostr and nostr-sdk packages and also in test_utils remove custom ncryptsec implementation and use the newly added implementation nip49 version in rust-nostr note a patched v0.30 is used so that log_n is exposed so that user can be warned it might take a few seconds to decrypt. this has now been merged into the library. note that this will no longer decrypt existing ncryptsec values as it is uses a longer string. this should therefore be bundled with the upcoming change to storing nsec and ncryptsec in git config. --- src/key_handling/encryption.rs | 155 ++++------------------------------------- 1 file changed, 14 insertions(+), 141 deletions(-) (limited to 'src') diff --git a/src/key_handling/encryption.rs b/src/key_handling/encryption.rs index 54002fa..3f4ee41 100644 --- a/src/key_handling/encryption.rs +++ b/src/key_handling/encryption.rs @@ -1,16 +1,7 @@ -use std::str::FromStr; - -use anyhow::{anyhow, bail, ensure, Context, Result}; -use chacha20poly1305::{ - aead::{rand_core::RngCore, Aead, AeadCore, KeyInit, OsRng, Payload}, - XChaCha20Poly1305, -}; +use anyhow::Result; #[cfg(test)] use mockall::*; use nostr::{prelude::*, Keys}; -use nostr_sdk::bech32::{self, FromBase32, ToBase32}; -use rand::{distributions::Alphanumeric, thread_rng, Rng}; -use zeroize::Zeroize; #[derive(Default)] pub struct Encryptor; @@ -20,143 +11,38 @@ pub trait EncryptDecrypt { /// requires less CPU time if the password is long fn encrypt_key(&self, keys: &Keys, password: &str) -> Result; fn decrypt_key(&self, encrypted_key: &str, password: &str) -> Result; - /// generates a long random string - fn random_token(&self) -> String; } /// approach and code adapted from nostr gossip client impl EncryptDecrypt for Encryptor { fn encrypt_key(&self, keys: &Keys, password: &str) -> Result { - // Generate a random 16-byte salt - let salt = { - let mut salt: [u8; 16] = [0; 16]; - OsRng.fill_bytes(&mut salt); - salt - }; - - let nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng); - let log2_rounds: u8 = if password.len() > 20 { // we have enough of entropy - no need to spend CPU time adding much more 1 } else { + println!("this may take a few seconds..."); // default (scrypt::Params::RECOMMENDED_LOG_N) is 17 but 30s is too long to wait 15 }; - - let associated_data: Vec = vec![1]; - - let ciphertext = { - let cipher = { - let symmetric_key = password_to_key(password, &salt, log2_rounds) - .context("failed create encryption key from password")?; - XChaCha20Poly1305::new((&symmetric_key).into()) - }; - cipher - .encrypt( - &nonce, - Payload { - msg: keys - .secret_key() - .context( - "supplied key should reveal secret key. Is this a public key only?", - )? - .display_secret() - .to_string() - .as_bytes(), - aad: &associated_data, - }, - ) - .map_err(|_| anyhow!("ChaChaPoly1305 failed to encrypt nsec with password"))? - }; - // Combine salt, IV and ciphertext - let mut concatenation: Vec = Vec::new(); - concatenation.push(0x1); // 1 byte version number - concatenation.push(log2_rounds); // 1 byte for scrypt N (rounds) - concatenation.extend(salt); // 16 bytes of salt - concatenation.extend(nonce); // 24 bytes of nonce - concatenation.extend(associated_data); // 1 byte of key security - concatenation.extend(ciphertext); // 48 bytes of ciphertext expected - // Total length is 91 = 1 + 1 + 16 + 24 + 1 + 48 - - bech32::encode( - "ncryptsec", - concatenation.to_base32(), - bech32::Variant::Bech32, - ) - .context("encrypted nsec failed to encode") + Ok(nostr::nips::nip49::EncryptedSecretKey::new( + keys.secret_key()?, + password, + log2_rounds, + KeySecurity::Medium, + )? + .to_bech32()?) } fn decrypt_key(&self, encrypted_key: &str, password: &str) -> Result { - let data = - bech32::decode(encrypted_key).context("failed to decode encrypted key as bech32")?; - if data.0 != "ncryptsec" { - bail!("encrypted key is in the wrong format - it doesnt start with ncryptsec"); - } - let concatenation = Vec::::from_base32(&data.1) - .context("failed to convert bech32::decode output to Vec")?; - - // Break into parts - let version: u8 = concatenation[0]; - ensure!(version == 0x1, "encryption version is incorrect"); - let log2_rounds: u8 = concatenation[1]; - let salt: [u8; 16] = concatenation[2..2 + 16].try_into()?; - let nonce = &concatenation[2 + 16..2 + 16 + 24]; - let associated_data = &concatenation[(2 + 16 + 24)..=(2 + 16 + 24)]; - let ciphertext = &concatenation[2 + 16 + 24 + 1..]; - - let cipher = { - let symmetric_key = password_to_key(password, &salt, log2_rounds)?; - XChaCha20Poly1305::new((&symmetric_key).into()) - }; - - let payload = Payload { - msg: ciphertext, - aad: associated_data, - }; - - let mut inner_secret = cipher - .decrypt(nonce.into(), payload) - .map_err(|_| anyhow!("failed to decrypt"))?; - - if associated_data.is_empty() { - bail!("invalid encrypted key"); + let encrypted_key = nostr::nips::nip49::EncryptedSecretKey::from_bech32(encrypted_key)?; + // to request that log_n gets exposed + if encrypted_key.log_n() > 14 { + println!("this may take a few seconds..."); } - - let key = - Keys::from_str(std::str::from_utf8(&inner_secret).context("inner secret is not [u8]")?) - .context( - "incorrect password. Key decrypted with password did not produce a valid nsec.", - )?; - - inner_secret.zeroize(); - - Ok(key) - } - - fn random_token(&self) -> String { - thread_rng() - .sample_iter(&Alphanumeric) - .take(32) - .map(char::from) - .collect() + Ok(nostr::Keys::new(encrypted_key.to_secret_key(password)?)) } } -/// uses scrypt to stretch password into key -fn password_to_key(password: &str, salt: &[u8; 16], log_n: u8) -> Result<[u8; 32]> { - let params = scrypt::Params::new(log_n, 8, 1, 32) - .context("scrypt failed to generate params to stretch password")?; - let mut key: [u8; 32] = [0; 32]; - if log_n > 14 { - println!("this may take a few seconds..."); - } - - scrypt::scrypt(password.as_bytes(), salt, ¶ms, &mut key) - .context("scrypt failed to stretch password")?; - Ok(key) -} - #[cfg(test)] mod tests { use test_utils::*; @@ -235,17 +121,4 @@ mod tests { ); Ok(()) } - - #[test] - fn password_to_key_returns_ok_with_standard_password() { - let salt = { - let mut salt: [u8; 16] = [0; 16]; - OsRng.fill_bytes(&mut salt); - salt - }; - - let log2_rounds: u8 = 1; - - assert!(password_to_key(TEST_PASSWORD, &salt, log2_rounds).is_ok()); - } } -- cgit v1.2.3