Created
May 2, 2025 00:27
-
-
Save yeiichi/c1f78daec5794b96c058713589ecc5a1 to your computer and use it in GitHub Desktop.
A class that provides a simplified interface for common GPG operations.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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