0
# Key Discovery and Analysis
1
2
Advanced key discovery, scanning, and analysis operations for examining keys without importing them, understanding key structures, and managing trust relationships.
3
4
## Capabilities
5
6
### Key Scanning Without Import
7
8
Examine key files and data without importing them into the keyring, allowing for key analysis and validation before commitment.
9
10
```python { .api }
11
def scan_keys(self, filename):
12
"""
13
Scan keys from a file without importing them to the keyring.
14
15
Parameters:
16
- filename (str): Path to key file to scan
17
18
Returns:
19
ScanKeys: List of key information found in the file
20
"""
21
22
def scan_keys_mem(self, key_data):
23
"""
24
Scan keys from memory without importing them to the keyring.
25
26
Parameters:
27
- key_data (str): ASCII-armored or binary key data to scan
28
29
Returns:
30
ScanKeys: List of key information found in the data
31
"""
32
```
33
34
### Trust Management
35
36
Manage trust levels in the web of trust system for establishing key authenticity and reliability.
37
38
```python { .api }
39
def trust_keys(self, fingerprints, trustlevel):
40
"""
41
Set trust level for one or more keys in the web of trust.
42
43
Parameters:
44
- fingerprints (str|list): Key fingerprints to set trust for
45
- trustlevel (str): Trust level to assign
46
'1' = I don't know or won't say
47
'2' = I do NOT trust
48
'3' = I trust marginally
49
'4' = I trust fully
50
'5' = I trust ultimately (own keys)
51
52
Returns:
53
TrustResult: Result with trust operation status
54
"""
55
```
56
57
### Advanced Key Operations
58
59
Perform specialized key operations including subkey management and key relationship analysis.
60
61
```python { .api }
62
def add_subkey(self, master_key, master_passphrase=None, algorithm='rsa',
63
usage='encrypt', expire='-'):
64
"""
65
Add a subkey to an existing master key.
66
67
Parameters:
68
- master_key (str): Master key fingerprint or ID
69
- master_passphrase (str): Master key passphrase
70
- algorithm (str): Subkey algorithm ('rsa', 'dsa', 'ecdsa', 'eddsa')
71
- usage (str): Subkey usage ('encrypt', 'sign', 'auth')
72
- expire (str): Expiration ('-' for same as master, '0' for no expiration)
73
74
Returns:
75
AddSubkey: Result with new subkey information
76
"""
77
```
78
79
## Result Types
80
81
```python { .api }
82
class ScanKeys(ListKeys):
83
# Inherits from ListKeys - same structure but for scanned keys
84
# List of key dictionaries containing detailed key information:
85
# - type: Key type ('pub', 'sec', 'sub', 'ssb')
86
# - length: Key length in bits
87
# - algo: Algorithm number
88
# - keyid: Key ID (short form)
89
# - date: Creation date
90
# - expires: Expiration date (if any)
91
# - dummy: Dummy key indicator
92
# - ownertrust: Owner trust level
93
# - sig_class: Signature class
94
# - capabilities: Key capabilities string
95
# - issuer: Key issuer
96
# - flag: Key flags
97
# - token: Token serial number
98
# - hash_algo: Hash algorithm number
99
# - curve: Curve name for ECC keys
100
# - fingerprint: Full key fingerprint
101
# - uids: List of user IDs associated with the key
102
# - sigs: List of signatures on the key (if requested)
103
# - subkeys: List of subkeys under this master key
104
pass
105
106
class TrustResult(StatusHandler):
107
# Result of trust level setting operations
108
pass
109
110
class AddSubkey(StatusHandler):
111
type: str # Subkey type/algorithm
112
fingerprint: str # New subkey fingerprint
113
```
114
115
## Usage Examples
116
117
### Key File Analysis
118
119
```python
120
import gnupg
121
122
gpg = gnupg.GPG()
123
124
# Scan a key file without importing
125
scanned_keys = gpg.scan_keys('/path/to/keyfile.asc')
126
127
print(f"Found {len(scanned_keys)} keys in file:")
128
for key in scanned_keys:
129
print(f"\nKey ID: {key['keyid']}")
130
print(f"Fingerprint: {key['fingerprint']}")
131
print(f"Algorithm: {key['algo']} ({key['length']} bits)")
132
print(f"Created: {key['date']}")
133
if key['expires']:
134
print(f"Expires: {key['expires']}")
135
136
print(f"User IDs:")
137
for uid in key['uids']:
138
print(f" - {uid}")
139
140
print(f"Capabilities: {key['capabilities']}")
141
142
if key['subkeys']:
143
print(f"Subkeys:")
144
for subkey in key['subkeys']:
145
print(f" - {subkey['keyid']} ({subkey['length']} bits, {subkey['capabilities']})")
146
```
147
148
### Key Data Analysis from Memory
149
150
```python
151
# Analyze key data received over network or from database
152
key_data = """-----BEGIN PGP PUBLIC KEY BLOCK-----
153
154
mQENBF... (key data here) ...
155
-----END PGP PUBLIC KEY BLOCK-----"""
156
157
scanned = gpg.scan_keys_mem(key_data)
158
159
def analyze_key_strength(key_info):
160
"""Analyze key strength and provide recommendations."""
161
162
analysis = {
163
'strength': 'unknown',
164
'recommendations': [],
165
'warnings': []
166
}
167
168
# Check key length
169
length = key_info.get('length', 0)
170
algo = key_info.get('algo', 0)
171
172
if algo == 1: # RSA
173
if length < 2048:
174
analysis['strength'] = 'weak'
175
analysis['warnings'].append(f'RSA key length {length} is too short')
176
analysis['recommendations'].append('Use RSA 2048 bits or higher')
177
elif length < 3072:
178
analysis['strength'] = 'acceptable'
179
else:
180
analysis['strength'] = 'strong'
181
elif algo in [16, 20]: # ElGamal
182
if length < 2048:
183
analysis['strength'] = 'weak'
184
analysis['warnings'].append(f'ElGamal key length {length} is too short')
185
elif algo == 17: # DSA
186
if length < 2048:
187
analysis['strength'] = 'weak'
188
analysis['warnings'].append('DSA-1024 is deprecated')
189
analysis['recommendations'].append('Use RSA-2048 or higher')
190
191
# Check expiration
192
if not key_info.get('expires'):
193
analysis['recommendations'].append('Consider setting an expiration date')
194
else:
195
from datetime import datetime
196
try:
197
exp_date = datetime.strptime(key_info['expires'], '%Y-%m-%d')
198
if exp_date < datetime.now():
199
analysis['warnings'].append('Key has expired')
200
except:
201
pass
202
203
return analysis
204
205
# Analyze each scanned key
206
for key in scanned:
207
analysis = analyze_key_strength(key)
208
print(f"\nKey {key['keyid']} analysis:")
209
print(f"Strength: {analysis['strength']}")
210
211
for warning in analysis['warnings']:
212
print(f"⚠️ Warning: {warning}")
213
214
for rec in analysis['recommendations']:
215
print(f"💡 Recommendation: {rec}")
216
```
217
218
### Trust Management
219
220
```python
221
# Set trust levels for keys
222
def setup_trust_relationships(gpg_instance):
223
"""Set up trust relationships for a set of known keys."""
224
225
trust_assignments = {
226
# Ultimate trust (own keys)
227
'5': ['ABCD1234DEADBEEF5678CAFE'], # Your own key
228
229
# Full trust (fully trusted colleagues)
230
'4': [
231
'BEEF5678CAFE1234DEAD9876', # Alice's key
232
'CAFE1234DEAD5678BEEF9876', # Bob's key
233
],
234
235
# Marginal trust (somewhat trusted)
236
'3': [
237
'DEAD5678BEEF1234CAFE9876', # Charlie's key
238
],
239
240
# No trust
241
'2': [
242
'BADD0G15DEAD5678BEEF1234', # Revoked or compromised key
243
]
244
}
245
246
for trust_level, fingerprints in trust_assignments.items():
247
if fingerprints:
248
result = gpg_instance.trust_keys(fingerprints, trust_level)
249
if result.status == 'success':
250
level_names = {'2': 'no trust', '3': 'marginal', '4': 'full', '5': 'ultimate'}
251
print(f"Set {level_names.get(trust_level, trust_level)} trust for {len(fingerprints)} keys")
252
253
# Apply trust settings
254
setup_trust_relationships(gpg)
255
256
# Check current trust levels by listing keys
257
keys = gpg.list_keys()
258
for key in keys:
259
trust = key.get('ownertrust', 'unknown')
260
print(f"Key {key['keyid']}: trust level {trust}")
261
```
262
263
### Subkey Management
264
265
```python
266
# Add encryption subkey to existing signing key
267
master_fingerprint = 'DEADBEEFCAFEBABE1234567890ABCDEF12345678'
268
269
# Add RSA encryption subkey
270
result = gpg.add_subkey(
271
master_key=master_fingerprint,
272
master_passphrase='master_key_passphrase',
273
algorithm='rsa',
274
usage='encrypt'
275
)
276
277
if result.fingerprint:
278
print(f"Added encryption subkey: {result.fingerprint}")
279
else:
280
print(f"Failed to add subkey: {result.status}")
281
282
# Add authentication subkey for SSH usage
283
auth_result = gpg.add_subkey(
284
master_key=master_fingerprint,
285
master_passphrase='master_key_passphrase',
286
algorithm='rsa',
287
usage='auth',
288
expire='2y' # Expire in 2 years
289
)
290
291
# Add ECC subkey for modern cryptography
292
ecc_result = gpg.add_subkey(
293
master_key=master_fingerprint,
294
master_passphrase='master_key_passphrase',
295
algorithm='ecdsa',
296
usage='sign'
297
)
298
```
299
300
### Key Validation and Health Checks
301
302
```python
303
def validate_key_health(gpg_instance, key_identifier):
304
"""Comprehensive key health check and validation."""
305
306
# Get detailed key information
307
keys = gpg_instance.list_keys(keys=[key_identifier])
308
if not keys:
309
return {'status': 'not_found', 'message': 'Key not found in keyring'}
310
311
key = keys[0]
312
health_report = {
313
'key_id': key['keyid'],
314
'fingerprint': key['fingerprint'],
315
'status': 'healthy',
316
'issues': [],
317
'recommendations': []
318
}
319
320
# Check if key is expired
321
if key.get('expires'):
322
from datetime import datetime, timedelta
323
try:
324
exp_date = datetime.strptime(key['expires'], '%Y-%m-%d')
325
now = datetime.now()
326
327
if exp_date < now:
328
health_report['status'] = 'expired'
329
health_report['issues'].append('Key has expired')
330
elif exp_date < now + timedelta(days=30):
331
health_report['issues'].append('Key expires within 30 days')
332
health_report['recommendations'].append('Consider extending expiration')
333
except:
334
pass
335
336
# Check key strength
337
length = key.get('length', 0)
338
if length < 2048:
339
health_report['issues'].append(f'Key length {length} is weak by current standards')
340
health_report['recommendations'].append('Consider generating a new 2048+ bit key')
341
342
# Check for revocation
343
if key.get('revoked'):
344
health_report['status'] = 'revoked'
345
health_report['issues'].append('Key has been revoked')
346
347
# Check subkey health
348
subkey_issues = []
349
for subkey in key.get('subkeys', []):
350
if subkey.get('expires'):
351
try:
352
sub_exp = datetime.strptime(subkey['expires'], '%Y-%m-%d')
353
if sub_exp < datetime.now():
354
subkey_issues.append(f"Subkey {subkey['keyid']} has expired")
355
except:
356
pass
357
358
if subkey.get('length', 0) < 2048:
359
subkey_issues.append(f"Subkey {subkey['keyid']} has weak length")
360
361
if subkey_issues:
362
health_report['issues'].extend(subkey_issues)
363
health_report['recommendations'].append('Consider adding new subkeys')
364
365
# Check user ID validity
366
if not key.get('uids'):
367
health_report['issues'].append('No user IDs found')
368
else:
369
# Check for email validation (basic check)
370
valid_emails = []
371
for uid in key['uids']:
372
if '@' in uid and '.' in uid.split('@')[1]:
373
valid_emails.append(uid)
374
375
if not valid_emails:
376
health_report['recommendations'].append('Consider adding a valid email address')
377
378
return health_report
379
380
# Usage
381
health = validate_key_health(gpg, 'user@example.com')
382
print(f"Key health status: {health['status']}")
383
384
if health['issues']:
385
print("Issues found:")
386
for issue in health['issues']:
387
print(f" ⚠️ {issue}")
388
389
if health['recommendations']:
390
print("Recommendations:")
391
for rec in health['recommendations']:
392
print(f" 💡 {rec}")
393
```
394
395
### Advanced Key Discovery
396
397
```python
398
def discover_key_relationships(gpg_instance, target_keyid):
399
"""Discover relationships and signatures for a given key."""
400
401
# Get key with signature information
402
keys = gpg_instance.list_keys(keys=[target_keyid], sigs=True)
403
if not keys:
404
return None
405
406
key = keys[0]
407
relationships = {
408
'key_id': key['keyid'],
409
'fingerprint': key['fingerprint'],
410
'signed_by': [],
411
'certifications': [],
412
'cross_signatures': []
413
}
414
415
# Analyze signatures
416
for sig in key.get('sigs', []):
417
sig_info = {
418
'signer_id': sig.get('keyid'),
419
'signer_uid': sig.get('uid'),
420
'sig_class': sig.get('sig_class'),
421
'creation_date': sig.get('date')
422
}
423
424
if sig.get('sig_class') == '10': # Generic certification
425
relationships['certifications'].append(sig_info)
426
elif sig.get('sig_class') == '13': # Positive certification
427
relationships['signed_by'].append(sig_info)
428
elif sig.get('sig_class') == '18': # Subkey binding signature
429
relationships['cross_signatures'].append(sig_info)
430
431
return relationships
432
433
# Analyze key relationships
434
relationships = discover_key_relationships(gpg, 'alice@example.com')
435
if relationships:
436
print(f"Key relationships for {relationships['key_id']}:")
437
print(f"Certified by {len(relationships['signed_by'])} entities")
438
print(f"Has {len(relationships['certifications'])} certifications")
439
440
if relationships['signed_by']:
441
print("Signed by:")
442
for signer in relationships['signed_by'][:5]: # Show first 5
443
print(f" - {signer['signer_id']} ({signer['creation_date']})")
444
```