I/O of Keys and Ciphertexts Using HElib

When using a public key cryptosystem, it is common to have the public key available for other parties to encrypt messages. Usually the tutorials using HElib focus on a particular operation and do not consider that the encryption and decryption may be performed by different parties. This post will focus on three steps related to the I/O of keys and ciphertexts using HElib:

  1. Generate and output the public and secret keys to a file;
  2. Read the public key from a file and use it to encrypt a message, writing the ciphertext to a file;
  3. Read a ciphertext from a file and use the secret key to decrypt it.

Although HElib already comes with an example of I/O, I found it a bit too complex for a first view on how to do I/O. For this reason, I tried to divide the above steps in different programs and run them separately as if they were done by different parties. The complete code is available at GitHub.

For the rest of the post, let’s assume the existence of two parties: Alice and Bob. Bob wants to send a message to Alice and decides to use HElib to encrypt the message.

Step 1: Keys Generation

The first step (keyGenerator.cpp) consists in Alice generating the secret and public keys that will be used in the protocol. After doing the usual initialization when working with HElib (i.e., setting parameters and generating the context from those parameters), the first thing that should be exported to file is the context. The context will be used to recreate the keys when reading them from the files.

// Files that will contain the public and secret keys
fstream secKeyFile("seckey.txt", fstream::out|fstream::trunc);
fstream pubKeyFile("pubkey.txt", fstream::out|fstream::trunc);

// Write the context to the files that will contain the keys
// The context information is used to recreate the keys later
writeContextBase(secKeyFile, context);
writeContextBase(pubKeyFile, context);
secKeyFile << context << std::endl;
pubKeyFile << context << std::endl;

After that, Alice can generate the keys and export them as well to the created files (which already contain the context):

// Generates the secret key
FHESecKey secretKey(context);
const FHEPubKey&amp; publicKey = secretKey;

// Writes both the secret and the public keys to files
secKeyFile << secretKey << std::endl;
pubKeyFile << publicKey << std::endl;

This step will generate two new files: seckey.txt and pubkey.txt. Alice can then send the pubkey.txt file to Bob so he encrypts the message to be transmitted. This encryption is done in the second step.

Step 2: Encryption

The second step (encrypt.cpp) considers that Bob received Alice’s public key, and now he wants to encrypt a message (message.txt) using this key. Before reading the public key itself, Bob needs to read the context information that will be used to construct the context of this public key:

// Parameters needed to reconstruct the context
unsigned long m, p, r;
vector<long> gens, ords;

fstream pubKeyFile("pubkey.txt", fstream::in);

// Initializes a context object with some parameters from the file
readContextBase(pubKeyFile, m, p, r, gens, ords);
FHEcontext context(m, p, r, gens, ords);

// Reads the context itself
pubKeyFile >> context;

Next, Bob can initialize a public key object using the context and read the public key from the file as well:

FHEPubKey publicKey(context);
pubKeyFile >> publicKey;

After that, Bob encode the plaintext message in a vector of integers and encrypt it. The encoding of the message can vary (e.g., hash n-grams into a Bloom filter) and I don’t cover that in this post. The size of the vector depends on the size of the EncryptedArray, which is defined by the initialization parameters:

// Creates the EncryptedArray object based on the context previously read from the file
EncryptedArray ea(context);
uint nslots = ea.size();

std::fstream messageFile("message.txt", fstream::in);

// Tokenizes the message (a collection of integers)
// Each integer will be in a different position in the array to be encrypted
uint count = 0;
std::vector<long int> plaintext(nslots, 0);
for (;;) {
  std::string token;
  if(!(messageFile>>token)) break;
  plaintext[count] = std::stoi(token);

// Encrypts the plaintext vector
Ctxt ctxt(publicKey);
ea.encrypt(ctxt, publicKey, plaintext);

The last part of this step consists in outputting the ciphertext to a file (ciphertext.txt):

std::fstream ciphertext("ciphertext.txt", fstream::out|fstream::trunc);
ciphertext << ctxt;

So now that Bob that created the ciphertext, he can send it to Alice. In the last step, she will then use her secret key to decrypt this ciphertext and obtain the original message that Bob wanted to transmit.

Step 3: Decryption

Alice starts the third step (decrypt.cpp) by reading the file containing the secret key that she generated previously in Step 1. This is very similar to what Bob did when reading the public key:

// Parameters used to reconstruct the context
unsigned long m, p, r;
vector<long> gens, ords;

// Read the context to reconstruct the secret key
fstream secKeyFile("seckey.txt", fstream::in);
readContextBase(secKeyFile, m, p, r, gens, ords);
FHEcontext context(m, p, r, gens, ords);
secKeyFile >> context;

// Initializes the secret key object using the context and reads the key from the file
FHESecKey secretKey(context);
const FHEPubKey&amp; publicKey = secretKey;
secKeyFile >> secretKey;

Note that the public key was not read, but it was generated from the context. It will be used to initialize a ciphertext object later.

Alice then reads the ciphertext to be decrypted:

// Initializes a ciphertext object using the public key
fstream ciphertextFile("ciphertext.txt", fstream::in);
Ctxt ctxt(publicKey);
ciphertextFile >> ctxt;

Finally, Alice decrypts the ciphertext using a EncryptedArray that was constructed with the context she also read from the secret key file. She can check the decrypted vector to see the message that Bob wanted to send her:

EncryptedArray ea(context);
long nslots = ea.size();

std::vector<long int> decrypted(nslots, 0);
ea.decrypt(ctxt, secretKey, decrypted);

Final Notes

This example can be improved in many ways (e.g., the coding and the protocol itself), but I believe it shows the basics of I/O of keys and ciphertexts using HElib.

Regarding the files created (seckey.txt, pubkey.txt and ciphertext.txt), they can be very large, and their sizes depend on the initialization parameters.

As always, the comments section welcomes questions and suggestions, and the code is available at GitHub.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s