Skip to content

Commit 5bd36dc

Browse files
authored
Secret Sharing in RSA (#99)
* 📦 Add cryptography package - Demotes hypothesis and typish to dev package * ✨Replace ElGamal with RSA for Auxiliary Key * 🐛Missing TearDown in Decryption tests * 📝Update Documentation for Auxiliary Encrypt/Decrypt override
1 parent 2d45b50 commit 5bd36dc

File tree

10 files changed

+388
-148
lines changed

10 files changed

+388
-148
lines changed

Pipfile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,19 @@ verify_ssl = true
77
black = "*"
88
coverage = "*"
99
docutils = "*"
10+
hypothesis = "==5.15.1"
1011
mkdocs = "*"
1112
mypy = "*"
1213
pydeps = "*"
1314
pydocstyle = "*"
1415
pylint = "*"
1516
pytest = "*"
17+
typish = '*'
1618

1719
[packages]
18-
hypothesis = "==5.15.1"
1920
numpy = '==1.18.2'
2021
jsons = '==1.1.2'
21-
typish = '*'
22+
cryptography = "*"
2223

2324
[requires]
2425
python_version = "3.8"

Pipfile.lock

Lines changed: 141 additions & 69 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/1_Key_Ceremony.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,26 +34,29 @@ This is a detailed description of the entire Key Ceremony Process
3434

3535
1. The ceremony details are decided upon. These include a `number_of_guardians` and `quorum` of guardians required for decryption.
3636
2. Each guardian creates a unique `id` and `sequence_order`.
37-
3. Each guardian must generate their `auxiliary key pair`.
37+
3. Each guardian must generate their `auxiliary key pair`.\*
3838
4. Each guardian must give the other guardians their `auxiliary public key` directly or through a mediator.
3939
5. Each guardian must check if all `auxiliary public keys` are received.
4040
6. Each guardian must generate their `election key pair` _(ElGamal key pair)_. This will generate a corresponding Schnorr `proof` and `polynomial` used for generating `election partial key backups` for sharing.
4141
7. Each guardian must give the other guardians their `election public key` directly or through a mediator.
4242
8. Each guardian must check if all `election public keys` are received.
43-
9. Each guardian must generate `election partial key backup` for each other guardian. The guardian will use their `polynomial` and the designated guardian's `sequence_order` to create the value. The backup will be encrypted with the designated guardian's `auxiliary public key`
43+
9. Each guardian must generate `election partial key backup` for each other guardian. The guardian will use their `polynomial` and the designated guardian's `sequence_order` to create the value. The backup will be encrypted with the designated guardian's `auxiliary public key`\*
4444
10. Each guardian must send each encrypted `election partial key backup` to the designated guardian directly or through a `mediator`.
4545
11. Each guardian checks if all encrypted `election partial key backups` have been received by their recipient guardian directly or through a mediator.
46-
12. Each recipient guardian decrypts each received encrypted `election partial key backup` with their own `auxiliary private key`
46+
12. Each recipient guardian decrypts each received encrypted `election partial key backup` with their own `auxiliary private key`\*
4747
13. Each recipient guardian verifies each `election partial key backup` and sends confirmation of verification
4848
- If the proof verifies, continue
4949
- If the proof fails
50-
1. Sender guardian publishes the `election partial key backup` value sent to recipient as a `election partial key challenge` where the value is **unencrypted** to all the other guardians \*
50+
1. Sender guardian publishes the `election partial key backup` value sent to recipient as a `election partial key challenge` where the value is **unencrypted** to all the other guardians \*\*
5151
2. Alternate guardian (outside sender or original recipient) attempts to verify key
5252
- If the proof verifies, continue
5353
- If the proof fails again, the accused (sender guardian) should be evicted and process should be restarted with new guardian.
5454
14. On receipt of all verifications of `election partial private keys` by all guardians, generate and publish `joint key` from election public keys
5555

56-
\* **Note:** _The confidentiality of this value is now gone, but since the two Guardians are in the dispute, at least one is misbehaving and could be revealing this data._
56+
57+
\* **Note:** _The auxiliary encrypt and decrypt functions can be overridden to allow different encryption mechanisms other than the default._
58+
59+
\*\* **Note:** _The confidentiality of this value is now gone, but since the two Guardians are in the dispute, at least one is misbehaving and could be revealing this data._
5760

5861
## Files
5962

src/electionguard/auxiliary.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from typing import Callable, Optional, NamedTuple
2+
3+
from .types import GUARDIAN_ID
4+
5+
MESSAGE = str
6+
PUBLIC_KEY = str
7+
SECRET_KEY = str
8+
ENCRYPTED_MESSAGE = str
9+
10+
11+
class AuxiliaryKeyPair(NamedTuple):
12+
"""A tuple of a secret key and public key."""
13+
14+
secret_key: SECRET_KEY
15+
"""The secret or private key"""
16+
public_key: PUBLIC_KEY
17+
18+
19+
class AuxiliaryPublicKey(NamedTuple):
20+
"""A tuple of auxiliary public key and owner information"""
21+
22+
owner_id: GUARDIAN_ID
23+
"""
24+
The unique identifier of the guardian
25+
"""
26+
27+
sequence_order: int
28+
"""
29+
The sequence order of the auxiliary public key (usually the guardian's sequence order)
30+
"""
31+
32+
key: PUBLIC_KEY
33+
"""
34+
A string representation of the Auxiliary public key.
35+
It is up to the external `AuxiliaryEncrypt` function to know how to parse this value
36+
"""
37+
38+
39+
AuxiliaryEncrypt = Callable[[MESSAGE, PUBLIC_KEY], Optional[ENCRYPTED_MESSAGE]]
40+
"""A callable type that represents the auxiliary encryption scheme."""
41+
42+
AuxiliaryDecrypt = Callable[[ENCRYPTED_MESSAGE, SECRET_KEY], Optional[MESSAGE]]
43+
"""A callable type that represents the auxiliary decryption scheme."""

src/electionguard/guardian.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@
2323
combine_election_public_keys,
2424
CeremonyDetails,
2525
CoefficientValidationSet,
26-
default_auxiliary_decrypt,
27-
default_auxiliary_encrypt,
2826
ElectionJointKey,
2927
ElectionKeyPair,
3028
ElectionPartialKeyBackup,
@@ -35,12 +33,13 @@
3533
generate_election_key_pair,
3634
generate_election_partial_key_backup,
3735
generate_election_partial_key_challenge,
38-
generate_elgamal_auxiliary_key_pair,
36+
generate_rsa_auxiliary_key_pair,
3937
PublicKeySet,
4038
verify_election_partial_key_backup,
4139
verify_election_partial_key_challenge,
4240
)
4341
from .logs import log_warning
42+
from .rsa import rsa_encrypt, rsa_decrypt
4443
from .types import GUARDIAN_ID
4544
from .utils import get_optional
4645

@@ -179,7 +178,7 @@ def generate_auxiliary_key_pair(
179178
self,
180179
generate_auxiliary_key_pair: Callable[
181180
[], AuxiliaryKeyPair
182-
] = generate_elgamal_auxiliary_key_pair,
181+
] = generate_rsa_auxiliary_key_pair,
183182
) -> None:
184183
"""
185184
Generate auxiliary key pair
@@ -277,7 +276,7 @@ def guardian_election_public_keys(
277276
return ReadOnlyDataStore(self._guardian_election_public_keys)
278277

279278
def generate_election_partial_key_backups(
280-
self, encrypt: AuxiliaryEncrypt = default_auxiliary_encrypt
279+
self, encrypt: AuxiliaryEncrypt = rsa_encrypt
281280
) -> bool:
282281
"""
283282
Generate all election partial key backups based on existing public keys
@@ -292,6 +291,11 @@ def generate_election_partial_key_backups(
292291
backup = generate_election_partial_key_backup(
293292
self.object_id, self._election_keys.polynomial, auxiliary_key, encrypt
294293
)
294+
if backup is None:
295+
log_warning(
296+
f"guardian; {self.object_id} could not generate election partial key backups: failed to encrypt"
297+
)
298+
return False
295299
self._backups_to_share.set(auxiliary_key.owner_id, backup)
296300

297301
return True
@@ -328,9 +332,7 @@ def all_election_partial_key_backups_received(self) -> bool:
328332

329333
# Verification
330334
def verify_election_partial_key_backup(
331-
self,
332-
guardian_id: GUARDIAN_ID,
333-
decrypt: AuxiliaryDecrypt = default_auxiliary_decrypt,
335+
self, guardian_id: GUARDIAN_ID, decrypt: AuxiliaryDecrypt = rsa_decrypt,
334336
) -> Optional[ElectionPartialKeyVerification]:
335337
"""
336338
Verify election partial key backup value is in polynomial
@@ -449,7 +451,7 @@ def compensate_decrypt(
449451
elgamal: ElGamalCiphertext,
450452
extended_base_hash: ElementModQ,
451453
nonce_seed: ElementModQ = None,
452-
decrypt: AuxiliaryDecrypt = default_auxiliary_decrypt,
454+
decrypt: AuxiliaryDecrypt = rsa_decrypt,
453455
) -> Optional[Tuple[ElementModP, ChaumPedersenProof]]:
454456
"""
455457
Compute a compensated partial decryption of an elgamal encryption
@@ -474,7 +476,14 @@ def compensate_decrypt(
474476
)
475477
return None
476478

477-
decrypted_value = decrypt(backup.encrypted_value, self._auxiliary_keys)
479+
decrypted_value = decrypt(
480+
backup.encrypted_value, self._auxiliary_keys.secret_key
481+
)
482+
if decrypted_value is None:
483+
log_warning(
484+
f"compensate decrypt guardian {self.object_id} failed decryption for {missing_guardian_id}"
485+
)
486+
return None
478487
partial_secret_key = get_optional(int_to_q(int(decrypted_value)))
479488

480489
# 𝑀_{𝑖,l} = 𝐴^P𝑖_{l}

src/electionguard/key_ceremony.py

Lines changed: 29 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
from dataclasses import dataclass
2-
from typing import (
3-
Callable,
4-
List,
5-
NamedTuple,
6-
)
2+
from typing import List, Optional, NamedTuple
73

4+
from .auxiliary import (
5+
AuxiliaryKeyPair,
6+
AuxiliaryDecrypt,
7+
AuxiliaryEncrypt,
8+
AuxiliaryPublicKey,
9+
)
810
from .data_store import DataStore
911
from .election_polynomial import (
1012
compute_polynomial_coordinate,
@@ -18,9 +20,10 @@
1820
elgamal_keypair_random,
1921
)
2022
from .group import int_to_q, rand_q, ElementModP, ElementModQ
23+
from .rsa import rsa_keypair, rsa_decrypt, rsa_encrypt
2124
from .schnorr import SchnorrProof, make_schnorr_proof
22-
from .types import GUARDIAN_ID
2325
from .serializable import Serializable
26+
from .types import GUARDIAN_ID
2427
from .utils import get_optional
2528

2629
ElectionJointKey = ElementModP
@@ -35,48 +38,6 @@ class CeremonyDetails(NamedTuple):
3538
quorum: int
3639

3740

38-
class AuxiliaryKeyPair(NamedTuple):
39-
"""A tuple of a secret key and public key."""
40-
41-
secret_key: str
42-
"""The secret or private key"""
43-
public_key: str
44-
45-
46-
class AuxiliaryPublicKey(NamedTuple):
47-
"""A tuple of auxiliary public key and owner information"""
48-
49-
owner_id: GUARDIAN_ID
50-
"""
51-
The unique identifier of the guardian
52-
"""
53-
54-
sequence_order: int
55-
"""
56-
The sequence order of the auxiliary public key (usually the guardian's sequence order)
57-
"""
58-
59-
key: str
60-
"""
61-
A string representation of the Auxiliary public key.
62-
It is up to the external `AuxiliaryEncrypt` function to know how to parse this value
63-
"""
64-
65-
66-
AuxiliaryEncrypt = Callable[[str, AuxiliaryPublicKey], str]
67-
"""
68-
A Genric callable type that represents an encryption/decryption scheme.
69-
"""
70-
71-
# FIX_ME ISSUE #47: Default Auxiliary Encrypt is temporary placeholder
72-
default_auxiliary_encrypt = lambda unencrypted, key: unencrypted
73-
74-
AuxiliaryDecrypt = Callable[[str, AuxiliaryKeyPair], str]
75-
76-
# FIX_ME ISSUE #47: Default Auxiliary Decrypt is temporary placeholder
77-
default_auxiliary_decrypt = lambda encrypted, key_pair: encrypted
78-
79-
8041
class ElectionKeyPair(NamedTuple):
8142
"""A tuple of election key pair, proof and polynomial"""
8243

@@ -187,6 +148,15 @@ def generate_elgamal_auxiliary_key_pair() -> AuxiliaryKeyPair:
187148
)
188149

189150

151+
def generate_rsa_auxiliary_key_pair() -> AuxiliaryKeyPair:
152+
"""
153+
Generate auxiliary key pair using RSA
154+
:return: Auxiliary key pair
155+
"""
156+
rsa_key_pair = rsa_keypair()
157+
return AuxiliaryKeyPair(rsa_key_pair.private_key, rsa_key_pair.public_key)
158+
159+
190160
def generate_election_key_pair(
191161
quorum: int, nonce: ElementModQ = None
192162
) -> ElectionKeyPair:
@@ -207,8 +177,8 @@ def generate_election_partial_key_backup(
207177
owner_id: GUARDIAN_ID,
208178
polynomial: ElectionPolynomial,
209179
auxiliary_public_key: AuxiliaryPublicKey,
210-
encrypt: AuxiliaryEncrypt = default_auxiliary_encrypt,
211-
) -> ElectionPartialKeyBackup:
180+
encrypt: AuxiliaryEncrypt = rsa_encrypt,
181+
) -> Optional[ElectionPartialKeyBackup]:
212182
"""
213183
Generate election partial key backup for sharing
214184
:param owner_id: Owner of election key
@@ -220,7 +190,9 @@ def generate_election_partial_key_backup(
220190
value = compute_polynomial_coordinate(
221191
auxiliary_public_key.sequence_order, polynomial
222192
)
223-
encrypted_value = encrypt(str(value.to_int()), auxiliary_public_key)
193+
encrypted_value = encrypt(str(value.to_int()), auxiliary_public_key.key)
194+
if encrypted_value is None:
195+
return None
224196
return ElectionPartialKeyBackup(
225197
owner_id,
226198
auxiliary_public_key.owner_id,
@@ -253,7 +225,7 @@ def verify_election_partial_key_backup(
253225
verifier_id: GUARDIAN_ID,
254226
backup: ElectionPartialKeyBackup,
255227
auxiliary_key_pair: AuxiliaryKeyPair,
256-
decrypt: AuxiliaryDecrypt = default_auxiliary_decrypt,
228+
decrypt: AuxiliaryDecrypt = rsa_decrypt,
257229
) -> ElectionPartialKeyVerification:
258230
"""
259231
Verify election partial key backup contain point on owners polynomial
@@ -263,9 +235,12 @@ def verify_election_partial_key_backup(
263235
:param decrypt: Decryption function using auxiliary key
264236
"""
265237

266-
decrypted_value = decrypt(backup.encrypted_value, auxiliary_key_pair)
238+
decrypted_value = decrypt(backup.encrypted_value, auxiliary_key_pair.secret_key)
239+
if decrypted_value is None:
240+
return ElectionPartialKeyVerification(
241+
backup.owner_id, backup.designated_id, verifier_id, False
242+
)
267243
value = get_optional(int_to_q(int(decrypted_value)))
268-
269244
return ElectionPartialKeyVerification(
270245
backup.owner_id,
271246
backup.designated_id,

0 commit comments

Comments
 (0)