Log In Sign Up

An Attack on the the Encryption Scheme of the Moscow Internet Voting System

by   Alexander Golovnev, et al.

The next Moscow City Duma elections will be held on September 8th with an option of Internet voting. Some source code of the voting system is posted online for public testing. Pierrick Gaudry recently showed that due to the relatively small length of the key, the encryption scheme could be easily broken. This issue has been fixed in the current version of the voting system. In this note we show that the new implementation of the ElGamal encryption system is not semantically secure. We also demonstrate how this newly found security vulnerability can be potentially used for counting the number of votes cast for a candidate.


page 1

page 2

page 3

page 4


Breaking the encryption scheme of the Moscow internet voting system

In September 2019, voters for the election at the Parliament of the city...

UC Modelling and Security Analysis of the Estonian IVXV Internet Voting System

Estonian Internet voting has been used in national-wide elections since ...

Pakistan's Internet Voting Experiment

Pakistan recently conducted small-scale trials of a remote Internet voti...

An analysis of Coggia-Couvreur attack on Loidreau's rank-metric public key encryption scheme in the general case

In this paper we show that in the case where the public-key can be disti...

A code-based hybrid signcryption scheme

A key encapsulation mechanism (KEM) that takes as input an arbitrary str...

An algorithm for a fairer and better voting system

The major finding, of this article, is an ensemble method, but more exac...

Blind proxy voting

A secret ballot mechanism that enables voting in absence is proposed. It...

1 Introduction

The Internet voting system developed for the Moscow City Duma elections had a public testing during the month of July. Some of the system’s code was posted online [git19], and the organizers asked the public to test several attack scenarios [Pub19]. The system is poorly documented, but from the source code and brief descriptions of the system [Kri19], we know that it uses the Etherium blockchain and ElGamal encryption. In one of the attack scenarios, the organizers publish a challenge consisting of the public key and some encrypted messages.111 and

Recently Pierrick Gaudry [Gau19a] discovered that the implemented encryption system used a concatenation of three ElGamal encryptions each with a key of length 256 bits. Gaudry showed that since the keys were too short, each private key could be retrieved in minutes. Moreover, Gaudry provided a very short and beautiful script computing the secret keys from the public keys. In the same note, Gaudry remarked that the implemented version of ElGamal worked in groups of even order, which means that it leaked a bit of the message. Both of these issues have been fixed in the current version of the Internet voting system. Gaudry [Gau19b] acknowledged that these specific issues have been fixed, but he is still doubtful about the security of the system. (In particular, he says [Gau19b] that he cannot properly test the system due to the lack of documentation, and is concerned about potential flaws caused by the recent big changes fixing the key length issue.)

In this note we show that the new implementation of the encryption system also leaks a bit of the message. This is caused by the usage of ElGamal where the message is not mapped to the cyclic group under consideration. We show that this flaw potentially can be used for counting the number of votes cast for a candidate, which is illegal (until the end of the election period).

After the publication of this note, Pierrick Gaudry told us that he did warn the developers of the system that they should make sure that the system design does not allow attackers to encrypt elements outside of the group. As we show in this note, even the messages generated by the system (let alone an attacker) do not necessarily belong to the group.

2 Attack

The current implementation of the electronic voting system uses the ElGamal public key encryption scheme. The source code is available in [git19], and the encoding procedure is provided in Appendix A. Below we summarize the current implementation.

Let and be primes of length about bits, and let be the group of quadratic residues modulo . Note that . The system picks a generating . Let and be the public and secret key respectively, and . A message is encoded as a pair

where is a random number.

The problem with the current implementation is that is allowed to be any integer from which is naturally mapped to an element of . For semantic security (under the Decisional Diffie-Hellman assumption), the message should instead be hashed to one of the elements of the group generated by . In the case when is not necessarily picked from the group of quadratic residues, the Decisional Diffie-Hellman assumption does not hold. Moreover, such a system is not semantically secure.

If is a quadratic residue, then for every choice of randomness of the encryption algorithm , the second component of its encoding is a quadratic residue. Indeed, if , then for , which implies that

Similarly, if is not a quadratic residue, then is not a quadratic residue either.

Due to Euler’s criterion, there is an efficient way to distinguish the two cases: for a prime is a quadratic residue if and only if . Since this exponentiation can be performed in time , this gives an efficient distinguishing attack.

In order to see that the implemented code indeed applies ElGamal to elements outside of the group generated by , one can apply Euler’s criterion to the published encrypted messages. In Appendix B we provide Python code showing that exactly five out of the ten published messages are quadratic residues modulo .

3 Example

Due to the lack of documentation and only some code available online, we cannot say for sure which parts of ballots are encrypted. The following lines of code suggest that only the “deputy id” field might be encrypted222See the following links for the code on github: and :

var dataToEncrypt = parseInt($(this).data(’value’));
return signer.getSignedTransaction(votingId, dataToEncrypt, entropy, encryptor);


<input class="bulletin__radio" type="radio" name="deputy" value="{$}" />

The “deputy id” field stores the id of the voter’s chosen candidate. While the attack distinguishes two identical ballots with different votes with high probability regardless of which parts of the ballot are encrypted, for simplicity in this example we will assume that only the “deputy id” field is encrypted. Also, for this example we take the currently published group


If the number of candidates is , and the deputy ids are integers from to , then the adversary can easily count the number of votes cast for candidate number 2. (In particular, if there are only two candidates, then the adversary can count the total number of votes for each of the candidates).

Indeed, since and are quadratic residues modulo , and is not, it suffices to raise the encrypted message to the power to check whether the vote is cast for candidate number . In Appendix C we give Python code emulating the implemented encryption system, and give an example of how to count the number of encryptions of the message among encryptions of messages from to .

We emphasize that this attack will distinguish (with high probability) two messages that differ only in their vote, regardless of the values of the deputy ids and which fields of the ballots are encrypted. Let be the number of candidates. Note that exactly half of the elements of

are quadratic residues. Assuming the uniform distribution of the plain messages we have that the probability of distinguishing a vote for one candidate from the votes for other candidates is

for , and is for .

4 Suggested Fix

The described security vulnerability is a major issue of the implemented cryptographic scheme. We note that the provided attack does not recover the secret key as required by the public testing scenario [Pub19], but rather breaks the system without recovering the secret key.

On the other hand, we believe that there might be an easy way to fix this issue. Since the current implementation already assumes that the message (see line 13 in the code block in Appendix A), one can use a trivial hashing procedure to map each such message to the group of quadratic residues.

In the encoding procedure, we suggest to check whether . If the equality holds, then is not a quadratic residue, but is. Therefore, one can set and continue the encoding procedure.

In the decoding procedure, an analogous step will be required. If the decoded message , then it also should be negated ().


I would like to thank Pierrick Gaudry and Noah Stephens-Davidowitz for their comments on an earlier version of this note.


Appendix A Current Implementation of ElGamal

Below we provide the current implementation of the ElGamal encryption in the Internet voting system.333

async encrypt(data, entropy) {
    if (!data) {
      throw new Error(’Data must present!’);
    if (!entropy) {
      throw new Error(’Entropy must present!’);
    const dataAsBI = new BigInt(data.toString());
    const entropyAsBI = new BigInt(entropy.toString());
    if (dataAsBI.compareTo(this.Q) >= 0) {
      throw new Error(’Data to encrypt can not be bigger or equal that (P-1)/2!’);
    if (entropyAsBI.compareTo(BigInt.ONE) <= 0) {
      throw new Error(’Entropy for Session Key must be Integer bigger than 1!’);
    if (entropyAsBI.compareTo(this.moduleP) >= 0) {
      throw new Error(’Entropy for Session Key can not be bigger or equal Basis Module P!’);
    const randomBigInt = await getRandomBigInt(BigInt.ONE, this.moduleP.subtract(BigInt.ONE));
    const xoredRandomBigInt = randomBigInt.xor(entropyAsBI);
    const sessionKey = trimBigInt(xoredRandomBigInt, this.moduleP.bitLength() - 1);
    const sharedKey = this.publicKey.modPow(sessionKey, this.moduleP);
    const a = this.generatorG.modPow(sessionKey, this.moduleP).toString();
    const b = sharedKey
    return { a, b };

Appendix B Published Encrypted Messages are Not Quadratic Residues

In this Appendix we use the provided public key and encrypted messages444 and to show that not all messages are quadratic residues in . Here is the set of the second components of the encrypted messages (that is, each element of the set is where is some plain message, is the public key, and is a random number). The code shows that only five out of ten elements are quadratic residues (give 1 when raised to the power ).

p = 10062759081450625618037903678618826196600591242500860802791085970455088296159&\break&14188038720723057459046019130152450978128758867982127126946624453236782013843&\break&59740027439588690880234391145675099291004487668846511981135309331094869021425&\break&403957856145722681330313515482620918593602329299394441379077427748866822254003
q = (p-1)//2
c2 = [
for i in range(len(c2)):
    print(pow(c2[i], q, p))

Appendix C Example

Here we show that with high probability one can distinguish encryptions of one message from the encryptions of other messages. In this example we use the currently provided public key for the ElGamal system.555 We generate a hundred of random encryptions of the messages from to . Then without the knowledge of the secret key, we correctly recover the number of encryptions of the message .

import random
p = 10062759081450625618037903678618826196600591242500860802791085970455088296159&\break&14188038720723057459046019130152450978128758867982127126946624453236782013843&\break&59740027439588690880234391145675099291004487668846511981135309331094869021425&\break&403957856145722681330313515482620918593602329299394441379077427748866822254003
q = (p-1)//2
g = 20938162663634717592050150871604811965450185147804653686210341370048875526465&\break&65192598809608149897753169371105819433388566048465513249705171128931623257482&\break&20949810611902707966342172282736385317307210244235915353395023927031589746870&\break&88258365877782810885461860894240185695001397437423927797525752628688868571010
pk = 69869939556699578412481990448414288405398435179055114398103563207158851026044&\break&17268760023814098454244173381151935563527447828022461894488632630303059380694&\break&21286985815829955355067923635563794223005965324347558568201679805005654774823&\break&09964287055106318673831750120449183092965628809034498878410377075732012129326
def encrypt(data):
    if (data >= q):
      print(’Data to encrypt can not be bigger or equal that (P-1)/2!’)
    sessionKey = random.randint(1, p-1)
    sharedKey = pow(pk, sessionKey, p)
    a = pow(g, sessionKey, p)
    b = sharedKey * data % p
    return (a, b)
#number of candidates is 4, their ids are {1, 2, 3, 4}.
count = [0] * 5
encryptions = []
for i in range(100):
    candidate = random.randint(1, 4)
    count[candidate] += 1
for i in range(1, len(count)):
    print(’Number of votes for candidate %d is %d % (i, count[i]))
notQResidues = 0
for (a, b) in encryptions:
    if pow(b, q, p) != 1:
        notQResidues += 1
print(’\nNumber of encrypted votes for candidate 2 is %d % notQResidues)