Skip to content

Instantly share code, notes, and snippets.

@yeiichi
Created May 2, 2025 00:27
Show Gist options
  • Save yeiichi/c1f78daec5794b96c058713589ecc5a1 to your computer and use it in GitHub Desktop.
Save yeiichi/c1f78daec5794b96c058713589ecc5a1 to your computer and use it in GitHub Desktop.
A class that provides a simplified interface for common GPG operations.
#!/usr/bin/env python3
"""
A class that provides a simplified interface for common GPG operations
like encryption, decryption, signing, and key management. `GPGWrapper`
"""
from functools import wraps
from pathlib import Path
from typing import Optional, List, Dict, Any, Callable
import gnupg
class GPGError(Exception):
"""Custom exception class for GPG-related errors."""
pass
def with_file_handling(func: Callable) -> Callable:
"""Decorator to handle file operations consistently."""
@wraps(func)
def wrapper(self, input_path: Path, *args, **kwargs):
if not input_path.exists():
raise GPGError(f"Input file not found: {input_path}")
try:
with input_path.open('rb') as f:
return func(self, f, *args, **kwargs)
except (IOError, OSError) as e:
raise GPGError(f"File operation failed: {e}")
return wrapper
class GPGWrapper:
"""
Wrapper for interacting with GPG for encryption, decryption, signing, and key management.
"""
DEFAULT_GPG_HOME = Path.home() / ".gnupg"
DEFAULT_GPG_BINARY = "gpg"
def __init__(
self,
gpg_home: Path = DEFAULT_GPG_HOME,
gpg_binary: str = DEFAULT_GPG_BINARY,
always_trust: bool = True
):
self.gpg = gnupg.GPG(gnupghome=str(gpg_home), gpgbinary=gpg_binary)
self.always_trust = always_trust
@with_file_handling
def encrypt_file(
self,
file_handle,
recipient_key: str,
output_path: Optional[Path] = None
) -> bool:
"""Encrypt a file for a specified recipient."""
encrypted_data = self.gpg.encrypt_file(
file_handle,
recipients=[recipient_key],
output=str(output_path) if output_path else None,
always_trust=self.always_trust
)
return encrypted_data.ok
@with_file_handling
def decrypt_file(
self,
file_handle,
output_path: Optional[Path] = None
) -> bool:
"""Decrypt an encrypted file."""
decrypted_data = self.gpg.decrypt_file(
file_handle,
output=str(output_path) if output_path else None
)
return decrypted_data.ok
@with_file_handling
def sign_file(
self,
file_handle,
signing_key: str,
output_path: Optional[Path] = None
) -> bool:
"""Sign a file with the specified key."""
signed_data = self.gpg.sign_file(
file_handle,
keyid=signing_key,
output=str(output_path) if output_path else None
)
return signed_data.fingerprint is not None
def import_key(self, key_file: Path) -> Any:
"""Import a GPG key from a file."""
if not key_file.exists():
raise GPGError(f"Key file not found: {key_file}")
return self.gpg.import_keys(key_file.read_text())
def list_keys(self, secret: bool = False) -> List[Dict[str, Any]]:
"""List all available GPG keys."""
return self.gpg.list_keys(secret=secret)
def is_key_present(self, key_id: str, secret: bool = False) -> bool:
"""Check if a specific key is present in the keyring."""
return any(key['keyid'] == key_id for key in self.list_keys(secret=secret))
if __name__ == "__main__":
print("GPG Encryption/Decryption Example")
try:
gpgw = GPGWrapper()
file_to_encrypt = Path(input("Enter the path to the file to encrypt: "))
recipient_fpr = input("Recipient fingerprint: ").strip()
success = gpgw.encrypt_file(file_to_encrypt, recipient_key=recipient_fpr,
output_path=Path("secret.txt.gpg"))
print("Encryption OK:", success)
decrypted_ok = gpgw.decrypt_file(Path("secret.txt.gpg"),
output_path=Path("decrypted.txt"))
print("Decryption OK:", decrypted_ok)
except GPGError as e_:
print(f"Error: {e_}")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment