Transform encryption allows for proxy encryption. It basically uses three keys: one to encrypt the data for the recipient, the second to encrypt it for another recipient, and the third to decrypt the ciphertext.
Transform Encryption in Rust |
Outline
Let's say that Alice has stored an encrypted file, which uses a given key (\(K_A\)). Alice can then protect her key by encrypting the key with her public key. She can then decrypt this encrypted key with her private key (\(E_pk(K_A)\)):
But let's say that Alice now wants to share the encrypted document with Bob, and where we want to convert the key encrypted with Alice's public key, into one that can be decrypted by Bob's private key. For this, we can use transform (or proxy) re-encryption. At the core of this is a transform key, which is the key which can decrypt the protected key to Bob's private one. This is done by Trent, who is trusted to take Alice's private key and Bob's public key, and create a transform key (\(T_{AB}\)). This can then be given to Bob when required, along with Alice's protected encryption key:
In this case, Trent can become the proxy and trust create the transformation key, and receive Bob's public key, and Alice's private key. So let's code using Rust (as it is a highly secure programming environment, with lots of libraries - crates).
Method
Now you should know that in public-key encryption, that you can have a public key and a private key. Normally if Alice sends encrypted data to Bob, he will use his public key to encrypt the data (Bpub), and then Bob would use his private key (Bpriv) to decrypt it. But now let’s say we have two key pairs: (\(A_{priv}, A_{pub}\)) and (\(B_{priv}, B_{pub}\)), and who are owned by Alice and Bob, respectively. Could we encrypt with Alice’s public key (\(A_{pub}\)) and then for it to be decrypted with Bob’s private key (\(B_{priv}\))? This is known as transform encryption, and where we have a special transform key (\(A_{pub}\) -> \(B_{pub}\)) using Alice’s private key (\(A_{priv}\)) and Bob’s public key (\(B_{pub}\)). We then could pass the encrypted data, encrypted with Alice’s public key (Apub) to Trent, and then to also send Trent the transformation key. Trent can then create the required ciphertext for Bob, and which he can only decrypt with his private key (\(B_{priv}\)).
Now, let’s say that Alice wants to send a secret message to a group (Bob, Carol and Dave), and where the group has its own public key, and where each of the group has the required private key for the group. Now Alice can use Trent as a trusted proxy:
- Alice uses her public key (Apub) and encrypts the data, and sends it to Trent to store.
- Alice then generates a transform key for the group and deposits this with Trent.
- Bob then asks Trent for the encrypted data, and Trent uses the transform to convert it into ciphertext.
- Bob then takes the ciphertext from Trent and then decrypts it with the group’s private key.
In this way, Trent is the proxy for the encrypted data, but Trent cannot read the secret message that Alice is sending, and Alice cannot see who is requesting the encrypted data. We thus preserve the privacy of the request from Bob, but where Trent cannot read the message.
Code
First we create the with:
cargo new transform
We then go into the transform folder, and add the following to the cargo.toml file:
[package] name = "pr" version = "0.1.0" authors = ["billbuchanan "] edition = "2018" [dependencies] recrypt = "0.11.0" hex = "0.4.2" pad = "0.1"
Next we go into the src folder, and edit the main.rs file with:
use recrypt::prelude::*; use recrypt::api::Plaintext; use pad::PadStr; use std::env; fn main() { fn unsize(x: &[T]) -> &[T] { x } // create a new recrypt let recrypt = Recrypt::new(); let mut mystr="Hello123"; let args: Vec = env::args().collect(); if args.len() >1 { mystr = &args[1];} let x=Plaintext::new_from_slice(mystr.pad_to_width_with_char(384,' ').as_bytes()); let pt = x.unwrap(); let signing_keypair= recrypt.generate_ed25519_key_pair(); let (alice_priv_key, alice_pub_key) = recrypt.generate_key_pair().unwrap(); let encrypted_val = recrypt.encrypt(&pt, &alice_pub_key, &signing_keypair).unwrap(); let (bob_priv_key, bob_pub_key) = recrypt.generate_key_pair().unwrap(); let alice_to_bob_transform_key = recrypt.generate_transform_key( &alice_priv_key, &bob_pub_key, &signing_keypair).unwrap(); let transformed_val = recrypt.transform( encrypted_val, alice_to_bob_transform_key, &signing_keypair).unwrap(); let decrypted_val = recrypt.decrypt(transformed_val, &bob_priv_key).unwrap(); println!("\nInput string:\t{} ",mystr); println!("\nSigning key:\t{} ",hex::encode(unsize(signing_keypair.bytes()))); println!("\nalice Private key:\t{} ",hex::encode(unsize(alice_priv_key.bytes()))); let (x,y)=alice_pub_key.bytes_x_y(); println!("\nalice Public key:\t{},{} ",hex::encode(unsize(x)),hex::encode(unsize(y)) ); println!("\nbob Private key:\t{} ",hex::encode(unsize(bob_priv_key.bytes()))); let (x,y)=bob_pub_key.bytes_x_y(); println!("\nbob Public key:\t{},{} ",hex::encode(unsize(x)),hex::encode(unsize(y)) ); println!("\nDecrypted:\t{} ",String::from_utf8_lossy(decrypted_val.bytes())); }
Finally we simply build with:
cargo build
A sample run is:
Signing key: 3b8c142e3ce8b4ed2f86d1fcc8d0eb65cd0f53bc379f5320201414053b2917601a66d2a14003c7b6484b1a42cfd5b958ee68999882c4e04f60708d32dd12a113 alice Private key: 14b08e8bf6d732d42a00de1cd7e40116cfdaea01e95d7db5a1a0af1e4c8b6aa4 alice Public key: 19a8a55d65bfbc1c92f90d977dd4f3ec6c27be60391628c1b8cfc6db415383d1,5404ffda817c57158c4e675381df07b99630f28b458d8a61ad69ea9293f52cf0 bob Private key: 58e8a2ae6af497b7f5315eb7f1d3d1034ab590ffce1635979833b3cc86a12611 bob Public key: 51a202aafb4bfd0b94737cc659dc8ad83a6aa46a22651b975d77c6880c7310c4,3ec0d6e0ce78dd3825740b5605ff2f2cfd6fa39d5d1fe683889d0b6bd5bb4a83 Decrypted: Hello123