Splitting a Private Key Into Pieces to Share Decryption Among a Group with Homomorphic Encryption

Every encryption scheme has an uncomfortable truth at its center: whoever holds the private key can read everything. That makes the private key a single point of failure in two directions. If it is stolen, or its holder goes rogue, every ciphertext is exposed. If it is lost, every ciphertext is gone forever.

Pirates had a classic answer to the same problem: tear the treasure map into pieces, and give each crew member one. Nobody can sail off with the gold alone. But a torn map has two flaws. Every piece still shows part of the route, so a crew member with a good piece and a good guess might get there first. And if a single crew member walks the plank with their piece, nobody finds the treasure ever again, because every piece is needed.


🙋‍♂️ You may consider to enroll my top-rated cryptography course on Udemy

Public Key Cryptography From Scratch

Close-up of a vintage map displaying the Indian Ocean under warm lighting, evoking a sense of exploration by pexels

Cryptography has a much better way to tear the map, called threshold decryption. The private key is split into n pieces, called shares, so that any t of them are enough to decrypt, while fewer than t reveal nothing at all—not even part of the route. For instance, a board of 5 directors can let any 3 of them decrypt the company’s encrypted data, so neither a single director can read it alone nor losing 2 of them blocks access. In this post, we’ll see how it works and build it with LightPHE in Python, Go, Java and TypeScript.

How Threshold Decryption Works

Splitting the Key with Shamir’s Secret Sharing

The private key is split with Shamir’s secret sharing. A dealer picks a random polynomial of degree t - 1 whose constant term is the secret key, and gives each shareholder one point on it:

f(x) = sk + a_1 x + a_2 x^2 + ... + a_(t-1) x^(t-1)      random a_1, ..., a_(t-1)

share of shareholder i = f(i)                            for i = 1, 2, ..., n

Any t points determine a polynomial of degree t - 1, so any t shareholders could rebuild sk = f(0) with Lagrange interpolation. With only t - 1 points, every possible secret is equally likely, so fewer shareholders learn literally nothing.

This is where our treasure map gets fixed. A torn piece of map shows part of the route, but a single point on a random polynomial says nothing about the polynomial’s value at zero, where the key is hidden, so a share reveals nothing about the key. And since any t points are enough, the crew no longer needs every piece: a 3-of-5 crew can lose 2 members and still find the treasure.

Decrypting Without Ever Rebuilding the Key

Here is the clever part: the shareholders never put the key back together. If they did, whoever assembled it would become the single point of failure again. Instead, each shareholder applies its own share to the ciphertext, producing a partial decryption, and the Lagrange interpolation happens on the partial decryptions, in the exponent. Take ElGamal, whose ciphertext is a pair (c1, c2) and whose decryption computes m = c2 / c1^sk:

partial decryption of shareholder i:   d_i = c1^(share_i)

combining any t of them:               c1^sk = d_1^(λ_1) * d_2^(λ_2) * ... * d_t^(λ_t)
                                       m = c2 / c1^sk

λ_i are the lagrange coefficients of the participating shareholders

A single partial decryption reveals nothing about the plaintext, and combining them needs no secret at all, so anyone holding the public key can be the combiner. The key itself never exists in one place after the split.

Required Cryptographic Features

1. Decryption that is an exponentiation with the private key. The trick above only works if decryption raises something to the power of the private key, as c1^sk in ElGamal or c^d in Paillier. Then shares of the key turn into partial decryptions, and Lagrange coefficients combine them in the exponent.

2. Lagrange coefficients that can be computed. Lagrange coefficients are fractions, so they need division modulo the order of the group. For the ElGamal family, the order is a public prime q, which is why ElGamal and Exponential ElGamal keys must be generated with prime_order = True for threshold decryption. For Paillier and Damgard-Jurik, the order is secret, since it reveals the factorization of the modulus. The fix comes from Shoup’s threshold signatures: multiply every coefficient by Δ = n!, where n is the number of shareholders, which turns them into integers, and divide the extra factor out at the end.

3. Homomorphism, to decrypt only aggregates. Strictly speaking, threshold decryption doesn’t need homomorphic encryption, but together they are far more powerful. Anyone can add up ciphertexts with the public key, and the shareholders decrypt only the final result—a vote tally, a total, an average—never an individual ciphertext. This is exactly what we did in our verifiable referendum, where trustees decrypt the tally, not the ballots.

4. Verifiable partial decryptions. Threshold decryption spreads trust, but it doesn’t make shareholders honest. A cheating shareholder can send a wrong partial decryption, and the combined plaintext will be silently wrong. Worse, since Lagrange coefficients are public, a cheater can shift the result by any amount it likes, e.g. add 50 votes to its own party. So each shareholder can attach a zero-knowledge proof that its partial decryption was computed with its own share, and the combiner can discard any partial decryption whose proof fails. For the ElGamal family, this is a Chaum-Pedersen proof that works with any prime order keys. For Paillier and Damgard-Jurik, it needs keys generated with safe_primes = True.

Which Algorithms Can Be Used, and Why

LightPHE supports threshold decryption for five cryptosystems, the ones with well-established threshold variants. I ran the examples below against all of them, and asked the others for threshold keys as well:

AlgorithmThreshold decryptionNotes
ElGamal✅Multiplicatively homomorphic. Keys need prime_order = True
Exponential ElGamal✅Additively homomorphic. Keys need prime_order = True. Decryption solves a discrete logarithm, so plaintexts must stay small
Elliptic Curve ElGamal✅Additively homomorphic with short keys. The base point of the curve already has a prime order, so no extra parameter is needed. Plaintexts must stay small, too
Paillier✅Additively homomorphic with large plaintexts. Shoup’s Δ trick handles the secret group order. Keys need safe_primes = True to prove partial decryptions
Damgard-Jurik✅Same as Paillier, with an even larger plaintext space
RSA❌Threshold RSA exists for signatures—Shoup’s paper above is exactly that—but RSA encryption is deterministic and only multiplicative, a poor fit for protecting data, and LightPHE doesn’t offer it
Okamoto-Uchiyama, Benaloh, Goldwasser-Micali, Damgard-Geisler-Kroigaard, Boneh-Goh-Nissim and others❌LightPHE refuses them with does not support threshold decryption. Threshold variants of these schemes are either not standard or not implemented in LightPHE, so pick one of the five above when you need a quorum

One more restriction: ciphertexts boosted with Catalano-Fiore to support a multiplication cannot be threshold-decrypted, so threshold decryption works on additive (or, for ElGamal, multiplicative) results only.

Example 1: A Board of Directors

A company’s departments submit encrypted budgets, and the board has 5 directors. Any 3 of them can decrypt the total together, but no director can read it alone, and the board still works when 2 directors are on vacation.





Two different groups of 3 directors get the same 245, and 2 directors get an error, not a wrong number. After generate_threshold_keys, the dealer’s cryptosystem drops its private key in Python, Java and TypeScript. In Go, a cryptosystem is immutable, so the dealer keeps its keys: publish the returned public key and discard the dealer, as the Go tab does. The same code works for the ElGamal family as well: just build the dealer with Exponential-ElGamal and prime_order = True, or with EllipticCurve-ElGamal.

Example 2: Hospitals and a Cheating Shareholder

Five hospitals want to publish the total number of flu patients in their region this year, without revealing their individual numbers. Each hospital encrypts its count, the counts are added up encrypted, and the hospitals hold the 5 shares of the key, any 3 of which can decrypt. But one hospital cheats: instead of its partial decryption of the total, it sends a partial decryption of another ciphertext to distort the published figure.

Without verification, the forged partial decryption doesn’t cause an error—it just produces a meaningless number, over 300 digits long in my run, instead of 5795. A cheater who is more careful than ours could make that number look perfectly plausible. With verification, anyone holding the public key can check each partial decryption and see exactly which hospital cheated, and the combiner discards the forged one and logs a warning: Partial decryption of party 1 has an invalid proof. It is discarded. Since 3 honest hospitals remain, the correct total is still restored. Java snippets use assert for brevity, so run them with -ea to enable assertions.

More Scenarios

  • E-voting: Trustees nominated by competing parties hold the shares of the election key, so no single trustee can decrypt a ballot, and a trustee who tries to add votes to its own party is caught by the proofs.
  • Federated learning: Thousands of phones encrypt their model updates, a server adds them up, and a committee of servers decrypts only the sum, so no single server sees an individual update or slips in a poisoned one.
  • Backup recovery: A company encrypts its backups, and any 3 of 5 executives can restore them. A single compromised laptop doesn’t expose the backups, and losing two keys doesn’t lose the backups either.
  • Auditing: Two of three auditors are required to open the total of a company’s encrypted transactions, so a single bribed auditor can neither peek early nor misreport the total.
  • Sealed-bid auctions: Bids stay encrypted until the auction closes, and only a quorum of independent auctioneers can open them, so no single insider can leak the leading bid.

Caveats

  • The dealer must be trusted: The dealer knows the whole private key while splitting it. LightPHE drops the key right after the split, but if a trusted dealer is not acceptable, the shareholders need to generate the key together with a distributed key generation protocol, which LightPHE doesn’t provide.
  • t colluding shareholders can decrypt anything: The threshold protects against fewer than t shareholders. If t of them collude, they can decrypt any ciphertext, including individual ballots or records. Choose shareholders with competing interests, and have them refuse to partially decrypt anything but aggregates.
  • Shares are static: If shares leak one by one over the years, an attacker eventually collects t of them. Some systems refresh shares periodically without changing the key, which LightPHE doesn’t do, so rotate keys instead when shareholders change.
  • Prove partial decryptions: Without proofs, a single cheating shareholder silently corrupts the result. Turn on prove and verify whenever shareholders don’t fully trust each other, which is usually the reason to use threshold decryption in the first place.

Conclusion

Threshold decryption removes the single point of failure of a private key. Shamir’s secret sharing splits the key into shares, each shareholder decrypts partially with its own share, and Lagrange interpolation in the exponent combines any t partial decryptions into the plaintext, without ever rebuilding the key. That requires a decryption that is an exponentiation with the private key, and Lagrange coefficients that can be computed, which is why LightPHE supports it for ElGamal, Exponential ElGamal, Elliptic Curve ElGamal, Paillier and Damgard-Jurik. Combined with homomorphic encryption, shareholders only ever decrypt aggregates, and with proofs of partial decryption, a cheating shareholder is caught instead of corrupting the result.

In other words: no single person can read your data, no single person can lose it, and no single person can lie about it—all in a few lines of Python, Go, Java or TypeScript.


Support this blog financially if you do like!

Buy me a coffee      Buy me a coffee


Leave a Reply