Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance Issues #399

Open
tengkuizdihar opened this issue Jul 5, 2023 · 3 comments
Open

Performance Issues #399

tengkuizdihar opened this issue Jul 5, 2023 · 3 comments

Comments

@tengkuizdihar
Copy link

What were you trying to do

Right now I'm trying to create a note taking application. Every note is a row in the database and the contents are encrypted by a password based encryption method.

What happened

It's quite slow, for example encrypting a small struct takes about ~1.5 second. Decryption also has a similar runtime. Is there a way to speed this process up?

Below is the function I used to encrypt data.

pub fn encrypt_data<T: Serialize>(
    data: T,
    secret: &str, // this is user's password
) -> Result<Vec<u8>, CryptoError> {
    let encryptor =
        age::Encryptor::with_user_passphrase(age::secrecy::Secret::new(secret.to_owned()));

    let mut encrypted = vec![];
    let mut writer = encryptor
        .wrap_output(&mut encrypted)
        .map_err(|_| CryptoError::CryptoError)?;

    let writing_start = Instant::now();
    writer
        .write_all(data_serialized.as_slice())
        .map_err(|_| CryptoError::CryptoError)?;

    writer.finish().map_err(|_| CryptoError::CryptoError)?;

    Ok(encrypted)
}

This is the function I used to test how long it take to encrypt it.

fn testEncryption() {
    let user_password = "boogydown5114141";

    #[derive(Serialize, Deserialize)]
    struct TestingData {
        first: String,
        second: String,
    }

    let start = Instant::now();
    let _ = encrypt_data(
        TestingData {
            first: "back in the days in the boulevard".to_string(),
            second: "back in the days in the boulevard".to_string(),
        },
        user_password,
    )
    .unwrap();
    println!("Encryption Duration: {:?}", start.elapsed());
}

Printed: Encryption Duration: 1.841529562s

I'm also aware of #148, seems like it's abandoned for now (?).

Questions

If this is currently unsolvable, forgive me if I'm being rude, but do you have a suggestion about other encryption libraries that could do a passphrase based encryption upon a stream of data? Similar to what with_user_passphrase() offers. I'm also interested in helping this, but I doubt my current understanding of encryption is good enough to help you guys.

@str4d
Copy link
Owner

str4d commented Jul 5, 2023

The performance problem you are noticing is the cost of running the scrypt PBKDF on the user passphrase, which is automatically set at encryption time to a hardness level of at least 1 second of computation on the current machine (and due to the granularity of the hardness setting could be anywhere between 1 and 2 seconds). This is a security measure used by all password-based encryption mechanisms, and thus you'll likely see the same performance issues with any other library. Additionally, when used this way, the passphrase is run through PBKDF with a unique salt for each encryption (as a defense against security issues related to passphrase reuse), which means the 1-2 seconds of PBKDF cost are paid per row.

[Obligatory "I Am Not Your Cryptographer" disclaimer]

Instead of encrypting every row with the user's passphrase, a more performant approach would be to encrypt every row with a native age key, and encrypt that with the user's passphrase. Then on start, your application could decrypt the native age key into memory, and then use that for on-the-fly encryption and decryption of rows.

@tengkuizdihar
Copy link
Author

tengkuizdihar commented Jul 7, 2023

@str4d is there a way for me to use a "native age key" directly to encrypt a stream of data?

I'll maybe try to make the Stream struct public so it can be reusable for as long as my application is opened.

@tengkuizdihar
Copy link
Author

tengkuizdihar commented Jul 7, 2023

Instead of encrypting every row with the user's passphrase, a more performant approach would be to encrypt every row with a native age key, and encrypt that with the user's passphrase.

I think it's currently impossible to do that right now because StreamWriter::finish() consumes itself after it's used.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants