CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-python-gnupg

A wrapper for the Gnu Privacy Guard (GPG or GnuPG)

Pending
Overview
Eval results
Files

key-discovery.mddocs/

Key Discovery and Analysis

Advanced key discovery, scanning, and analysis operations for examining keys without importing them, understanding key structures, and managing trust relationships.

Capabilities

Key Scanning Without Import

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
    """

Trust Management

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
    """

Advanced Key Operations

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
    """

Result Types

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 fingerprint

Usage Examples

Key File Analysis

import 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']})")

Key Data Analysis from Memory

# 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}")

Trust Management

# 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}")

Subkey Management

# 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'
)

Key Validation and Health Checks

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}")

Advanced Key Discovery

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

docs

encryption-decryption.md

gpg-instance.md

index.md

key-discovery.md

key-management.md

keyserver-operations.md

signing-verification.md

tile.json