A wrapper for the Gnu Privacy Guard (GPG or GnuPG)
—
Advanced key discovery, scanning, and analysis operations for examining keys without importing them, understanding key structures, and managing trust relationships.
Examine key files and data without importing them into the keyring, allowing for key analysis and validation before commitment.
def scan_keys(self, filename):
"""
Scan keys from a file without importing them to the keyring.
Parameters:
- filename (str): Path to key file to scan
Returns:
ScanKeys: List of key information found in the file
"""
def scan_keys_mem(self, key_data):
"""
Scan keys from memory without importing them to the keyring.
Parameters:
- key_data (str): ASCII-armored or binary key data to scan
Returns:
ScanKeys: List of key information found in the data
"""Manage trust levels in the web of trust system for establishing key authenticity and reliability.
def trust_keys(self, fingerprints, trustlevel):
"""
Set trust level for one or more keys in the web of trust.
Parameters:
- fingerprints (str|list): Key fingerprints to set trust for
- trustlevel (str): Trust level to assign
'1' = I don't know or won't say
'2' = I do NOT trust
'3' = I trust marginally
'4' = I trust fully
'5' = I trust ultimately (own keys)
Returns:
TrustResult: Result with trust operation status
"""Perform specialized key operations including subkey management and key relationship analysis.
def add_subkey(self, master_key, master_passphrase=None, algorithm='rsa',
usage='encrypt', expire='-'):
"""
Add a subkey to an existing master key.
Parameters:
- master_key (str): Master key fingerprint or ID
- master_passphrase (str): Master key passphrase
- algorithm (str): Subkey algorithm ('rsa', 'dsa', 'ecdsa', 'eddsa')
- usage (str): Subkey usage ('encrypt', 'sign', 'auth')
- expire (str): Expiration ('-' for same as master, '0' for no expiration)
Returns:
AddSubkey: Result with new subkey information
"""class ScanKeys(ListKeys):
# Inherits from ListKeys - same structure but for scanned keys
# List of key dictionaries containing detailed key information:
# - type: Key type ('pub', 'sec', 'sub', 'ssb')
# - length: Key length in bits
# - algo: Algorithm number
# - keyid: Key ID (short form)
# - date: Creation date
# - expires: Expiration date (if any)
# - dummy: Dummy key indicator
# - ownertrust: Owner trust level
# - sig_class: Signature class
# - capabilities: Key capabilities string
# - issuer: Key issuer
# - flag: Key flags
# - token: Token serial number
# - hash_algo: Hash algorithm number
# - curve: Curve name for ECC keys
# - fingerprint: Full key fingerprint
# - uids: List of user IDs associated with the key
# - sigs: List of signatures on the key (if requested)
# - subkeys: List of subkeys under this master key
pass
class TrustResult(StatusHandler):
# Result of trust level setting operations
pass
class AddSubkey(StatusHandler):
type: str # Subkey type/algorithm
fingerprint: str # New subkey fingerprintimport gnupg
gpg = gnupg.GPG()
# Scan a key file without importing
scanned_keys = gpg.scan_keys('/path/to/keyfile.asc')
print(f"Found {len(scanned_keys)} keys in file:")
for key in scanned_keys:
print(f"\nKey ID: {key['keyid']}")
print(f"Fingerprint: {key['fingerprint']}")
print(f"Algorithm: {key['algo']} ({key['length']} bits)")
print(f"Created: {key['date']}")
if key['expires']:
print(f"Expires: {key['expires']}")
print(f"User IDs:")
for uid in key['uids']:
print(f" - {uid}")
print(f"Capabilities: {key['capabilities']}")
if key['subkeys']:
print(f"Subkeys:")
for subkey in key['subkeys']:
print(f" - {subkey['keyid']} ({subkey['length']} bits, {subkey['capabilities']})")# Analyze key data received over network or from database
key_data = """-----BEGIN PGP PUBLIC KEY BLOCK-----
mQENBF... (key data here) ...
-----END PGP PUBLIC KEY BLOCK-----"""
scanned = gpg.scan_keys_mem(key_data)
def analyze_key_strength(key_info):
"""Analyze key strength and provide recommendations."""
analysis = {
'strength': 'unknown',
'recommendations': [],
'warnings': []
}
# Check key length
length = key_info.get('length', 0)
algo = key_info.get('algo', 0)
if algo == 1: # RSA
if length < 2048:
analysis['strength'] = 'weak'
analysis['warnings'].append(f'RSA key length {length} is too short')
analysis['recommendations'].append('Use RSA 2048 bits or higher')
elif length < 3072:
analysis['strength'] = 'acceptable'
else:
analysis['strength'] = 'strong'
elif algo in [16, 20]: # ElGamal
if length < 2048:
analysis['strength'] = 'weak'
analysis['warnings'].append(f'ElGamal key length {length} is too short')
elif algo == 17: # DSA
if length < 2048:
analysis['strength'] = 'weak'
analysis['warnings'].append('DSA-1024 is deprecated')
analysis['recommendations'].append('Use RSA-2048 or higher')
# Check expiration
if not key_info.get('expires'):
analysis['recommendations'].append('Consider setting an expiration date')
else:
from datetime import datetime
try:
exp_date = datetime.strptime(key_info['expires'], '%Y-%m-%d')
if exp_date < datetime.now():
analysis['warnings'].append('Key has expired')
except:
pass
return analysis
# Analyze each scanned key
for key in scanned:
analysis = analyze_key_strength(key)
print(f"\nKey {key['keyid']} analysis:")
print(f"Strength: {analysis['strength']}")
for warning in analysis['warnings']:
print(f"⚠️ Warning: {warning}")
for rec in analysis['recommendations']:
print(f"💡 Recommendation: {rec}")# Set trust levels for keys
def setup_trust_relationships(gpg_instance):
"""Set up trust relationships for a set of known keys."""
trust_assignments = {
# Ultimate trust (own keys)
'5': ['ABCD1234DEADBEEF5678CAFE'], # Your own key
# Full trust (fully trusted colleagues)
'4': [
'BEEF5678CAFE1234DEAD9876', # Alice's key
'CAFE1234DEAD5678BEEF9876', # Bob's key
],
# Marginal trust (somewhat trusted)
'3': [
'DEAD5678BEEF1234CAFE9876', # Charlie's key
],
# No trust
'2': [
'BADD0G15DEAD5678BEEF1234', # Revoked or compromised key
]
}
for trust_level, fingerprints in trust_assignments.items():
if fingerprints:
result = gpg_instance.trust_keys(fingerprints, trust_level)
if result.status == 'success':
level_names = {'2': 'no trust', '3': 'marginal', '4': 'full', '5': 'ultimate'}
print(f"Set {level_names.get(trust_level, trust_level)} trust for {len(fingerprints)} keys")
# Apply trust settings
setup_trust_relationships(gpg)
# Check current trust levels by listing keys
keys = gpg.list_keys()
for key in keys:
trust = key.get('ownertrust', 'unknown')
print(f"Key {key['keyid']}: trust level {trust}")# Add encryption subkey to existing signing key
master_fingerprint = 'DEADBEEFCAFEBABE1234567890ABCDEF12345678'
# Add RSA encryption subkey
result = gpg.add_subkey(
master_key=master_fingerprint,
master_passphrase='master_key_passphrase',
algorithm='rsa',
usage='encrypt'
)
if result.fingerprint:
print(f"Added encryption subkey: {result.fingerprint}")
else:
print(f"Failed to add subkey: {result.status}")
# Add authentication subkey for SSH usage
auth_result = gpg.add_subkey(
master_key=master_fingerprint,
master_passphrase='master_key_passphrase',
algorithm='rsa',
usage='auth',
expire='2y' # Expire in 2 years
)
# Add ECC subkey for modern cryptography
ecc_result = gpg.add_subkey(
master_key=master_fingerprint,
master_passphrase='master_key_passphrase',
algorithm='ecdsa',
usage='sign'
)def validate_key_health(gpg_instance, key_identifier):
"""Comprehensive key health check and validation."""
# Get detailed key information
keys = gpg_instance.list_keys(keys=[key_identifier])
if not keys:
return {'status': 'not_found', 'message': 'Key not found in keyring'}
key = keys[0]
health_report = {
'key_id': key['keyid'],
'fingerprint': key['fingerprint'],
'status': 'healthy',
'issues': [],
'recommendations': []
}
# Check if key is expired
if key.get('expires'):
from datetime import datetime, timedelta
try:
exp_date = datetime.strptime(key['expires'], '%Y-%m-%d')
now = datetime.now()
if exp_date < now:
health_report['status'] = 'expired'
health_report['issues'].append('Key has expired')
elif exp_date < now + timedelta(days=30):
health_report['issues'].append('Key expires within 30 days')
health_report['recommendations'].append('Consider extending expiration')
except:
pass
# Check key strength
length = key.get('length', 0)
if length < 2048:
health_report['issues'].append(f'Key length {length} is weak by current standards')
health_report['recommendations'].append('Consider generating a new 2048+ bit key')
# Check for revocation
if key.get('revoked'):
health_report['status'] = 'revoked'
health_report['issues'].append('Key has been revoked')
# Check subkey health
subkey_issues = []
for subkey in key.get('subkeys', []):
if subkey.get('expires'):
try:
sub_exp = datetime.strptime(subkey['expires'], '%Y-%m-%d')
if sub_exp < datetime.now():
subkey_issues.append(f"Subkey {subkey['keyid']} has expired")
except:
pass
if subkey.get('length', 0) < 2048:
subkey_issues.append(f"Subkey {subkey['keyid']} has weak length")
if subkey_issues:
health_report['issues'].extend(subkey_issues)
health_report['recommendations'].append('Consider adding new subkeys')
# Check user ID validity
if not key.get('uids'):
health_report['issues'].append('No user IDs found')
else:
# Check for email validation (basic check)
valid_emails = []
for uid in key['uids']:
if '@' in uid and '.' in uid.split('@')[1]:
valid_emails.append(uid)
if not valid_emails:
health_report['recommendations'].append('Consider adding a valid email address')
return health_report
# Usage
health = validate_key_health(gpg, 'user@example.com')
print(f"Key health status: {health['status']}")
if health['issues']:
print("Issues found:")
for issue in health['issues']:
print(f" ⚠️ {issue}")
if health['recommendations']:
print("Recommendations:")
for rec in health['recommendations']:
print(f" 💡 {rec}")def discover_key_relationships(gpg_instance, target_keyid):
"""Discover relationships and signatures for a given key."""
# Get key with signature information
keys = gpg_instance.list_keys(keys=[target_keyid], sigs=True)
if not keys:
return None
key = keys[0]
relationships = {
'key_id': key['keyid'],
'fingerprint': key['fingerprint'],
'signed_by': [],
'certifications': [],
'cross_signatures': []
}
# Analyze signatures
for sig in key.get('sigs', []):
sig_info = {
'signer_id': sig.get('keyid'),
'signer_uid': sig.get('uid'),
'sig_class': sig.get('sig_class'),
'creation_date': sig.get('date')
}
if sig.get('sig_class') == '10': # Generic certification
relationships['certifications'].append(sig_info)
elif sig.get('sig_class') == '13': # Positive certification
relationships['signed_by'].append(sig_info)
elif sig.get('sig_class') == '18': # Subkey binding signature
relationships['cross_signatures'].append(sig_info)
return relationships
# Analyze key relationships
relationships = discover_key_relationships(gpg, 'alice@example.com')
if relationships:
print(f"Key relationships for {relationships['key_id']}:")
print(f"Certified by {len(relationships['signed_by'])} entities")
print(f"Has {len(relationships['certifications'])} certifications")
if relationships['signed_by']:
print("Signed by:")
for signer in relationships['signed_by'][:5]: # Show first 5
print(f" - {signer['signer_id']} ({signer['creation_date']})")Install with Tessl CLI
npx tessl i tessl/pypi-python-gnupg