0
# ECDH Key Exchange
1
2
Elliptic Curve Diffie-Hellman (ECDH) key agreement protocol implementation for secure shared secret generation between parties. ECDH allows two parties to establish a shared secret over an insecure channel by exchanging public keys and combining them with their private keys.
3
4
## Capabilities
5
6
### ECDH Class
7
8
The ECDH class provides a complete implementation of the elliptic curve Diffie-Hellman key agreement protocol.
9
10
#### Initialization and Curve Management
11
12
```python { .api }
13
class ECDH:
14
def __init__(self, curve=None, private_key=None, public_key=None):
15
"""
16
Initialize ECDH instance.
17
18
Parameters:
19
- curve: Curve object, elliptic curve to use
20
- private_key: SigningKey object, existing private key
21
- public_key: VerifyingKey object, existing public key
22
"""
23
24
def set_curve(self, key_curve):
25
"""
26
Set the elliptic curve for ECDH operations.
27
28
Parameters:
29
- key_curve: Curve object, curve to use for operations
30
"""
31
```
32
33
#### Private Key Management
34
35
```python { .api }
36
def generate_private_key(self):
37
"""
38
Generate a new random private key and return corresponding public key.
39
40
Returns:
41
VerifyingKey object, the generated public key
42
43
Raises:
44
NoCurveError: if curve is not set
45
"""
46
47
def load_private_key(self, private_key):
48
"""
49
Load private key from SigningKey object.
50
51
Parameters:
52
- private_key: SigningKey object
53
54
Returns:
55
VerifyingKey object, corresponding public key
56
57
Raises:
58
InvalidCurveError: if key curve doesn't match set curve
59
"""
60
61
def load_private_key_bytes(self, private_key):
62
"""
63
Load private key from raw byte string.
64
65
Parameters:
66
- private_key: bytes, raw private key
67
68
Returns:
69
VerifyingKey object, corresponding public key
70
71
Raises:
72
NoCurveError: if curve is not set
73
"""
74
75
def load_private_key_der(self, private_key_der):
76
"""
77
Load private key from DER-encoded bytes.
78
79
Parameters:
80
- private_key_der: bytes, DER-encoded private key
81
82
Returns:
83
VerifyingKey object, corresponding public key
84
"""
85
86
def load_private_key_pem(self, private_key_pem):
87
"""
88
Load private key from PEM-encoded string or bytes.
89
90
Parameters:
91
- private_key_pem: str or bytes, PEM-encoded private key
92
93
Returns:
94
VerifyingKey object, corresponding public key
95
"""
96
97
def get_public_key(self):
98
"""
99
Get the current public key.
100
101
Returns:
102
VerifyingKey object, current public key
103
104
Raises:
105
NoKeyError: if no private key is loaded
106
"""
107
```
108
109
#### Remote Public Key Management
110
111
```python { .api }
112
def load_received_public_key(self, public_key):
113
"""
114
Load the other party's public key from VerifyingKey object.
115
116
Parameters:
117
- public_key: VerifyingKey object
118
119
Raises:
120
InvalidCurveError: if key curve doesn't match set curve
121
"""
122
123
def load_received_public_key_bytes(self, public_key_str, valid_encodings=None):
124
"""
125
Load the other party's public key from raw bytes.
126
127
Parameters:
128
- public_key_str: bytes, raw public key
129
- valid_encodings: list of str, acceptable point encodings or None
130
131
Raises:
132
NoCurveError: if curve is not set
133
"""
134
135
def load_received_public_key_der(self, public_key_der):
136
"""
137
Load the other party's public key from DER-encoded bytes.
138
139
Parameters:
140
- public_key_der: bytes, DER-encoded public key
141
"""
142
143
def load_received_public_key_pem(self, public_key_pem):
144
"""
145
Load the other party's public key from PEM-encoded string or bytes.
146
147
Parameters:
148
- public_key_pem: str or bytes, PEM-encoded public key
149
"""
150
```
151
152
#### Shared Secret Generation
153
154
```python { .api }
155
def generate_sharedsecret(self):
156
"""
157
Generate shared secret as integer.
158
159
Returns:
160
int, shared secret value
161
162
Raises:
163
NoKeyError: if private key or received public key not loaded
164
InvalidSharedSecretError: if computed secret is invalid (point at infinity)
165
"""
166
167
def generate_sharedsecret_bytes(self):
168
"""
169
Generate shared secret as byte string.
170
171
Returns:
172
bytes, shared secret as raw bytes
173
174
Raises:
175
NoKeyError: if private key or received public key not loaded
176
InvalidSharedSecretError: if computed secret is invalid (point at infinity)
177
"""
178
```
179
180
## Exception Classes
181
182
```python { .api }
183
class NoKeyError(Exception):
184
"""Raised when a required key is not set but needed for operation."""
185
186
class NoCurveError(Exception):
187
"""Raised when curve is not set but needed for operation."""
188
189
class InvalidCurveError(Exception):
190
"""Raised when public and private keys use different curves."""
191
192
class InvalidSharedSecretError(Exception):
193
"""Raised when shared secret computation results in point at infinity."""
194
```
195
196
## Usage Examples
197
198
### Basic ECDH Key Exchange
199
200
```python
201
from ecdsa import ECDH, NIST256p
202
203
# Party A setup
204
alice = ECDH(curve=NIST256p)
205
alice_public_key = alice.generate_private_key()
206
207
# Party B setup
208
bob = ECDH(curve=NIST256p)
209
bob_public_key = bob.generate_private_key()
210
211
# Exchange public keys (over insecure channel)
212
alice.load_received_public_key(bob_public_key)
213
bob.load_received_public_key(alice_public_key)
214
215
# Both parties compute the same shared secret
216
alice_secret = alice.generate_sharedsecret_bytes()
217
bob_secret = bob.generate_sharedsecret_bytes()
218
219
assert alice_secret == bob_secret
220
print(f"Shared secret established: {alice_secret.hex()}")
221
```
222
223
### ECDH with Existing Keys
224
225
```python
226
from ecdsa import ECDH, SigningKey, SECP256k1
227
228
# Use existing private keys
229
alice_private = SigningKey.generate(curve=SECP256k1)
230
bob_private = SigningKey.generate(curve=SECP256k1)
231
232
# Setup ECDH instances with existing keys
233
alice_ecdh = ECDH(curve=SECP256k1)
234
alice_public = alice_ecdh.load_private_key(alice_private)
235
236
bob_ecdh = ECDH(curve=SECP256k1)
237
bob_public = bob_ecdh.load_private_key(bob_private)
238
239
# Exchange public keys
240
alice_ecdh.load_received_public_key(bob_public)
241
bob_ecdh.load_received_public_key(alice_public)
242
243
# Generate shared secrets
244
alice_secret = alice_ecdh.generate_sharedsecret()
245
bob_secret = bob_ecdh.generate_sharedsecret()
246
247
assert alice_secret == bob_secret
248
```
249
250
### ECDH with Key Serialization
251
252
```python
253
from ecdsa import ECDH, NIST384p
254
255
# Alice generates key pair and exports public key
256
alice = ECDH(curve=NIST384p)
257
alice_public_key = alice.generate_private_key()
258
alice_public_pem = alice_public_key.to_pem()
259
260
# Bob generates key pair and exports public key
261
bob = ECDH(curve=NIST384p)
262
bob_public_key = bob.generate_private_key()
263
bob_public_pem = bob_public_key.to_pem()
264
265
# Exchange serialized public keys (e.g., over network)
266
# Alice loads Bob's public key from PEM
267
alice.load_received_public_key_pem(bob_public_pem)
268
269
# Bob loads Alice's public key from PEM
270
bob.load_received_public_key_pem(alice_public_pem)
271
272
# Both parties generate the same shared secret
273
alice_secret = alice.generate_sharedsecret_bytes()
274
bob_secret = bob.generate_sharedsecret_bytes()
275
276
assert alice_secret == bob_secret
277
print(f"ECDH key exchange completed successfully")
278
```
279
280
### Error Handling
281
282
```python
283
from ecdsa import ECDH, NIST256p, NoKeyError, NoCurveError, InvalidCurveError
284
285
try:
286
# Attempt to generate key without setting curve
287
ecdh = ECDH()
288
public_key = ecdh.generate_private_key() # Raises NoCurveError
289
except NoCurveError:
290
print("Must set curve before generating keys")
291
292
try:
293
# Attempt to generate shared secret without loading received key
294
ecdh = ECDH(curve=NIST256p)
295
ecdh.generate_private_key()
296
secret = ecdh.generate_sharedsecret() # Raises NoKeyError
297
except NoKeyError:
298
print("Must load received public key before generating shared secret")
299
300
try:
301
# Attempt to use keys from different curves
302
from ecdsa import SECP256k1
303
alice = ECDH(curve=NIST256p)
304
alice_public = alice.generate_private_key()
305
306
bob = ECDH(curve=SECP256k1)
307
bob_public = bob.generate_private_key()
308
309
alice.load_received_public_key(bob_public) # Raises InvalidCurveError
310
except InvalidCurveError:
311
print("Both parties must use the same curve")
312
```
313
314
### Different Curves Support
315
316
```python
317
from ecdsa import ECDH, SECP256k1, Ed25519, BRAINPOOLP384r1
318
319
# Bitcoin's secp256k1 curve
320
bitcoin_ecdh = ECDH(curve=SECP256k1)
321
bitcoin_public = bitcoin_ecdh.generate_private_key()
322
323
# Edwards curve (note: Ed25519 typically used for EdDSA, not ECDH)
324
# Standard ECDH is usually done with Weierstrass curves
325
edwards_ecdh = ECDH(curve=Ed25519)
326
edwards_public = edwards_ecdh.generate_private_key()
327
328
# Brainpool curve
329
brainpool_ecdh = ECDH(curve=BRAINPOOLP384r1)
330
brainpool_public = brainpool_ecdh.generate_private_key()
331
332
print(f"Generated keys for different curves:")
333
print(f"- Bitcoin secp256k1: {len(bitcoin_public.to_string())} bytes")
334
print(f"- Edwards Ed25519: {len(edwards_public.to_string())} bytes")
335
print(f"- Brainpool P384r1: {len(brainpool_public.to_string())} bytes")
336
```