RSA is known to be used by default when generating SSH keys with the ssh-keygen utility available on most Linux distributions. It can be represented as follows:
We're going to reproduce a similar key generation and then encrypt and decrypt a message in an example of Rust code. We recommend that you create a new project for the practical part that follows via cargo new.
Two dependencies are required and must be added to your cargo.toml file. These are the most recent versions at the time of writing:
rsa
= 0.9.3
rand
= 0.8.5
Within your main.rs file, we import the structures and methods required for RSA key generation:
use rsa::{Pkcs1v15Encrypt, RsaPrivateKey, RsaPublicKey};
We'll then use the documentation to create our private and public keys. These two objects are structures defined in the rsa crates and are available here: private and public. As indicated, we need a random number rng: &mut R
to be created with rand and a key size bit_size
, set to 2048, to use the new()
method. As error handling is not the priority here, we'll simply handle them with an expect()
. The code will be as follows:
# src/main.rs
use rand;
use rsa::{Pkcs1v15Encrypt, RsaPrivateKey, RsaPublicKey};
fn main() {
let mut rand_number = rand::thread_rng();
let key_size = 2048;
let private_key =
RsaPrivateKey::new(&mut rand_number, key_size).expect("Failed to generate to private key");
let public_key = RsaPublicKey::from(&private_key);
}
The public key is generated by passing a reference to the previously created private key. We then have several possibilities for using our key pair. We'll look at two use cases in the next few lines: encrypting and decrypting a string, and recovering our public and private keys.
The string we're going to use is the following:
let data = b "A message to be encrypted";
Within our RsaPublicKey structure, an implementation has been made to enable us to encrypt a message: the "encrypt" function. The public key is used to encrypt the data, while the private key is used to decrypt it.
This function takes as parameters a random number (in the same way as our key generation), a padding and the message we wish to encrypt in the form of an array of u8 data (corresponding to the ASCII value of our letters within the message). A simple check is added, the "assert_ne", to verify that the value of our encrypted message is indeed different from our base message. The code to be added is as follows:
let data = b "A message to be encrypted";
let mut rand_number = rand::thread_rng();
let data_encrypted = public_key
.encrypt(&mut rand_number, Pkcs1v15Encrypt, &data[..])
.expect("Failed to encrypt the data");
assert_ne!(&data[..], &data_encrypted[..]);
The final data we recover is "data_encrypted", which is a u8 vector containing our encrypted message. To decrypt our message, we can look at the documentation associated with our private key. Within the implementations, the "decrypt" function is proposed. It takes as parameters a padding (in the same way as "encrypt") and the message to be decrypted. It will return a vector of u8, which will be exactly identical to our u8 array in the "data" variable at the beginning.
The result can then be displayed in ASCII using a loop:
for data in data_decrypted {
print!("{}", data.to_ascii_lowercase() as char);
}
println!();
If you simply wish to display or save your public or private key, new imports are required: pkcs1::{EncodeRsaPrivateKey, EncodeRsaPublicKey, LineEnding}
which are included in the rsa crate. The final import block will therefore be :
use rsa::{
pkcs1::{EncodeRsaPrivateKey, EncodeRsaPublicKey, LineEnding},
Pkcs1v15Encrypt, RsaPrivateKey, RsaPublicKey,
};
We chose pkcs1 because it supports a classic format for encoding RSA keys in binary (DER) or textual (PEM) form. In our example, pkcs8 could also have been used, as this is the mode most often used today. To differentiate between them, we can look at the value of the first line once our key has been exported to a file.
For pkcs1, we'll have :
-----BEGIN RSA PRIVATE KEY-----
and for pkcs8 :
-----BEGIN PRIVATE KEY-----
.
A search on the Internet for a way to export RSA keys in Rust yielded initial results indicating the use of the "export-rsa" crate:
We advise you to check the crates.io links before using a crate because, as in this case, the crate is deprecated and an indication is given as to which crate has been replaced.
This is just an example, but it could apply to your next crate search and use. Having taken this wrong road ourselves at the outset, we returned to the native implementation via the rsa crate, where the key export information was already present before our internet search.
Returning to our exports, two Traits will be used: EncodeRsaPublicKey and EncodeRsaPrivateKey both to display the keys and to save them in a file. Within these Traits, we'll use the to_pkcs1_pem method, which takes as parameter a reference to our key and one of the LineEnding enumeration values. We've chosen "LF", which corresponds to a simple line feed (on Unix systems) \n
. The final data will be a String if there was no error:
let pub_pem = EncodeRsaPublicKey::to_pkcs1_pem(&public_key, LineEnding::LF);
println!("{:?}", pub_pem.unwrap());
let priv_pem = EncodeRsaPrivateKey::to_pkcs1_pem(&private_key, LineEnding::LF);
println!("{:?}", priv_pem.unwrap());
This will correctly display the public and private key at runtime.
The last point in this chapter is to save our keys in PEM format. Instead of using the to_pkcs1_pem
method, write_pkcs1_pem_file
will be used. The arguments are the same as above, but one must be added: the path where the file will be written, corresponding to path_pub
and path_priv
in our code and pointing to the project folder. Don't forget to modify this data when you implement it.
let path_pub = String::from("/home/bobby/Documents/crypto/id_rsa.pub");
let path_priv = String::from("/home/bobby/Documents/crypto/id_rsa");
EncodeRsaPublicKey::write_pkcs1_pem_file(&public_key, path_pub, LineEnding::LF)
.expect("Failed to create the key file");
EncodeRsaPrivateKey::write_pkcs1_pem_file(&private_key, path_priv, LineEnding::LF)
.expect("Failed to create the key file");
After execution, we can see the files in our project via an ls -lah
:
-rw------- 1 bobby bobby 1.7K Nov 23 15:24 id_rsa
-rw-r--r-- 1 bobby bobby 426 Nov 23 15:24 id_rsa.pub
And a "file" confirms that the system has recognized our key:
┌──(kali@Kali)-[~/Documents/crypto]
└─$ file id_rsa
id_rsa: PEM RSA private key
It's now possible to use your public and private keys to exchange information via ssh, for example. This first chapter on cryptography with RSA is now complete, and you'll find the full code in the section just below.