RSA Key pair generation in Rust

Hi, I was wondering how i Generate RSA keypair in Rust, and do the following:

  1. Save the private key in a file in my device
  2. Upload the public key to a server
  3. Later read the private key from file, load it and sign a message
  4. Send this signature in server
  5. Server verifies the signature using corresponding public key

Later on load the private key from file in my device, sign some message and send it to server, so that the server can verify with the corresponding public key ut has.

Now I explored the "rsa" crafte, its simple to generate the key-pair. But i am not getting any solution that how i write the private key to a file, and later on load it from file to sign the message. I mean convert both private and public key as string, write the private key in a file, upload the public key to server and later, load the private key string from file and create the private key instance to sign the message. The procedure is fairly simple i know, but i cant figure it out how. Any help?

Take a look at this: GitHub - lukaszwojtow/caverr

Uts a cli tool. However i resolved it with pkcs1:RsaPrivateKey and RsaPublicKey. Currently its working fine for generating keys, saving private key, uploading public key, signing message with private key. Now i am trying to verify the sign via public key. My server is written in Python, trying with python cryptgraphy library to verify the sign, gettibg invalid signature always, I will check.

fn sign_data(data: DeviceData) -> Result<String, String> {
    let secret_path = dirs::data_dir()
        .ok_or("Failed to retrieve data directory")?
        .join("my-secrets");
    let keyfile_path = secret_path.join("keyfile.pem");
    let keyfile_path2 = secret_path.join("pub_keyfile.pem");

    let json_string = serde_json::to_string(&data)
        .map_err(|e| format!("Failed to serialize data to JSON: {}", e))?;

    let private_key_pem = fs::read_to_string(&keyfile_path)
        .map_err(|e| format!("Failed to read private key: {}", e))?;

    let pub_key_pem = fs::read_to_string(&keyfile_path2)
        .map_err(|e| format!("Failed to read public key: {}", e))?;


    let private_key = RsaPrivateKey::from_pkcs1_pem(&private_key_pem)
    .map_err(|e| format!("Failed to parse private key: {}", e))?;

    let padding_scheme = Pkcs1v15Sign::new_raw();
    let signature = private_key.sign(padding_scheme.clone(),json_string.clone().as_bytes()).expect("Failed to sign");


    let public_key = RsaPublicKey::from_pkcs1_pem(&pub_key_pem)
    .map_err(|e| format!("Failed to parse public key: {}", e))?;
    let verification_result = public_key.verify(padding_scheme, json_string.as_bytes(), &signature);
    println!("{:?}", json_string);
    match verification_result {
    Ok(())=>{
        println!("Data Verfied");
        println!("Signature: {:?}", signature);
    },
    Err(err)=>println!("Verfication Failed"),
    };
    Ok(base64::encode(signature))
}

This is my function that read a private key from file, sign a data, base64encode the sign. It works fine, i tried to verify the signature via corresponding public key, it works fine too.

However, for the verification process, I have a python server which holds the public key. I am trying to verify the sign in python, but i am missing something, getting InvalidSignature. Is it due to the padding Scheme and Algorithm? What Hash algorthm rsa in Rust use internally? Can anyone help? I am attaching py python code as well for reference.

    public_key = serialization.load_pem_public_key(pub_key, backend=default_backend())
    signed_data = base64.b64decode(signature) # Signature comes from rust
    # Verify the signature
    try:
        algorithm = hashes.SHA256()
        padding_scheme = padding.PKCS1v15()
        public_key.verify(
            signature=signed_data,
            data=original_data.encode('utf-8'), # original_data is the data to create the signature in rust
            padding=padding_scheme,
            algorithm=algorithm
        )
        return True
    except Exception as e:
        return False

It seems the json_string variable is not returned from the sign_data function. How does it sent to the python server and how the server accept it? Does original_data in python have same value as json_string byte by byte? How did you check it? Also same for the value of signature in both side.

The crate-level docs for the rsa crate have a whole section dedicated to PKCS#1 encoding. Follow the linked traits, they have easy-to-use helper methods for writing/reading keys to/from DER files.

Hi thank you for the reply. Actually I am using Tauri with react, the data comes from react frontend to the Tauri sign-data function, in Rust code it just sign and send the signature, and from react side the same data(passed to this function) was sent to python server. Let me share what i did so far.

#[tauri::command]
fn sign_data(data: String) -> Result<String, String> {
    let secret_path = dirs::data_dir()
        .ok_or("Failed to retrieve data directory")?
        .join("my-secrets");
    let keyfile_path = secret_path.join("keyfile.pem");
    let keyfile_path2 = secret_path.join("pub_keyfile.pem");

    let private_key_pem = fs::read_to_string(&keyfile_path)
        .map_err(|e| format!("Failed to read private key: {}", e))?;

    let pub_key_pem = fs::read_to_string(&keyfile_path2)
        .map_err(|e| format!("Failed to read public key: {}", e))?;


    let private_key = RsaPrivateKey::from_pkcs1_pem(&private_key_pem)
    .map_err(|e| format!("Failed to parse private key: {}", e))?;

    let padding_scheme = Pkcs1v15Sign::new_unprefixed();
    let signature = private_key.sign(padding_scheme.clone(),data.clone().as_bytes()).expect("Failed to sign");

    // Test Verification
    let public_key = RsaPublicKey::from_pkcs1_pem(&pub_key_pem)
    .map_err(|e| format!("Failed to parse public key: {}", e))?;
    let verification_result = public_key.verify(padding_scheme, data.as_bytes(), &signature);
    println!("{:?}", data);
    match verification_result {
    Ok(())=>{
        println!("Data Verfied");
        println!("Signature: {:?}", signature);
    },
    Err(err)=>println!("Verfication Failed"),
    };
    Ok(base64::encode(signature))
}

In this rust function written in Tauri, I expect a String data as a message to sign, I load the private key written in pem file, sign the data, and return the signature as base64 encoding>

const getSignedData = async(data)=>{
    return Promise.resolve(await window.__TAURI__.invoke("sign_data", {data: data}));
}
 const fetchData=(my_data) => {
   let data = JSON.stringify({...my_data});
   let url = my_python_server_url;
   getSignedData(data).then((resp)=>{
    // Here i get the sign from sign-data function in rust invoked by tauri, and send the same message as original_data that i passed to create the signature
     axios.post(url, {"data": resp[0], "original_data": data}).then((resp)=>{})
  }
}

Hope this helps, to see what I am doing.

I am sharing my python code below to verify the sign:

def verify_terminal_request(data):
    public_key_str = my_public_key (loaded from db as string)
    key = public_key_str.encode('utf-8')
    public_key = serialization.load_pem_public_key(key, backend=default_backend())
    signed_data = data.get('data')
    signed_data = base64.b64decode(signed_data)
    # Verify the signature
    try:
        algorithm = hashes.SHA256()
        padding_scheme = padding.PKCS1v15()
        public_key.verify(
            signature=signed_data,
            data=data.get('original_data').encode('utf-8'),
            padding=padding_scheme,
            algorithm=algorithm
        )
        return True
    except Exception as e:
        return False

When i verify the sign using Rust itself, its rsa crate, RsaPublicKey.verify function, it works perfect (as you could see, i added the verification function in rust as well). However samething not working in python.

I am thinking about the padding scheme and the algorithm, in python we need to use the adding scheme and algorithm to verify the signature. In rust we dont define any algorithm when create the sign. And for padding in Rust I use

let padding_scheme = Pkcs1v15Sign::new_unprefixed();

as you could see in the code. I doubt something is mismtached there? But I am not sure and i dont know where is the issue.

Hi, thank you for you reply. I find out it and resolved writing keys in pem file and loading key from pem file to create the instance back. Thank you.