str4d/rage

Performance Issues

tengkuizdihar opened this issue · 3 comments

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 commented

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.

@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.

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.