Homomorphic encryption is a special type of encryption that lets you do calculations on encrypted values as if they weren’t encrypted. One reason it’s desired is that secure computing could be done in the cloud, if practical homomorphic encryption were available.

Homomorphic encryption has been a hot research topic since 2009, when Craig Gentry figured out a way to do it while working on his PhD. Since then, people have been working on making it better, faster and more efficient.

You can read more about a basic incarnation of his ideas in my blog posts:

Super Simple Symmetric Leveled Homomorphic Encryption Implementation

Improving the Security of the Super Simple Symmetric Leveled Homomorphic Encryption Implementation

This post is about a low tech type of homomorphic encryption that anyone can easily do and understand. There is also some very simple C++ that implements it.

This idea may very well be known about publically, but I’m not aware of any places that talk about it. I may just be ignorant of them though so ::shrug::

Quick Update

I’ve gotten some feedback on this article, the most often feedback being that this is obfuscation not encryption. I think that’s a fair assessment as the secret value you are trying to protect is in no way transformed, but is just hidden. This post could easily be titled Homomorphic Obfuscation, and perhaps should be.

To see other feedback and responses to this post, check out the reddit links at the bottom!

The Idea

The idea is actually super simple:

Take the value you want to encrypt. Hide it in a list of a bunch of other random values, and remember where it is in the list. The position in the list is your key. Send this list to an untrusted party. They do the same calculation on every item in the list and send it back. Since you know which value was your secret value, you know which answer is the one you care about.

At the end of that process, you have the resulting value, and they have no idea what value was your secret value. You have done, by definition, homomorphic encryption!

There is a caveat of course… they know that your secret value was ONE of the values on the list.

Security Details

The thing here is that security is a sliding scale between resource usage (computation time, RAM, network bandwidth, etc) and security.

The list size is your security parameter in this case.

A larger list of random values means that it takes longer to transfer the data, more memory to store it, it takes longer to do the homomorphic computations, but the untrusted party is less sure about what your secret value is.

On the other hand, a shorter list is faster to transmit, easier to store, quicker to compute with, but the untrusted party has a better idea what your secret value is.

For maximal security you can just take this to the extreme – if your secret value is a 32 bit floating point number, you could make a list with all possible 2^32 floating point numbers in it, have them do the calculation and send it back. You can even do an optimization here and not even generate or send the list, but rather just have the person doing the calculations generate the full 2^32 float list, do the calculations, and send you the results.

That gets pretty big pretty fast though. That list would actually be 16 gigabytes, but the untrusted party would know almost nothing about your value, other than it can be represented by a 32 bit floating point number.

Depending on your security needs, you might be ok with shortening your list a bit to bring that number down. Making your list only be one million numbers long (999,999 random numbers and one value you actually care about), your list is only 3.8 megabytes.

Not quite as bad.

Some Interesting Abilities

Using this homomorphic encryption, like other homomorphic encryption, you can do computation involving multiple encrypted values. AKA you could multiply two encrypted values together. To do this, you are going to need to encrypt all values involved using the same key. In other words, they are going to have to be at the same index in each of their respective lists of random numbers.

Something else that is interesting is that you can also encode MULTIPLE secret values in your encrypted value list. You could have 1 secret value at index 50 and another at index 100 for instance. Doing this, you get a sort of homomorphic SIMD setup.

Homomorphic SIMD is actually a real thing in other homomorphic encryption methods as well. Check out this paper for instance:

Fully Homomorphic SIMD Operations

The only problem with homomorphic SIMD is that adding more secret values to the same encrypted list decreases the security, since there are more values in the list that you don’t want other people to know about.

You can of course also modify encrypted values by unencrypted values. You could multiply an encrypted value by 3, by multiplying every value in the list by 3.

Extending to Public Key Cryptography

If you wanted to use asymmetric key cryptography (public/private keys) instead of symmetric key cryptography, that is doable here too.

What you would do is have the public key public as per usual, and that key would be used in a public key algorithm to encrypt the index of the secret value in the random list.

Doing this, the person who has the private key would be able to receive the list and encrypted index, decrypt the index, and then get the secret value out using that index.

Sample Code Tests

The sample code only does Symmetric key encryption, and does these 3 tests:

Encrypts two floating point numbers into a single list, SIMD style, does an operation on the encrypted values, then unencrypts and verifies the results. Does the same with two sets of floats (three floats in each set), to show how you can make encrypted values interact with each other. Does the operation, then unencrypts and verifies the results. Encrypts three values of a 3 byte structure, does an operation on the encrypted values, then unencrypts and verifies the results.

All secret data was hidden in lists of 10,000,000 random values. That made the first two tests (the ones done with 4 byte floats) have encrypted files of 38.1MB (40,000,000 bytes), and the last test (the one done with a 3 byte struct) had a file size of 28.6 MB (30,000,000 bytes).

Here are the timing of the above tests:

Sample Code

Here is the header, LTHE.h:

/* Written by Alan Wolfe http://blog.demofox.org Tweets by Atrix256

And here is the test program, main.cpp:

#include <stdio.h> #include "LTHE.h" #include <chrono> //================================================================================= // times a block of code struct SBlockTimer { SBlockTimer() { m_start = std::chrono::high_resolution_clock::now(); } ~SBlockTimer() { std::chrono::duration<float> seconds = std::chrono::high_resolution_clock::now() - m_start; printf(" %0.2f secondsn", seconds.count()); } std::chrono::high_resolution_clock::time_point m_start; }; //================================================================================= float TransformDataUnitary (float& value) { return (float)sqrt(value * 2.17f + 0.132); } //================================================================================= float TransformDataBinary (float& value1, float value2) { return (float)sqrt(value1 * value1 + value2 * value2); } //================================================================================= struct SStruct { uint8_t x, y, z; static SStruct Transform (const SStruct& b) { SStruct ret; ret.x = b.x * 2; ret.y = b.y * 3; ret.z = b.z * 4; return ret; } bool operator != (const SStruct& b) const { return b.x != x || b.y != y || b.z != z; } }; //================================================================================= int Test_FloatUnitaryOperation () { printf("n----- " __FUNCTION__ " -----n"); // Encrypt the data printf("Encrypting data: "); std::vector<float> secretValues = { 3.14159265359f, 435.0f }; std::vector<size_t> keys; { SBlockTimer timer; if (!LTHE::Encrypt(secretValues, 10000000, "Encrypted.dat", keys)) { fprintf(stderr, "Could not encrypt data.n"); return -1; } } // Transform the data printf("Transforming data:"); { SBlockTimer timer; if (!LTHE::TransformHomomorphically<float>("Encrypted.dat", "Transformed.dat", TransformDataUnitary)) { fprintf(stderr, "Could not transform encrypt data.n"); return -2; } } // Decrypt the data printf("Decrypting data: "); std::vector<float> decryptedValues; { SBlockTimer timer; if (!LTHE::Decrypt("Transformed.dat", decryptedValues, keys)) { fprintf(stderr, "Could not decrypt data.n"); return -3; } } // Verify the data printf("Verifying data: "); { SBlockTimer timer; for (size_t i = 0, c = secretValues.size(); i < c; ++i) { if (TransformDataUnitary(secretValues[i]) != decryptedValues[i]) { fprintf(stderr, "decrypted value mismatch!n"); return -4; } } } return 0; } //================================================================================= int Test_FloatBinaryOperation () { printf("n----- " __FUNCTION__ " -----n"); // Encrypt the data printf("Encrypting data: "); std::vector<float> secretValues1 = { 3.14159265359f, 435.0f, 1.0f }; std::vector<float> secretValues2 = { 1.0f, 5.0f, 9.0f }; std::vector<size_t> keys; { SBlockTimer timer; if (!LTHE::Encrypt(secretValues1, 10000000, "Encrypted1.dat", keys)) { fprintf(stderr, "Could not encrypt data.n"); return -1; } if (!LTHE::Encrypt(secretValues2, 10000000, "Encrypted2.dat", keys, false)) // reuse the keys made for secretValues1 { fprintf(stderr, "Could not encrypt data.n"); return -1; } } // Transform the data printf("Transforming data:"); { SBlockTimer timer; if (!LTHE::TransformHomomorphically<float>("Encrypted1.dat", "Encrypted2.dat", "Transformed.dat", TransformDataBinary)) { fprintf(stderr, "Could not transform encrypt data.n"); return -2; } } // Decrypt the data printf("Decrypting data: "); std::vector<float> decryptedValues; { SBlockTimer timer; if (!LTHE::Decrypt("Transformed.dat", decryptedValues, keys)) { fprintf(stderr, "Could not decrypt data.n"); return -3; } } // Verify the data printf("Verifying data: "); { SBlockTimer timer; for (size_t i = 0, c = secretValues1.size(); i < c; ++i) { if (TransformDataBinary(secretValues1[i], secretValues2[i]) != decryptedValues[i]) { fprintf(stderr, "decrypted value mismatch!n"); return -4; } } } return 0; } //================================================================================= int Test_StructUnitaryOperation () { printf("n----- " __FUNCTION__ " -----n"); // Encrypt the data printf("Encrypting data: "); std::vector<SStruct> secretValues = { {0,1,2},{ 3,4,5 },{ 6,7,8 } }; std::vector<size_t> keys; { SBlockTimer timer; if (!LTHE::Encrypt(secretValues, 10000000, "Encrypted.dat", keys)) { fprintf(stderr, "Could not encrypt data.n"); return -1; } } // Transform the data printf("Transforming data:"); { SBlockTimer timer; if (!LTHE::TransformHomomorphically<SStruct>("Encrypted.dat", "Transformed.dat", SStruct::Transform)) { fprintf(stderr, "Could not transform encrypt data.n"); return -2; } } // Decrypt the data printf("Decrypting data: "); std::vector<SStruct> decryptedValues; { SBlockTimer timer; if (!LTHE::Decrypt("Transformed.dat", decryptedValues, keys)) { fprintf(stderr, "Could not decrypt data.n"); return -3; } } // Verify the data printf("Verifying data: "); { SBlockTimer timer; for (size_t i = 0, c = secretValues.size(); i < c; ++i) { if (SStruct::Transform(secretValues[i]) != decryptedValues[i]) { fprintf(stderr, "decrypted value mismatch!n"); return -4; } } } return 0; } //================================================================================= int main (int argc, char **argv) { // test doing an operation on a single encrypted float int ret = Test_FloatUnitaryOperation(); if (ret != 0) { system("pause"); return ret; } // test doing an operation on two encrypted floats ret = Test_FloatBinaryOperation(); if (ret != 0) { system("pause"); return ret; } // test doing an operation on a single 3 byte struct ret = Test_StructUnitaryOperation(); if (ret != 0) { system("pause"); return ret; } printf("nAll Tests Passed!nn"); system("pause"); return 0; }

If you found this post interesting or useful, or you have anything to add or talk about, let me know!

Reddit discussion:

r/programming

r/cryptography