Summary
I found a vulnerability in the implementation of Shamir's secret sharing scheme. The vulnerability allows an attacker to recover the mnemonic if the attacker knows t' out of t needed shares provided
t - t' is low and
t is high.
For example, the entropy of the secret is reduced by half if t' = 15 and t = 16. The vulnerability is due to the way the random numbers are generated.
Introduction
Shamir secret sharing
Shamir secret sharing scheme allows you to split a secret into n shares such that every t shares can be used to reconstruct the secret. The scheme makes use of Lagrange interpolation theorem that says that given t points in the plane (such that no two points has the same x coordinate) there exists exactly one polynomial that goes through the points. The secret is represented by the constant term of a degree t - 1 polynomial and the shares are represented by the points in the plane. Any t points are enough to reconstruct the polynomial and thus the secret.
Polynomial generation
Let me summarize how the shares are generated in your implementation. Suppose we have a one-byte secret s that we want to split into n shares such that each t (but no t-1) shares is enough to reconstruct the secret. The process is the following:
- Let
p be a polynomial of degree t - 1 over GF(256) such that
1.1. its constant coefficient is s and
1.2. the remaining t - 1 coefficients are generated uniformly at random from GF(256).
- Let
x_1, ..., x_n be a list of pairwise distinct non-zero elements from GF(256).
- For each
i in 1, ..., n let y_i be p(x_i) (evaluated in GF(256)).
- The shares are
(x_1, y_1), ..., (x_n, y_n).
When the secret has multiple bytes, the polynomial is generated independently for each byte, but x_1, ..., x_n are the same for each polynomial.
Vulnerability
Coefficients generation
The vulnerability lies in the fact that the coefficients aren't generated uniformly at random from GF(256). This is how a coefficient c is generated:
a = random.SystemRandom().randint(0, 255)
i = int((time.time() % 0.0001) * 1000000) + 1
c = (a * i) % 255
In the language of mathematics:
a is drawn uniformly at random from 0, ..., 255.
i is drawn from 1, ..., 100. Although it is not drawn uniformly at random, for the sake of simplicity let's suppose it is.
- The coefficient is
a * i % 255.
The procedure suffers from the following two vulnerabilities:
- The coefficient is never 255.
- The result of
(a * i) % 255 is not uniformly distributed over 1, ..., 254.
First vulnerability
Since the coefficient is the remainder of a * i after division by 255, the coefficient is never 255. That means the coefficient is not generated uniformly.
Let's turn this vulnerability into an attack. Assume an attacker knows t - 1 shares out of t shares needed to reconstruct a one-byte secret. The attacker guesses the remaining share and reconstructs the polynomial. If the guess is correct, no polynomial coefficient except for the constant one is equal to 255. In other words, if the polynomial has a coefficient except for the constant one that is equal to 255, the guess is incorrect. The probability that no coefficient is equal to 255 provided the guess is incorrect is (255 / 256) ^ (t - 1). That means the attacker is able eliminate at average 256 * (255 / 256) ^ (t - 1) out of 256 possible guesses. In other words, the entropy of the one-byte secret is lowered by (t - 1) * log2(256 / 255) bits.
Since the individual bytes of a multiple-byte secret are shared separately, the entropy of a l-bit secret is lowered by l / 8 * (t - 1) * log2(256 / 255) bits. Even if there where as much as 256 shares, the entropy of a 128-bit secret would be reduced to 105 bits, which is not so bad.
Second vulnerability
Multiplication modulo a non-prime number is a tricky operation, since the function that maps x to x * y % m is a permutation of the set {1, ..., m - 1} if and only if y is relative prime to m. Consequently, even if both x and y are generated uniformly at random from the set {1, ..., m - 1}, the result of the operation x * y % m isn't uniformly distributed over {1, ..., m - 1} unless m is a prime.
Suppose for example that m = 6 and x, y are distributed uniformly on the set {1, ..., 6}. This is the multiplication table x, y -> x * y % 6:
| y/x |
1 |
2 |
3 |
4 |
5 |
| 1 |
1 |
2 |
3 |
4 |
5 |
| 2 |
2 |
4 |
0 |
2 |
4 |
| 3 |
3 |
0 |
3 |
0 |
3 |
| 4 |
4 |
2 |
4 |
2 |
4 |
| 5 |
5 |
4 |
3 |
2 |
1 |
The probability distribution of the result is:
| x * y % m |
0 |
1 |
2 |
3 |
4 |
5 |
| probability |
0.16 |
0.08 |
0.24 |
0.20 |
0.24 |
0.08 |
Not only is the result not uniformly distributed, it's not even distributed over {1, ..., m -1}!
It can be calculated that the entropy of a coefficient generated by your implementation is about 7.72 bits. Although it seems to be sufficient, the conditional entropy of a secret provided the attacker knows t - 1 out of t needed shares decreases as t increases.
First, suppose the secret has a single byte. The attacker guesses the remaining share and reconstructs the polynomial. If the guess is correct, each coefficient of the polynomial but the constant one follows the distribution with the entropy of 7.72 bits. If the guess is not correct, the coefficients are distributed approximately uniformly. Based on this fact, the attacker is able to calculate for each guess the probability that the guess is correct. These probabilities define a probability distribution of the secret. The key question is: What is the entropy of the distribution?
I wrote a program that approximates the entropy using Monte Carlo simulation. The following graph shows the dependency between t and the entropy of a single byte secret conditioned on the knowledge of t - 1 out of t needed shares:

Note that the graph is jagged, this is because the simulation approximates (rather than calculates) the entropy.
Without proof, this is an upper bound for the entropy of a single byte secret conditioned on the kowledge of t' out of t needed shares:

Now, suppose the secret has multiple bytes. Since the individual bytes of the secret are independent, the entropy of the whole secret is the sum of the entropies of the individual bytes. This is a bit more complicated from the attacker's perspective. The attacker is able to compute a probability distribution for every single byte of the secret. The complexity of such the computation is linear in the length of the secret. Since the individual bytes of the secret are independent, the probability of a secret is the product of the probabilities of the individual bytes. The attacker now tries to guess the secret starting from the most probable one. This is the trickiest part, since the attacker cannot enumerate all the secrets. The smaller the entropy of the secret, the sooner the attack succeeds.
Summary
I found a vulnerability in the implementation of Shamir's secret sharing scheme. The vulnerability allows an attacker to recover the mnemonic if the attacker knows
t'out oftneeded shares providedt - t'is low andtis high.For example, the entropy of the secret is reduced by half if
t' = 15andt = 16. The vulnerability is due to the way the random numbers are generated.Introduction
Shamir secret sharing
Shamir secret sharing scheme allows you to split a secret into
nshares such that everytshares can be used to reconstruct the secret. The scheme makes use of Lagrange interpolation theorem that says that giventpoints in the plane (such that no two points has the same x coordinate) there exists exactly one polynomial that goes through the points. The secret is represented by the constant term of a degreet - 1polynomial and the shares are represented by the points in the plane. Anytpoints are enough to reconstruct the polynomial and thus the secret.Polynomial generation
Let me summarize how the shares are generated in your implementation. Suppose we have a one-byte secret
sthat we want to split intonshares such that eacht(but not-1) shares is enough to reconstruct the secret. The process is the following:pbe a polynomial of degreet - 1over GF(256) such that1.1. its constant coefficient is
sand1.2. the remaining
t - 1coefficients are generated uniformly at random from GF(256).x_1, ..., x_nbe a list of pairwise distinct non-zero elements from GF(256).iin1, ..., nlety_ibep(x_i)(evaluated in GF(256)).(x_1, y_1), ..., (x_n, y_n).When the secret has multiple bytes, the polynomial is generated independently for each byte, but
x_1, ..., x_nare the same for each polynomial.Vulnerability
Coefficients generation
The vulnerability lies in the fact that the coefficients aren't generated uniformly at random from GF(256). This is how a coefficient
cis generated:In the language of mathematics:
ais drawn uniformly at random from0, ..., 255.iis drawn from1, ..., 100. Although it is not drawn uniformly at random, for the sake of simplicity let's suppose it is.a * i % 255.The procedure suffers from the following two vulnerabilities:
(a * i) % 255is not uniformly distributed over1, ..., 254.First vulnerability
Since the coefficient is the remainder of
a * iafter division by 255, the coefficient is never 255. That means the coefficient is not generated uniformly.Let's turn this vulnerability into an attack. Assume an attacker knows
t - 1shares out oftshares needed to reconstruct a one-byte secret. The attacker guesses the remaining share and reconstructs the polynomial. If the guess is correct, no polynomial coefficient except for the constant one is equal to 255. In other words, if the polynomial has a coefficient except for the constant one that is equal to 255, the guess is incorrect. The probability that no coefficient is equal to 255 provided the guess is incorrect is(255 / 256) ^ (t - 1). That means the attacker is able eliminate at average256 * (255 / 256) ^ (t - 1)out of 256 possible guesses. In other words, the entropy of the one-byte secret is lowered by(t - 1) * log2(256 / 255)bits.Since the individual bytes of a multiple-byte secret are shared separately, the entropy of a
l-bit secret is lowered byl / 8 * (t - 1) * log2(256 / 255)bits. Even if there where as much as 256 shares, the entropy of a 128-bit secret would be reduced to 105 bits, which is not so bad.Second vulnerability
Multiplication modulo a non-prime number is a tricky operation, since the function that maps
xtox * y % mis a permutation of the set{1, ..., m - 1}if and only ifyis relative prime tom. Consequently, even if bothxandyare generated uniformly at random from the set{1, ..., m - 1}, the result of the operationx * y % misn't uniformly distributed over{1, ..., m - 1}unlessmis a prime.Suppose for example that
m = 6andx,yare distributed uniformly on the set{1, ..., 6}. This is the multiplication tablex, y -> x * y % 6:The probability distribution of the result is:
Not only is the result not uniformly distributed, it's not even distributed over
{1, ..., m -1}!It can be calculated that the entropy of a coefficient generated by your implementation is about 7.72 bits. Although it seems to be sufficient, the conditional entropy of a secret provided the attacker knows
t - 1out oftneeded shares decreases astincreases.First, suppose the secret has a single byte. The attacker guesses the remaining share and reconstructs the polynomial. If the guess is correct, each coefficient of the polynomial but the constant one follows the distribution with the entropy of 7.72 bits. If the guess is not correct, the coefficients are distributed approximately uniformly. Based on this fact, the attacker is able to calculate for each guess the probability that the guess is correct. These probabilities define a probability distribution of the secret. The key question is: What is the entropy of the distribution?
I wrote a program that approximates the entropy using Monte Carlo simulation. The following graph shows the dependency between

tand the entropy of a single byte secret conditioned on the knowledge oft - 1out oftneeded shares:Note that the graph is jagged, this is because the simulation approximates (rather than calculates) the entropy.
Without proof, this is an upper bound for the entropy of a single byte secret conditioned on the kowledge of

t'out oftneeded shares:Now, suppose the secret has multiple bytes. Since the individual bytes of the secret are independent, the entropy of the whole secret is the sum of the entropies of the individual bytes. This is a bit more complicated from the attacker's perspective. The attacker is able to compute a probability distribution for every single byte of the secret. The complexity of such the computation is linear in the length of the secret. Since the individual bytes of the secret are independent, the probability of a secret is the product of the probabilities of the individual bytes. The attacker now tries to guess the secret starting from the most probable one. This is the trickiest part, since the attacker cannot enumerate all the secrets. The smaller the entropy of the secret, the sooner the attack succeeds.