0
# Encoding & Decoding Utilities
1
2
Comprehensive encoding and decoding utilities for digital signatures, cryptographic keys, and ASN.1 structures. The ecdsa library provides multiple encoding formats to ensure compatibility with different systems and standards including DER, PEM, SSH, and raw binary formats.
3
4
## Capabilities
5
6
### Signature Encoding Functions
7
8
Functions to encode ECDSA signature pairs (r, s) into various standard formats.
9
10
```python { .api }
11
def sigencode_string(r, s, order):
12
"""
13
Encode signature as raw concatenated byte string.
14
15
Parameters:
16
- r: int, signature r component
17
- s: int, signature s component
18
- order: int, curve order for length calculation
19
20
Returns:
21
bytes, concatenated r||s as fixed-length byte string
22
"""
23
24
def sigencode_strings(r, s, order):
25
"""
26
Encode signature components as separate byte strings.
27
28
Parameters:
29
- r: int, signature r component
30
- s: int, signature s component
31
- order: int, curve order for length calculation
32
33
Returns:
34
tuple[bytes, bytes], (r_bytes, s_bytes) as separate strings
35
"""
36
37
def sigencode_der(r, s, order):
38
"""
39
Encode signature in DER ASN.1 format.
40
41
Parameters:
42
- r: int, signature r component
43
- s: int, signature s component
44
- order: int, curve order (unused but kept for API consistency)
45
46
Returns:
47
bytes, DER-encoded ASN.1 SEQUENCE containing r and s INTEGERs
48
"""
49
50
def sigencode_string_canonize(r, s, order):
51
"""
52
Encode signature as canonical raw byte string (low-s form).
53
54
Parameters:
55
- r: int, signature r component
56
- s: int, signature s component
57
- order: int, curve order for canonicalization
58
59
Returns:
60
bytes, canonical concatenated r||s with s in low form
61
"""
62
63
def sigencode_strings_canonize(r, s, order):
64
"""
65
Encode signature components as canonical separate byte strings.
66
67
Parameters:
68
- r: int, signature r component
69
- s: int, signature s component
70
- order: int, curve order for canonicalization
71
72
Returns:
73
tuple[bytes, bytes], canonical (r_bytes, s_bytes) with low-s
74
"""
75
76
def sigencode_der_canonize(r, s, order):
77
"""
78
Encode signature in canonical DER format (low-s form).
79
80
Parameters:
81
- r: int, signature r component
82
- s: int, signature s component
83
- order: int, curve order for canonicalization
84
85
Returns:
86
bytes, canonical DER-encoded signature with low-s
87
"""
88
```
89
90
### Signature Decoding Functions
91
92
Functions to decode signatures from various formats back to (r, s) integer pairs.
93
94
```python { .api }
95
def sigdecode_string(signature, order):
96
"""
97
Decode signature from raw concatenated byte string.
98
99
Parameters:
100
- signature: bytes, concatenated r||s byte string
101
- order: int, curve order for length calculation
102
103
Returns:
104
tuple[int, int], (r, s) signature components
105
106
Raises:
107
ValueError: if signature length is incorrect
108
"""
109
110
def sigdecode_strings(rs_strings, order):
111
"""
112
Decode signature from separate byte string tuple.
113
114
Parameters:
115
- rs_strings: tuple[bytes, bytes], (r_bytes, s_bytes)
116
- order: int, curve order for validation
117
118
Returns:
119
tuple[int, int], (r, s) signature components
120
"""
121
122
def sigdecode_der(sig_der, order):
123
"""
124
Decode signature from DER ASN.1 format.
125
126
Parameters:
127
- sig_der: bytes, DER-encoded ASN.1 signature
128
- order: int, curve order for validation
129
130
Returns:
131
tuple[int, int], (r, s) signature components
132
133
Raises:
134
UnexpectedDER: if DER structure is invalid
135
"""
136
```
137
138
### Number and String Conversion Utilities
139
140
Low-level utilities for converting between integers and byte strings with proper length handling.
141
142
```python { .api }
143
def number_to_string(num, order):
144
"""
145
Convert integer to fixed-length byte string based on curve order.
146
147
Parameters:
148
- num: int, number to convert
149
- order: int, curve order determining output length
150
151
Returns:
152
bytes, big-endian byte string with length based on order
153
"""
154
155
def number_to_string_crop(num, order):
156
"""
157
Convert integer to minimal-length byte string.
158
159
Parameters:
160
- num: int, number to convert
161
- order: int, curve order for validation
162
163
Returns:
164
bytes, minimal big-endian byte string (no leading zeros)
165
"""
166
167
def string_to_number(string):
168
"""
169
Convert byte string to integer.
170
171
Parameters:
172
- string: bytes, big-endian byte string
173
174
Returns:
175
int, converted number
176
"""
177
178
def string_to_number_fixedlen(string, order):
179
"""
180
Convert byte string to integer with length validation.
181
182
Parameters:
183
- string: bytes, big-endian byte string
184
- order: int, curve order for length validation
185
186
Returns:
187
int, converted number
188
189
Raises:
190
ValueError: if string length doesn't match expected length
191
"""
192
```
193
194
### DER ASN.1 Encoding Functions
195
196
Functions for encoding data structures in Distinguished Encoding Rules (DER) format.
197
198
```python { .api }
199
def encode_sequence(*encoded_pieces):
200
"""
201
Encode ASN.1 SEQUENCE containing multiple elements.
202
203
Parameters:
204
- encoded_pieces: bytes, already DER-encoded elements
205
206
Returns:
207
bytes, DER-encoded SEQUENCE
208
"""
209
210
def encode_integer(r):
211
"""
212
Encode integer as ASN.1 INTEGER.
213
214
Parameters:
215
- r: int, integer to encode
216
217
Returns:
218
bytes, DER-encoded INTEGER
219
"""
220
221
def encode_octet_string(s):
222
"""
223
Encode byte string as ASN.1 OCTET STRING.
224
225
Parameters:
226
- s: bytes, byte string to encode
227
228
Returns:
229
bytes, DER-encoded OCTET STRING
230
"""
231
232
def encode_bitstring(s, unused=0):
233
"""
234
Encode byte string as ASN.1 BIT STRING.
235
236
Parameters:
237
- s: bytes, byte string to encode
238
- unused: int, number of unused bits in last byte (0-7)
239
240
Returns:
241
bytes, DER-encoded BIT STRING
242
"""
243
244
def encode_oid(first, second, *pieces):
245
"""
246
Encode ASN.1 OBJECT IDENTIFIER from components.
247
248
Parameters:
249
- first: int, first OID component
250
- second: int, second OID component
251
- pieces: int, additional OID components
252
253
Returns:
254
bytes, DER-encoded OBJECT IDENTIFIER
255
"""
256
257
def encode_constructed(tag, value):
258
"""
259
Encode constructed ASN.1 type with custom tag.
260
261
Parameters:
262
- tag: int, ASN.1 tag number
263
- value: bytes, content to encode
264
265
Returns:
266
bytes, DER-encoded constructed type
267
"""
268
269
def encode_implicit(tag, value, cls="context-specific"):
270
"""
271
Encode implicit ASN.1 tag.
272
273
Parameters:
274
- tag: int, tag number
275
- value: bytes, content to encode
276
- cls: str, tag class ("context-specific", "application", etc.)
277
278
Returns:
279
bytes, DER-encoded implicit tag
280
"""
281
```
282
283
### DER ASN.1 Decoding Functions
284
285
Functions for decoding DER-encoded ASN.1 structures.
286
287
```python { .api }
288
def remove_sequence(string):
289
"""
290
Decode ASN.1 SEQUENCE and return content.
291
292
Parameters:
293
- string: bytes, DER-encoded data starting with SEQUENCE
294
295
Returns:
296
tuple[bytes, bytes], (sequence_content, remaining_data)
297
298
Raises:
299
UnexpectedDER: if not a valid SEQUENCE
300
"""
301
302
def remove_integer(string):
303
"""
304
Decode ASN.1 INTEGER and return value.
305
306
Parameters:
307
- string: bytes, DER-encoded data starting with INTEGER
308
309
Returns:
310
tuple[int, bytes], (integer_value, remaining_data)
311
312
Raises:
313
UnexpectedDER: if not a valid INTEGER
314
"""
315
316
def remove_octet_string(string):
317
"""
318
Decode ASN.1 OCTET STRING and return content.
319
320
Parameters:
321
- string: bytes, DER-encoded data starting with OCTET STRING
322
323
Returns:
324
tuple[bytes, bytes], (octet_string_content, remaining_data)
325
"""
326
327
def remove_bitstring(string, expect_unused=0):
328
"""
329
Decode ASN.1 BIT STRING and return content.
330
331
Parameters:
332
- string: bytes, DER-encoded data starting with BIT STRING
333
- expect_unused: int, expected number of unused bits
334
335
Returns:
336
tuple[bytes, bytes], (bitstring_content, remaining_data)
337
338
Raises:
339
UnexpectedDER: if unused bits don't match expected
340
"""
341
342
def remove_object(string):
343
"""
344
Decode ASN.1 OBJECT IDENTIFIER and return OID tuple.
345
346
Parameters:
347
- string: bytes, DER-encoded data starting with OBJECT IDENTIFIER
348
349
Returns:
350
tuple[tuple[int, ...], bytes], (oid_tuple, remaining_data)
351
"""
352
353
def remove_constructed(string):
354
"""
355
Decode constructed ASN.1 type.
356
357
Parameters:
358
- string: bytes, DER-encoded constructed type
359
360
Returns:
361
tuple[int, bytes, bytes], (tag, content, remaining_data)
362
"""
363
```
364
365
### DER/PEM Conversion Utilities
366
367
Functions for converting between DER binary format and PEM text format.
368
369
```python { .api }
370
def encode_length(l):
371
"""
372
Encode ASN.1 length field.
373
374
Parameters:
375
- l: int, length to encode
376
377
Returns:
378
bytes, DER-encoded length
379
"""
380
381
def read_length(string):
382
"""
383
Decode ASN.1 length field.
384
385
Parameters:
386
- string: bytes, DER data starting with length field
387
388
Returns:
389
tuple[int, bytes], (length, remaining_data)
390
"""
391
392
def is_sequence(string):
393
"""
394
Check if data starts with ASN.1 SEQUENCE.
395
396
Parameters:
397
- string: bytes, DER-encoded data
398
399
Returns:
400
bool, True if starts with SEQUENCE tag
401
"""
402
403
def topem(der, name):
404
"""
405
Convert DER to PEM format.
406
407
Parameters:
408
- der: bytes, DER-encoded data
409
- name: str, PEM label (e.g., "PRIVATE KEY", "PUBLIC KEY")
410
411
Returns:
412
str, PEM-formatted string with headers and base64 encoding
413
"""
414
415
def unpem(pem):
416
"""
417
Convert PEM to DER format.
418
419
Parameters:
420
- pem: str or bytes, PEM-formatted data
421
422
Returns:
423
bytes, DER-encoded data (base64 decoded, headers removed)
424
"""
425
```
426
427
### Object Identifiers and Constants
428
429
Standard ASN.1 Object Identifiers used in elliptic curve cryptography.
430
431
```python { .api }
432
oid_ecPublicKey: tuple # (1, 2, 840, 10045, 2, 1) - EC public key algorithm
433
encoded_oid_ecPublicKey: bytes # DER-encoded version of above
434
oid_ecDH: tuple # (1, 3, 132, 1, 12) - ECDH algorithm
435
oid_ecMQV: tuple # (1, 3, 132, 1, 13) - ECMQV algorithm
436
```
437
438
### Random Number Generation
439
440
Utilities for cryptographically secure random number generation.
441
442
```python { .api }
443
class PRNG:
444
"""Pseudorandom number generator for deterministic signatures."""
445
446
def randrange(order, entropy=None):
447
"""
448
Generate cryptographically secure random number in range [1, order-1].
449
450
Parameters:
451
- order: int, upper bound (exclusive)
452
- entropy: callable or None, entropy source (default: os.urandom)
453
454
Returns:
455
int, random number in specified range
456
"""
457
```
458
459
## Exception Classes
460
461
```python { .api }
462
class UnexpectedDER(Exception):
463
"""Raised when DER encoding or decoding encounters invalid structure."""
464
```
465
466
## Usage Examples
467
468
### Signature Encoding and Decoding
469
470
```python
471
from ecdsa import SigningKey, NIST256p
472
from ecdsa.util import sigencode_der, sigdecode_der, sigencode_string, sigdecode_string
473
474
# Generate key and sign data
475
sk = SigningKey.generate(curve=NIST256p)
476
vk = sk.verifying_key
477
message = b"Test message for encoding"
478
479
# Sign with different encodings
480
sig_raw = sk.sign(message, sigencode=sigencode_string)
481
sig_der = sk.sign(message, sigencode=sigencode_der)
482
483
print(f"Raw signature length: {len(sig_raw)} bytes")
484
print(f"DER signature length: {len(sig_der)} bytes")
485
486
# Verify with corresponding decodings
487
vk.verify(sig_raw, message, sigdecode=sigdecode_string)
488
vk.verify(sig_der, message, sigdecode=sigdecode_der)
489
490
# Decode signatures to (r, s) pairs
491
r_raw, s_raw = sigdecode_string(sig_raw, sk.curve.order)
492
r_der, s_der = sigdecode_der(sig_der, sk.curve.order)
493
494
print(f"Raw signature r: {r_raw}")
495
print(f"DER signature r: {r_der}")
496
print(f"Signatures match: {r_raw == r_der and s_raw == s_der}")
497
```
498
499
### Working with DER/ASN.1 Structures
500
501
```python
502
from ecdsa.der import encode_sequence, encode_integer, remove_sequence, remove_integer
503
504
# Create DER-encoded sequence containing two integers
505
r, s = 12345, 67890
506
der_r = encode_integer(r)
507
der_s = encode_integer(s)
508
der_sequence = encode_sequence(der_r, der_s)
509
510
print(f"DER sequence: {der_sequence.hex()}")
511
512
# Decode the sequence
513
sequence_content, remaining = remove_sequence(der_sequence)
514
decoded_r, content_after_r = remove_integer(sequence_content)
515
decoded_s, final_remaining = remove_integer(content_after_r)
516
517
print(f"Decoded r: {decoded_r}, s: {decoded_s}")
518
print(f"Original r: {r}, s: {s}")
519
print(f"Match: {decoded_r == r and decoded_s == s}")
520
```
521
522
### PEM/DER Conversion
523
524
```python
525
from ecdsa import SigningKey, NIST256p
526
from ecdsa.der import topem, unpem
527
528
# Generate key and export as DER
529
sk = SigningKey.generate(curve=NIST256p)
530
der_key = sk.to_der()
531
532
# Convert DER to PEM
533
pem_key = topem(der_key, "EC PRIVATE KEY")
534
print("PEM format:")
535
print(pem_key.decode())
536
537
# Convert PEM back to DER
538
der_from_pem = unpem(pem_key)
539
print(f"DER roundtrip successful: {der_key == der_from_pem}")
540
541
# Also works with public keys
542
vk = sk.verifying_key
543
der_pubkey = vk.to_der()
544
pem_pubkey = topem(der_pubkey, "PUBLIC KEY")
545
der_pubkey_roundtrip = unpem(pem_pubkey)
546
print(f"Public key roundtrip successful: {der_pubkey == der_pubkey_roundtrip}")
547
```
548
549
### Number and String Conversions
550
551
```python
552
from ecdsa.util import number_to_string, string_to_number, number_to_string_crop
553
from ecdsa import NIST256p
554
555
# Work with curve order for proper length calculation
556
order = NIST256p.order
557
test_number = 0x1234567890abcdef
558
559
# Convert number to fixed-length string
560
fixed_bytes = number_to_string(test_number, order)
561
minimal_bytes = number_to_string_crop(test_number, order)
562
563
print(f"Original number: 0x{test_number:x}")
564
print(f"Fixed length: {len(fixed_bytes)} bytes - {fixed_bytes.hex()}")
565
print(f"Minimal length: {len(minimal_bytes)} bytes - {minimal_bytes.hex()}")
566
567
# Convert back to numbers
568
recovered_fixed = string_to_number(fixed_bytes)
569
recovered_minimal = string_to_number(minimal_bytes)
570
571
print(f"Recovered from fixed: 0x{recovered_fixed:x}")
572
print(f"Recovered from minimal: 0x{recovered_minimal:x}")
573
print(f"All match: {test_number == recovered_fixed == recovered_minimal}")
574
```
575
576
### Canonical Signatures (Low-S Form)
577
578
```python
579
from ecdsa import SigningKey, SECP256k1
580
from ecdsa.util import sigencode_der_canonize, sigdecode_der
581
582
# Bitcoin uses canonical signatures (low-s form)
583
sk = SigningKey.generate(curve=SECP256k1)
584
message = b"Bitcoin transaction data"
585
586
# Generate canonical signature
587
canonical_sig = sk.sign(message, sigencode=sigencode_der_canonize)
588
589
# Decode and verify s is in low form
590
r, s = sigdecode_der(canonical_sig, sk.curve.order)
591
print(f"Signature r: {r}")
592
print(f"Signature s: {s}")
593
print(f"s is canonical (low): {s <= sk.curve.order // 2}")
594
595
# Verify signature
596
vk = sk.verifying_key
597
vk.verify(canonical_sig, message, sigdecode=sigdecode_der)
598
print("Canonical signature verified successfully")
599
```
600
601
### Advanced DER Operations
602
603
```python
604
from ecdsa.der import (
605
encode_oid, remove_object, encode_octet_string, remove_octet_string,
606
encode_bitstring, remove_bitstring
607
)
608
609
# Work with Object Identifiers
610
secp256k1_oid = (1, 3, 132, 0, 10) # secp256k1 curve OID
611
encoded_oid = encode_oid(*secp256k1_oid)
612
decoded_oid, remaining = remove_object(encoded_oid)
613
614
print(f"Original OID: {secp256k1_oid}")
615
print(f"Decoded OID: {decoded_oid}")
616
print(f"OID match: {secp256k1_oid == decoded_oid}")
617
618
# Work with octet strings
619
test_data = b"Secret key material"
620
encoded_octets = encode_octet_string(test_data)
621
decoded_octets, remaining = remove_octet_string(encoded_octets)
622
623
print(f"Octet string roundtrip: {test_data == decoded_octets}")
624
625
# Work with bit strings (for public key encoding)
626
pubkey_data = b"\x04" + b"x" * 64 # Uncompressed public key format
627
encoded_bits = encode_bitstring(pubkey_data, unused=0)
628
decoded_bits, remaining = remove_bitstring(encoded_bits, expect_unused=0)
629
630
print(f"Bit string roundtrip: {pubkey_data == decoded_bits}")
631
```