Skip to content

File Encryption Tool with Advanced Security

Abstract

Create a professional file encryption and decryption tool that provides enterprise-grade security using AES-256 encryption with PBKDF2 key derivation. This project demonstrates advanced cryptography concepts, secure password handling, file integrity verification, and batch processing capabilities.

Prerequisites

  • Python 3.7 or above
  • Text Editor or IDE
  • Solid understanding of Python syntax and OOP concepts
  • Knowledge of cryptography and security principles
  • Familiarity with file handling and system operations
  • Understanding of password security and key derivation
  • Basic knowledge of encryption algorithms and best practices

Getting Started

Create a new project

  1. Create a new project folder and name it fileEncryptionToolfileEncryptionTool.
  2. Create a new file and name it fileencryption.pyfileencryption.py.
  3. Install required dependencies: pip install cryptographypip install cryptography
  4. Open the project folder in your favorite text editor or IDE.
  5. Copy the code below and paste it into your fileencryption.pyfileencryption.py file.

Write the code

  1. Add the following code to your fileencryption.pyfileencryption.py file.
⚙️ File Encryption Tool with Advanced Security
File Encryption Tool with Advanced Security
# File Encryption/Decryption Tool
 
import os
import hashlib
import secrets
import base64
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
import zipfile
import shutil
from pathlib import Path
from typing import Optional, List
import json
import getpass
from datetime import datetime
 
class FileEncryption:
    def __init__(self):
        self.key = None
        self.fernet = None
        
    def generate_key_from_password(self, password: str, salt: bytes = None) -> tuple:
        """Generate encryption key from password using PBKDF2"""
        if salt is None:
            salt = os.urandom(16)
        
        kdf = PBKDF2HMAC(
            algorithm=hashes.SHA256(),
            length=32,
            salt=salt,
            iterations=100000,
        )
        
        key = base64.urlsafe_b64encode(kdf.derive(password.encode()))
        return key, salt
    
    def set_key(self, password: str, salt: bytes = None) -> bytes:
        """Set encryption key from password"""
        self.key, salt = self.generate_key_from_password(password, salt)
        self.fernet = Fernet(self.key)
        return salt
    
    def generate_random_key(self) -> bytes:
        """Generate a random encryption key"""
        self.key = Fernet.generate_key()
        self.fernet = Fernet(self.key)
        return self.key
    
    def encrypt_file(self, file_path: str, output_path: str = None, 
                    preserve_original: bool = True) -> dict:
        """Encrypt a single file"""
        if not self.fernet:
            raise ValueError("No encryption key set")
        
        file_path = Path(file_path)
        if not file_path.exists():
            raise FileNotFoundError(f"File not found: {file_path}")
        
        # Determine output path
        if output_path is None:
            output_path = file_path.with_suffix(file_path.suffix + '.encrypted')
        else:
            output_path = Path(output_path)
        
        try:
            # Read and encrypt file
            with open(file_path, 'rb') as file:
                file_data = file.read()
            
            encrypted_data = self.fernet.encrypt(file_data)
            
            # Create metadata
            metadata = {
                'original_name': file_path.name,
                'original_size': len(file_data),
                'encrypted_size': len(encrypted_data),
                'timestamp': datetime.now().isoformat(),
                'checksum': hashlib.sha256(file_data).hexdigest()
            }
            
            # Write encrypted file with metadata
            with open(output_path, 'wb') as encrypted_file:
                # Write metadata as JSON header
                metadata_json = json.dumps(metadata).encode()
                metadata_size = len(metadata_json)
                
                # Write metadata size (4 bytes) + metadata + encrypted data
                encrypted_file.write(metadata_size.to_bytes(4, byteorder='big'))
                encrypted_file.write(metadata_json)
                encrypted_file.write(encrypted_data)
            
            # Remove original file if not preserving
            if not preserve_original:
                os.remove(file_path)
            
            return {
                'status': 'success',
                'original_file': str(file_path),
                'encrypted_file': str(output_path),
                'metadata': metadata
            }
            
        except Exception as e:
            return {
                'status': 'error',
                'error': str(e),
                'original_file': str(file_path)
            }
    
    def decrypt_file(self, encrypted_file_path: str, output_path: str = None,
                    preserve_encrypted: bool = True) -> dict:
        """Decrypt a single file"""
        if not self.fernet:
            raise ValueError("No encryption key set")
        
        encrypted_file_path = Path(encrypted_file_path)
        if not encrypted_file_path.exists():
            raise FileNotFoundError(f"Encrypted file not found: {encrypted_file_path}")
        
        try:
            # Read encrypted file
            with open(encrypted_file_path, 'rb') as encrypted_file:
                # Read metadata size
                metadata_size_bytes = encrypted_file.read(4)
                if len(metadata_size_bytes) != 4:
                    raise ValueError("Invalid encrypted file format")
                
                metadata_size = int.from_bytes(metadata_size_bytes, byteorder='big')
                
                # Read metadata
                metadata_json = encrypted_file.read(metadata_size)
                metadata = json.loads(metadata_json.decode())
                
                # Read encrypted data
                encrypted_data = encrypted_file.read()
            
            # Decrypt data
            decrypted_data = self.fernet.decrypt(encrypted_data)
            
            # Verify checksum
            if hashlib.sha256(decrypted_data).hexdigest() != metadata['checksum']:
                raise ValueError("File integrity check failed - data may be corrupted")
            
            # Determine output path
            if output_path is None:
                output_path = encrypted_file_path.parent / metadata['original_name']
            else:
                output_path = Path(output_path)
            
            # Write decrypted file
            with open(output_path, 'wb') as decrypted_file:
                decrypted_file.write(decrypted_data)
            
            # Remove encrypted file if not preserving
            if not preserve_encrypted:
                os.remove(encrypted_file_path)
            
            return {
                'status': 'success',
                'encrypted_file': str(encrypted_file_path),
                'decrypted_file': str(output_path),
                'metadata': metadata
            }
            
        except Exception as e:
            return {
                'status': 'error',
                'error': str(e),
                'encrypted_file': str(encrypted_file_path)
            }
    
    def encrypt_directory(self, directory_path: str, output_path: str = None,
                         include_subdirs: bool = True) -> dict:
        """Encrypt all files in a directory"""
        directory_path = Path(directory_path)
        if not directory_path.exists():
            raise FileNotFoundError(f"Directory not found: {directory_path}")
        
        if output_path is None:
            output_path = directory_path.with_suffix('.encrypted_archive')
        else:
            output_path = Path(output_path)
        
        results = []
        temp_dir = Path("temp_encryption")
        temp_dir.mkdir(exist_ok=True)
        
        try:
            # Get all files to encrypt
            if include_subdirs:
                files = list(directory_path.rglob('*'))
            else:
                files = list(directory_path.glob('*'))
            
            files = [f for f in files if f.is_file()]
            
            # Encrypt each file
            for file_path in files:
                relative_path = file_path.relative_to(directory_path)
                temp_encrypted_path = temp_dir / (str(relative_path) + '.encrypted')
                temp_encrypted_path.parent.mkdir(parents=True, exist_ok=True)
                
                result = self.encrypt_file(str(file_path), str(temp_encrypted_path))
                results.append(result)
            
            # Create archive of encrypted files
            with zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
                for root, dirs, files in os.walk(temp_dir):
                    for file in files:
                        file_path = Path(root) / file
                        arcname = file_path.relative_to(temp_dir)
                        zipf.write(file_path, arcname)
            
            # Clean up temp directory
            shutil.rmtree(temp_dir)
            
            successful_encryptions = sum(1 for r in results if r['status'] == 'success')
            
            return {
                'status': 'success',
                'directory': str(directory_path),
                'archive': str(output_path),
                'total_files': len(results),
                'successful': successful_encryptions,
                'failed': len(results) - successful_encryptions,
                'results': results
            }
            
        except Exception as e:
            # Clean up on error
            if temp_dir.exists():
                shutil.rmtree(temp_dir)
            
            return {
                'status': 'error',
                'error': str(e),
                'directory': str(directory_path)
            }
    
    def decrypt_directory(self, archive_path: str, output_directory: str = None) -> dict:
        """Decrypt an encrypted directory archive"""
        archive_path = Path(archive_path)
        if not archive_path.exists():
            raise FileNotFoundError(f"Archive not found: {archive_path}")
        
        if output_directory is None:
            output_directory = archive_path.with_suffix('')
        else:
            output_directory = Path(output_directory)
        
        output_directory.mkdir(exist_ok=True)
        temp_dir = Path("temp_decryption")
        
        try:
            # Extract archive
            with zipfile.ZipFile(archive_path, 'r') as zipf:
                zipf.extractall(temp_dir)
            
            results = []
            
            # Decrypt each file
            for encrypted_file in temp_dir.rglob('*.encrypted'):
                relative_path = encrypted_file.relative_to(temp_dir)
                relative_path = Path(str(relative_path).replace('.encrypted', ''))
                
                output_file_path = output_directory / relative_path
                output_file_path.parent.mkdir(parents=True, exist_ok=True)
                
                result = self.decrypt_file(str(encrypted_file), str(output_file_path))
                results.append(result)
            
            # Clean up temp directory
            shutil.rmtree(temp_dir)
            
            successful_decryptions = sum(1 for r in results if r['status'] == 'success')
            
            return {
                'status': 'success',
                'archive': str(archive_path),
                'output_directory': str(output_directory),
                'total_files': len(results),
                'successful': successful_decryptions,
                'failed': len(results) - successful_decryptions,
                'results': results
            }
            
        except Exception as e:
            # Clean up on error
            if temp_dir.exists():
                shutil.rmtree(temp_dir)
            
            return {
                'status': 'error',
                'error': str(e),
                'archive': str(archive_path)
            }
    
    def save_key_to_file(self, key_file_path: str, password: str = None):
        """Save encryption key to file (optionally password protected)"""
        if not self.key:
            raise ValueError("No key to save")
        
        key_data = {
            'key': base64.b64encode(self.key).decode(),
            'timestamp': datetime.now().isoformat()
        }
        
        if password:
            # Encrypt the key data with password
            temp_encryption = FileEncryption()
            salt = temp_encryption.set_key(password)
            
            key_json = json.dumps(key_data).encode()
            encrypted_key = temp_encryption.fernet.encrypt(key_json)
            
            protected_data = {
                'encrypted': True,
                'salt': base64.b64encode(salt).decode(),
                'data': base64.b64encode(encrypted_key).decode()
            }
            
            with open(key_file_path, 'w') as f:
                json.dump(protected_data, f, indent=2)
        else:
            key_data['encrypted'] = False
            with open(key_file_path, 'w') as f:
                json.dump(key_data, f, indent=2)
    
    def load_key_from_file(self, key_file_path: str, password: str = None):
        """Load encryption key from file"""
        with open(key_file_path, 'r') as f:
            key_data = json.load(f)
        
        if key_data.get('encrypted', False):
            if not password:
                raise ValueError("Password required for encrypted key file")
            
            # Decrypt the key data
            salt = base64.b64decode(key_data['salt'])
            encrypted_data = base64.b64decode(key_data['data'])
            
            temp_encryption = FileEncryption()
            temp_encryption.set_key(password, salt)
            
            decrypted_json = temp_encryption.fernet.decrypt(encrypted_data)
            actual_key_data = json.loads(decrypted_json.decode())
            
            self.key = base64.b64decode(actual_key_data['key'])
        else:
            self.key = base64.b64decode(key_data['key'])
        
        self.fernet = Fernet(self.key)
 
def create_sample_files():
    """Create sample files for testing"""
    sample_dir = Path("sample_files")
    sample_dir.mkdir(exist_ok=True)
    
    # Create text file
    with open(sample_dir / "sample.txt", 'w') as f:
        f.write("This is a sample text file for encryption testing.\n")
        f.write("It contains multiple lines of text.\n")
        f.write("The content will be encrypted and then decrypted.\n")
    
    # Create JSON file
    sample_data = {
        "name": "John Doe",
        "age": 30,
        "city": "New York",
        "hobbies": ["reading", "coding", "traveling"]
    }
    
    with open(sample_dir / "data.json", 'w') as f:
        json.dump(sample_data, f, indent=2)
    
    # Create binary file
    with open(sample_dir / "binary_data.bin", 'wb') as f:
        f.write(os.urandom(1024))  # 1KB of random data
    
    # Create subdirectory with files
    sub_dir = sample_dir / "subdirectory"
    sub_dir.mkdir(exist_ok=True)
    
    with open(sub_dir / "nested_file.txt", 'w') as f:
        f.write("This is a file in a subdirectory.")
    
    print(f"Sample files created in {sample_dir}")
 
def main():
    """Main function to run the file encryption tool"""
    encryption = FileEncryption()
    
    while True:
        print("\n=== File Encryption/Decryption Tool ===")
        print("1. Set password for encryption")
        print("2. Generate random key")
        print("3. Encrypt single file")
        print("4. Decrypt single file")
        print("5. Encrypt directory")
        print("6. Decrypt directory archive")
        print("7. Save key to file")
        print("8. Load key from file")
        print("9. Create sample files for testing")
        print("10. View current key status")
        print("0. Exit")
        
        try:
            choice = input("\nEnter your choice: ").strip()
            
            if choice == '1':
                password = getpass.getpass("Enter password for encryption: ")
                if len(password) < 8:
                    print("Warning: Password should be at least 8 characters long")
                
                salt = encryption.set_key(password)
                print("✓ Encryption key generated from password")
                print(f"Salt: {base64.b64encode(salt).decode()}")
            
            elif choice == '2':
                key = encryption.generate_random_key()
                print("✓ Random encryption key generated")
                print(f"Key: {base64.b64encode(key).decode()}")
                print("⚠ Make sure to save this key - you'll need it for decryption!")
            
            elif choice == '3':
                if not encryption.fernet:
                    print("❌ Please set an encryption key first (option 1 or 2)")
                    continue
                
                file_path = input("Enter file path to encrypt: ").strip()
                preserve = input("Preserve original file? (y/n): ").strip().lower() == 'y'
                
                result = encryption.encrypt_file(file_path, preserve_original=preserve)
                
                if result['status'] == 'success':
                    print("✓ File encrypted successfully!")
                    print(f"Original: {result['original_file']}")
                    print(f"Encrypted: {result['encrypted_file']}")
                    print(f"Original size: {result['metadata']['original_size']} bytes")
                    print(f"Encrypted size: {result['metadata']['encrypted_size']} bytes")
                else:
                    print(f"❌ Encryption failed: {result['error']}")
            
            elif choice == '4':
                if not encryption.fernet:
                    print("❌ Please set an encryption key first (option 1 or 2)")
                    continue
                
                file_path = input("Enter encrypted file path: ").strip()
                preserve = input("Preserve encrypted file? (y/n): ").strip().lower() == 'y'
                
                result = encryption.decrypt_file(file_path, preserve_encrypted=preserve)
                
                if result['status'] == 'success':
                    print("✓ File decrypted successfully!")
                    print(f"Encrypted: {result['encrypted_file']}")
                    print(f"Decrypted: {result['decrypted_file']}")
                    print(f"Original name: {result['metadata']['original_name']}")
                    print(f"Checksum verified: ✓")
                else:
                    print(f"❌ Decryption failed: {result['error']}")
            
            elif choice == '5':
                if not encryption.fernet:
                    print("❌ Please set an encryption key first (option 1 or 2)")
                    continue
                
                dir_path = input("Enter directory path to encrypt: ").strip()
                include_subdirs = input("Include subdirectories? (y/n): ").strip().lower() == 'y'
                
                print("Encrypting directory... This may take a while for large directories.")
                result = encryption.encrypt_directory(dir_path, include_subdirs=include_subdirs)
                
                if result['status'] == 'success':
                    print("✓ Directory encrypted successfully!")
                    print(f"Directory: {result['directory']}")
                    print(f"Archive: {result['archive']}")
                    print(f"Total files: {result['total_files']}")
                    print(f"Successful: {result['successful']}")
                    print(f"Failed: {result['failed']}")
                else:
                    print(f"❌ Directory encryption failed: {result['error']}")
            
            elif choice == '6':
                if not encryption.fernet:
                    print("❌ Please set an encryption key first (option 1 or 2)")
                    continue
                
                archive_path = input("Enter encrypted archive path: ").strip()
                output_dir = input("Enter output directory (or press Enter for default): ").strip()
                
                if not output_dir:
                    output_dir = None
                
                print("Decrypting archive... This may take a while for large archives.")
                result = encryption.decrypt_directory(archive_path, output_dir)
                
                if result['status'] == 'success':
                    print("✓ Archive decrypted successfully!")
                    print(f"Archive: {result['archive']}")
                    print(f"Output directory: {result['output_directory']}")
                    print(f"Total files: {result['total_files']}")
                    print(f"Successful: {result['successful']}")
                    print(f"Failed: {result['failed']}")
                else:
                    print(f"❌ Archive decryption failed: {result['error']}")
            
            elif choice == '7':
                if not encryption.key:
                    print("❌ No encryption key to save")
                    continue
                
                key_file = input("Enter key file path: ").strip()
                protect = input("Protect key with password? (y/n): ").strip().lower() == 'y'
                
                if protect:
                    password = getpass.getpass("Enter password to protect key: ")
                    encryption.save_key_to_file(key_file, password)
                else:
                    encryption.save_key_to_file(key_file)
                
                print("✓ Key saved successfully!")
            
            elif choice == '8':
                key_file = input("Enter key file path: ").strip()
                
                try:
                    # Check if file is encrypted
                    with open(key_file, 'r') as f:
                        key_data = json.load(f)
                    
                    if key_data.get('encrypted', False):
                        password = getpass.getpass("Enter password for key file: ")
                        encryption.load_key_from_file(key_file, password)
                    else:
                        encryption.load_key_from_file(key_file)
                    
                    print("✓ Key loaded successfully!")
                    
                except Exception as e:
                    print(f"❌ Failed to load key: {e}")
            
            elif choice == '9':
                create_sample_files()
            
            elif choice == '10':
                if encryption.key:
                    print("✓ Encryption key is set")
                    print(f"Key: {base64.b64encode(encryption.key).decode()[:32]}...")
                else:
                    print("❌ No encryption key set")
            
            elif choice == '0':
                print("Thank you for using the File Encryption Tool!")
                break
            
            else:
                print("Invalid choice. Please try again.")
        
        except KeyboardInterrupt:
            print("\n\nGoodbye!")
            break
        except Exception as e:
            print(f"An error occurred: {e}")
 
if __name__ == "__main__":
    main()
 
File Encryption Tool with Advanced Security
# File Encryption/Decryption Tool
 
import os
import hashlib
import secrets
import base64
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
import zipfile
import shutil
from pathlib import Path
from typing import Optional, List
import json
import getpass
from datetime import datetime
 
class FileEncryption:
    def __init__(self):
        self.key = None
        self.fernet = None
        
    def generate_key_from_password(self, password: str, salt: bytes = None) -> tuple:
        """Generate encryption key from password using PBKDF2"""
        if salt is None:
            salt = os.urandom(16)
        
        kdf = PBKDF2HMAC(
            algorithm=hashes.SHA256(),
            length=32,
            salt=salt,
            iterations=100000,
        )
        
        key = base64.urlsafe_b64encode(kdf.derive(password.encode()))
        return key, salt
    
    def set_key(self, password: str, salt: bytes = None) -> bytes:
        """Set encryption key from password"""
        self.key, salt = self.generate_key_from_password(password, salt)
        self.fernet = Fernet(self.key)
        return salt
    
    def generate_random_key(self) -> bytes:
        """Generate a random encryption key"""
        self.key = Fernet.generate_key()
        self.fernet = Fernet(self.key)
        return self.key
    
    def encrypt_file(self, file_path: str, output_path: str = None, 
                    preserve_original: bool = True) -> dict:
        """Encrypt a single file"""
        if not self.fernet:
            raise ValueError("No encryption key set")
        
        file_path = Path(file_path)
        if not file_path.exists():
            raise FileNotFoundError(f"File not found: {file_path}")
        
        # Determine output path
        if output_path is None:
            output_path = file_path.with_suffix(file_path.suffix + '.encrypted')
        else:
            output_path = Path(output_path)
        
        try:
            # Read and encrypt file
            with open(file_path, 'rb') as file:
                file_data = file.read()
            
            encrypted_data = self.fernet.encrypt(file_data)
            
            # Create metadata
            metadata = {
                'original_name': file_path.name,
                'original_size': len(file_data),
                'encrypted_size': len(encrypted_data),
                'timestamp': datetime.now().isoformat(),
                'checksum': hashlib.sha256(file_data).hexdigest()
            }
            
            # Write encrypted file with metadata
            with open(output_path, 'wb') as encrypted_file:
                # Write metadata as JSON header
                metadata_json = json.dumps(metadata).encode()
                metadata_size = len(metadata_json)
                
                # Write metadata size (4 bytes) + metadata + encrypted data
                encrypted_file.write(metadata_size.to_bytes(4, byteorder='big'))
                encrypted_file.write(metadata_json)
                encrypted_file.write(encrypted_data)
            
            # Remove original file if not preserving
            if not preserve_original:
                os.remove(file_path)
            
            return {
                'status': 'success',
                'original_file': str(file_path),
                'encrypted_file': str(output_path),
                'metadata': metadata
            }
            
        except Exception as e:
            return {
                'status': 'error',
                'error': str(e),
                'original_file': str(file_path)
            }
    
    def decrypt_file(self, encrypted_file_path: str, output_path: str = None,
                    preserve_encrypted: bool = True) -> dict:
        """Decrypt a single file"""
        if not self.fernet:
            raise ValueError("No encryption key set")
        
        encrypted_file_path = Path(encrypted_file_path)
        if not encrypted_file_path.exists():
            raise FileNotFoundError(f"Encrypted file not found: {encrypted_file_path}")
        
        try:
            # Read encrypted file
            with open(encrypted_file_path, 'rb') as encrypted_file:
                # Read metadata size
                metadata_size_bytes = encrypted_file.read(4)
                if len(metadata_size_bytes) != 4:
                    raise ValueError("Invalid encrypted file format")
                
                metadata_size = int.from_bytes(metadata_size_bytes, byteorder='big')
                
                # Read metadata
                metadata_json = encrypted_file.read(metadata_size)
                metadata = json.loads(metadata_json.decode())
                
                # Read encrypted data
                encrypted_data = encrypted_file.read()
            
            # Decrypt data
            decrypted_data = self.fernet.decrypt(encrypted_data)
            
            # Verify checksum
            if hashlib.sha256(decrypted_data).hexdigest() != metadata['checksum']:
                raise ValueError("File integrity check failed - data may be corrupted")
            
            # Determine output path
            if output_path is None:
                output_path = encrypted_file_path.parent / metadata['original_name']
            else:
                output_path = Path(output_path)
            
            # Write decrypted file
            with open(output_path, 'wb') as decrypted_file:
                decrypted_file.write(decrypted_data)
            
            # Remove encrypted file if not preserving
            if not preserve_encrypted:
                os.remove(encrypted_file_path)
            
            return {
                'status': 'success',
                'encrypted_file': str(encrypted_file_path),
                'decrypted_file': str(output_path),
                'metadata': metadata
            }
            
        except Exception as e:
            return {
                'status': 'error',
                'error': str(e),
                'encrypted_file': str(encrypted_file_path)
            }
    
    def encrypt_directory(self, directory_path: str, output_path: str = None,
                         include_subdirs: bool = True) -> dict:
        """Encrypt all files in a directory"""
        directory_path = Path(directory_path)
        if not directory_path.exists():
            raise FileNotFoundError(f"Directory not found: {directory_path}")
        
        if output_path is None:
            output_path = directory_path.with_suffix('.encrypted_archive')
        else:
            output_path = Path(output_path)
        
        results = []
        temp_dir = Path("temp_encryption")
        temp_dir.mkdir(exist_ok=True)
        
        try:
            # Get all files to encrypt
            if include_subdirs:
                files = list(directory_path.rglob('*'))
            else:
                files = list(directory_path.glob('*'))
            
            files = [f for f in files if f.is_file()]
            
            # Encrypt each file
            for file_path in files:
                relative_path = file_path.relative_to(directory_path)
                temp_encrypted_path = temp_dir / (str(relative_path) + '.encrypted')
                temp_encrypted_path.parent.mkdir(parents=True, exist_ok=True)
                
                result = self.encrypt_file(str(file_path), str(temp_encrypted_path))
                results.append(result)
            
            # Create archive of encrypted files
            with zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
                for root, dirs, files in os.walk(temp_dir):
                    for file in files:
                        file_path = Path(root) / file
                        arcname = file_path.relative_to(temp_dir)
                        zipf.write(file_path, arcname)
            
            # Clean up temp directory
            shutil.rmtree(temp_dir)
            
            successful_encryptions = sum(1 for r in results if r['status'] == 'success')
            
            return {
                'status': 'success',
                'directory': str(directory_path),
                'archive': str(output_path),
                'total_files': len(results),
                'successful': successful_encryptions,
                'failed': len(results) - successful_encryptions,
                'results': results
            }
            
        except Exception as e:
            # Clean up on error
            if temp_dir.exists():
                shutil.rmtree(temp_dir)
            
            return {
                'status': 'error',
                'error': str(e),
                'directory': str(directory_path)
            }
    
    def decrypt_directory(self, archive_path: str, output_directory: str = None) -> dict:
        """Decrypt an encrypted directory archive"""
        archive_path = Path(archive_path)
        if not archive_path.exists():
            raise FileNotFoundError(f"Archive not found: {archive_path}")
        
        if output_directory is None:
            output_directory = archive_path.with_suffix('')
        else:
            output_directory = Path(output_directory)
        
        output_directory.mkdir(exist_ok=True)
        temp_dir = Path("temp_decryption")
        
        try:
            # Extract archive
            with zipfile.ZipFile(archive_path, 'r') as zipf:
                zipf.extractall(temp_dir)
            
            results = []
            
            # Decrypt each file
            for encrypted_file in temp_dir.rglob('*.encrypted'):
                relative_path = encrypted_file.relative_to(temp_dir)
                relative_path = Path(str(relative_path).replace('.encrypted', ''))
                
                output_file_path = output_directory / relative_path
                output_file_path.parent.mkdir(parents=True, exist_ok=True)
                
                result = self.decrypt_file(str(encrypted_file), str(output_file_path))
                results.append(result)
            
            # Clean up temp directory
            shutil.rmtree(temp_dir)
            
            successful_decryptions = sum(1 for r in results if r['status'] == 'success')
            
            return {
                'status': 'success',
                'archive': str(archive_path),
                'output_directory': str(output_directory),
                'total_files': len(results),
                'successful': successful_decryptions,
                'failed': len(results) - successful_decryptions,
                'results': results
            }
            
        except Exception as e:
            # Clean up on error
            if temp_dir.exists():
                shutil.rmtree(temp_dir)
            
            return {
                'status': 'error',
                'error': str(e),
                'archive': str(archive_path)
            }
    
    def save_key_to_file(self, key_file_path: str, password: str = None):
        """Save encryption key to file (optionally password protected)"""
        if not self.key:
            raise ValueError("No key to save")
        
        key_data = {
            'key': base64.b64encode(self.key).decode(),
            'timestamp': datetime.now().isoformat()
        }
        
        if password:
            # Encrypt the key data with password
            temp_encryption = FileEncryption()
            salt = temp_encryption.set_key(password)
            
            key_json = json.dumps(key_data).encode()
            encrypted_key = temp_encryption.fernet.encrypt(key_json)
            
            protected_data = {
                'encrypted': True,
                'salt': base64.b64encode(salt).decode(),
                'data': base64.b64encode(encrypted_key).decode()
            }
            
            with open(key_file_path, 'w') as f:
                json.dump(protected_data, f, indent=2)
        else:
            key_data['encrypted'] = False
            with open(key_file_path, 'w') as f:
                json.dump(key_data, f, indent=2)
    
    def load_key_from_file(self, key_file_path: str, password: str = None):
        """Load encryption key from file"""
        with open(key_file_path, 'r') as f:
            key_data = json.load(f)
        
        if key_data.get('encrypted', False):
            if not password:
                raise ValueError("Password required for encrypted key file")
            
            # Decrypt the key data
            salt = base64.b64decode(key_data['salt'])
            encrypted_data = base64.b64decode(key_data['data'])
            
            temp_encryption = FileEncryption()
            temp_encryption.set_key(password, salt)
            
            decrypted_json = temp_encryption.fernet.decrypt(encrypted_data)
            actual_key_data = json.loads(decrypted_json.decode())
            
            self.key = base64.b64decode(actual_key_data['key'])
        else:
            self.key = base64.b64decode(key_data['key'])
        
        self.fernet = Fernet(self.key)
 
def create_sample_files():
    """Create sample files for testing"""
    sample_dir = Path("sample_files")
    sample_dir.mkdir(exist_ok=True)
    
    # Create text file
    with open(sample_dir / "sample.txt", 'w') as f:
        f.write("This is a sample text file for encryption testing.\n")
        f.write("It contains multiple lines of text.\n")
        f.write("The content will be encrypted and then decrypted.\n")
    
    # Create JSON file
    sample_data = {
        "name": "John Doe",
        "age": 30,
        "city": "New York",
        "hobbies": ["reading", "coding", "traveling"]
    }
    
    with open(sample_dir / "data.json", 'w') as f:
        json.dump(sample_data, f, indent=2)
    
    # Create binary file
    with open(sample_dir / "binary_data.bin", 'wb') as f:
        f.write(os.urandom(1024))  # 1KB of random data
    
    # Create subdirectory with files
    sub_dir = sample_dir / "subdirectory"
    sub_dir.mkdir(exist_ok=True)
    
    with open(sub_dir / "nested_file.txt", 'w') as f:
        f.write("This is a file in a subdirectory.")
    
    print(f"Sample files created in {sample_dir}")
 
def main():
    """Main function to run the file encryption tool"""
    encryption = FileEncryption()
    
    while True:
        print("\n=== File Encryption/Decryption Tool ===")
        print("1. Set password for encryption")
        print("2. Generate random key")
        print("3. Encrypt single file")
        print("4. Decrypt single file")
        print("5. Encrypt directory")
        print("6. Decrypt directory archive")
        print("7. Save key to file")
        print("8. Load key from file")
        print("9. Create sample files for testing")
        print("10. View current key status")
        print("0. Exit")
        
        try:
            choice = input("\nEnter your choice: ").strip()
            
            if choice == '1':
                password = getpass.getpass("Enter password for encryption: ")
                if len(password) < 8:
                    print("Warning: Password should be at least 8 characters long")
                
                salt = encryption.set_key(password)
                print("✓ Encryption key generated from password")
                print(f"Salt: {base64.b64encode(salt).decode()}")
            
            elif choice == '2':
                key = encryption.generate_random_key()
                print("✓ Random encryption key generated")
                print(f"Key: {base64.b64encode(key).decode()}")
                print("⚠ Make sure to save this key - you'll need it for decryption!")
            
            elif choice == '3':
                if not encryption.fernet:
                    print("❌ Please set an encryption key first (option 1 or 2)")
                    continue
                
                file_path = input("Enter file path to encrypt: ").strip()
                preserve = input("Preserve original file? (y/n): ").strip().lower() == 'y'
                
                result = encryption.encrypt_file(file_path, preserve_original=preserve)
                
                if result['status'] == 'success':
                    print("✓ File encrypted successfully!")
                    print(f"Original: {result['original_file']}")
                    print(f"Encrypted: {result['encrypted_file']}")
                    print(f"Original size: {result['metadata']['original_size']} bytes")
                    print(f"Encrypted size: {result['metadata']['encrypted_size']} bytes")
                else:
                    print(f"❌ Encryption failed: {result['error']}")
            
            elif choice == '4':
                if not encryption.fernet:
                    print("❌ Please set an encryption key first (option 1 or 2)")
                    continue
                
                file_path = input("Enter encrypted file path: ").strip()
                preserve = input("Preserve encrypted file? (y/n): ").strip().lower() == 'y'
                
                result = encryption.decrypt_file(file_path, preserve_encrypted=preserve)
                
                if result['status'] == 'success':
                    print("✓ File decrypted successfully!")
                    print(f"Encrypted: {result['encrypted_file']}")
                    print(f"Decrypted: {result['decrypted_file']}")
                    print(f"Original name: {result['metadata']['original_name']}")
                    print(f"Checksum verified: ✓")
                else:
                    print(f"❌ Decryption failed: {result['error']}")
            
            elif choice == '5':
                if not encryption.fernet:
                    print("❌ Please set an encryption key first (option 1 or 2)")
                    continue
                
                dir_path = input("Enter directory path to encrypt: ").strip()
                include_subdirs = input("Include subdirectories? (y/n): ").strip().lower() == 'y'
                
                print("Encrypting directory... This may take a while for large directories.")
                result = encryption.encrypt_directory(dir_path, include_subdirs=include_subdirs)
                
                if result['status'] == 'success':
                    print("✓ Directory encrypted successfully!")
                    print(f"Directory: {result['directory']}")
                    print(f"Archive: {result['archive']}")
                    print(f"Total files: {result['total_files']}")
                    print(f"Successful: {result['successful']}")
                    print(f"Failed: {result['failed']}")
                else:
                    print(f"❌ Directory encryption failed: {result['error']}")
            
            elif choice == '6':
                if not encryption.fernet:
                    print("❌ Please set an encryption key first (option 1 or 2)")
                    continue
                
                archive_path = input("Enter encrypted archive path: ").strip()
                output_dir = input("Enter output directory (or press Enter for default): ").strip()
                
                if not output_dir:
                    output_dir = None
                
                print("Decrypting archive... This may take a while for large archives.")
                result = encryption.decrypt_directory(archive_path, output_dir)
                
                if result['status'] == 'success':
                    print("✓ Archive decrypted successfully!")
                    print(f"Archive: {result['archive']}")
                    print(f"Output directory: {result['output_directory']}")
                    print(f"Total files: {result['total_files']}")
                    print(f"Successful: {result['successful']}")
                    print(f"Failed: {result['failed']}")
                else:
                    print(f"❌ Archive decryption failed: {result['error']}")
            
            elif choice == '7':
                if not encryption.key:
                    print("❌ No encryption key to save")
                    continue
                
                key_file = input("Enter key file path: ").strip()
                protect = input("Protect key with password? (y/n): ").strip().lower() == 'y'
                
                if protect:
                    password = getpass.getpass("Enter password to protect key: ")
                    encryption.save_key_to_file(key_file, password)
                else:
                    encryption.save_key_to_file(key_file)
                
                print("✓ Key saved successfully!")
            
            elif choice == '8':
                key_file = input("Enter key file path: ").strip()
                
                try:
                    # Check if file is encrypted
                    with open(key_file, 'r') as f:
                        key_data = json.load(f)
                    
                    if key_data.get('encrypted', False):
                        password = getpass.getpass("Enter password for key file: ")
                        encryption.load_key_from_file(key_file, password)
                    else:
                        encryption.load_key_from_file(key_file)
                    
                    print("✓ Key loaded successfully!")
                    
                except Exception as e:
                    print(f"❌ Failed to load key: {e}")
            
            elif choice == '9':
                create_sample_files()
            
            elif choice == '10':
                if encryption.key:
                    print("✓ Encryption key is set")
                    print(f"Key: {base64.b64encode(encryption.key).decode()[:32]}...")
                else:
                    print("❌ No encryption key set")
            
            elif choice == '0':
                print("Thank you for using the File Encryption Tool!")
                break
            
            else:
                print("Invalid choice. Please try again.")
        
        except KeyboardInterrupt:
            print("\n\nGoodbye!")
            break
        except Exception as e:
            print(f"An error occurred: {e}")
 
if __name__ == "__main__":
    main()
 
  1. Save the file.
  2. Run the following command to run the application.
command
C:\Users\username\Documents\fileEncryptionTool> python fileencryption.py
File Encryption Tool
===================
1. Encrypt a file
2. Decrypt a file
3. Batch encrypt directory
4. Generate secure password
5. Exit
Choose option: 1
Enter file path: document.pdf
Enter password: [hidden input]
✓ File encrypted successfully: document.pdf.enc
File hash: a1b2c3d4e5f6...
command
C:\Users\username\Documents\fileEncryptionTool> python fileencryption.py
File Encryption Tool
===================
1. Encrypt a file
2. Decrypt a file
3. Batch encrypt directory
4. Generate secure password
5. Exit
Choose option: 1
Enter file path: document.pdf
Enter password: [hidden input]
✓ File encrypted successfully: document.pdf.enc
File hash: a1b2c3d4e5f6...

Explanation

  1. The from cryptography.fernet import Fernetfrom cryptography.fernet import Fernet imports the Fernet symmetric encryption library.
  2. The PBKDF2HMACPBKDF2HMAC class provides secure password-based key derivation functionality.
  3. The FileEncryptionFileEncryption class manages all encryption and decryption operations.
  4. The generate_key_from_password()generate_key_from_password() method derives encryption keys from user passwords.
  5. The encrypt_file()encrypt_file() method encrypts individual files with secure key handling.
  6. The decrypt_file()decrypt_file() method reverses the encryption process to restore original files.
  7. Salt generation ensures unique keys even with identical passwords.
  8. File integrity verification uses SHA-256 hashing to detect corruption.
  9. Batch processing enables encryption of multiple files or entire directories.
  10. Secure password input prevents password exposure in command history.
  11. Progress tracking provides feedback for large file operations.
  12. Error handling manages file access issues and encryption failures.

Next Steps

Congratulations! You have successfully created a File Encryption Tool in Python. Experiment with the code and see if you can modify the application. Here are a few suggestions:

  • Add GUI interface with drag-and-drop functionality
  • Implement digital signatures for file authentication
  • Create secure file shredding for original files
  • Add compression before encryption for space efficiency
  • Implement key escrow and recovery mechanisms
  • Create encrypted archive functionality
  • Add steganography features for hidden encryption
  • Implement cloud storage integration with encryption
  • Create secure file sharing with key exchange

Conclusion

In this project, you learned how to create a File Encryption Tool in Python using advanced cryptography. You also learned about secure key derivation, file integrity verification, password security, and implementing enterprise-grade encryption solutions. You can find the source code on GitHub

How It Works

1. Cryptographic Foundation

fileencryption.py
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives.hashes import SHA256
from cryptography.hazmat.primitives.padding import PKCS7
from cryptography.hazmat.backends import default_backend
import os
import secrets
 
class FileEncryption:
    def __init__(self):
        self.algorithm = algorithms.AES
        self.key_size = 32  # 256 bits
        self.iv_size = 16   # 128 bits
        self.salt_size = 32 # 256 bits
        self.iterations = 100000  # PBKDF2 iterations
        self.backend = default_backend()
fileencryption.py
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives.hashes import SHA256
from cryptography.hazmat.primitives.padding import PKCS7
from cryptography.hazmat.backends import default_backend
import os
import secrets
 
class FileEncryption:
    def __init__(self):
        self.algorithm = algorithms.AES
        self.key_size = 32  # 256 bits
        self.iv_size = 16   # 128 bits
        self.salt_size = 32 # 256 bits
        self.iterations = 100000  # PBKDF2 iterations
        self.backend = default_backend()

The encryption system uses:

  • AES-256-CBC: Advanced Encryption Standard with 256-bit key
  • PBKDF2: Password-Based Key Derivation Function 2
  • Random Salt: Unique salt for each encryption
  • PKCS7 Padding: Standard padding for block ciphers
  • Secure Random: Cryptographically secure random number generation

2. Key Derivation Process

fileencryption.py
def derive_key_from_password(self, password: str, salt: bytes) -> bytes:
    """Derive encryption key from password using PBKDF2"""
    password_bytes = password.encode('utf-8')
    
    kdf = PBKDF2HMAC(
        algorithm=SHA256(),
        length=self.key_size,
        salt=salt,
        iterations=self.iterations,
        backend=self.backend
    )
    
    return kdf.derive(password_bytes)
 
def generate_salt(self) -> bytes:
    """Generate cryptographically secure random salt"""
    return secrets.token_bytes(self.salt_size)
 
def generate_iv(self) -> bytes:
    """Generate cryptographically secure random IV"""
    return secrets.token_bytes(self.iv_size)
fileencryption.py
def derive_key_from_password(self, password: str, salt: bytes) -> bytes:
    """Derive encryption key from password using PBKDF2"""
    password_bytes = password.encode('utf-8')
    
    kdf = PBKDF2HMAC(
        algorithm=SHA256(),
        length=self.key_size,
        salt=salt,
        iterations=self.iterations,
        backend=self.backend
    )
    
    return kdf.derive(password_bytes)
 
def generate_salt(self) -> bytes:
    """Generate cryptographically secure random salt"""
    return secrets.token_bytes(self.salt_size)
 
def generate_iv(self) -> bytes:
    """Generate cryptographically secure random IV"""
    return secrets.token_bytes(self.iv_size)

3. Encryption Implementation

fileencryption.py
def encrypt_file(self, input_file_path: str, output_file_path: str, 
                password: str, preserve_metadata: bool = True) -> dict:
    """Encrypt a file with AES-256-CBC"""
    try:
        # Generate cryptographic parameters
        salt = self.generate_salt()
        iv = self.generate_iv()
        key = self.derive_key_from_password(password, salt)
        
        # Create cipher
        cipher = Cipher(
            self.algorithm(key),
            modes.CBC(iv),
            backend=self.backend
        )
        encryptor = cipher.encryptor()
        
        # Initialize padding
        padder = PKCS7(128).padder()
        
        # File size for progress tracking
        file_size = os.path.getsize(input_file_path)
        processed_size = 0
        
        # Create encrypted file header
        header = self._create_file_header(salt, iv, preserve_metadata, input_file_path)
        
        with open(input_file_path, 'rb') as infile, \
             open(output_file_path, 'wb') as outfile:
            
            # Write header
            outfile.write(header)
            
            # Encrypt file content in chunks
            chunk_size = 64 * 1024  # 64KB chunks
            while True:
                chunk = infile.read(chunk_size)
                if not chunk:
                    break
                
                processed_size += len(chunk)
                
                # Pad the last chunk if necessary
                if len(chunk) < chunk_size:
                    padded_chunk = padder.update(chunk)
                    padded_chunk += padder.finalize()
                    encrypted_chunk = encryptor.update(padded_chunk)
                    encrypted_chunk += encryptor.finalize()
                else:
                    padded_chunk = padder.update(chunk)
                    encrypted_chunk = encryptor.update(padded_chunk)
                
                outfile.write(encrypted_chunk)
                
                # Progress callback
                if hasattr(self, 'progress_callback'):
                    self.progress_callback(processed_size, file_size)
        
        # Calculate file hash for integrity verification
        file_hash = self._calculate_file_hash(output_file_path)
        
        return {
            'success': True,
            'input_file': input_file_path,
            'output_file': output_file_path,
            'file_size': file_size,
            'encrypted_size': os.path.getsize(output_file_path),
            'hash': file_hash
        }
        
    except Exception as e:
        return {
            'success': False,
            'error': str(e),
            'input_file': input_file_path
        }
fileencryption.py
def encrypt_file(self, input_file_path: str, output_file_path: str, 
                password: str, preserve_metadata: bool = True) -> dict:
    """Encrypt a file with AES-256-CBC"""
    try:
        # Generate cryptographic parameters
        salt = self.generate_salt()
        iv = self.generate_iv()
        key = self.derive_key_from_password(password, salt)
        
        # Create cipher
        cipher = Cipher(
            self.algorithm(key),
            modes.CBC(iv),
            backend=self.backend
        )
        encryptor = cipher.encryptor()
        
        # Initialize padding
        padder = PKCS7(128).padder()
        
        # File size for progress tracking
        file_size = os.path.getsize(input_file_path)
        processed_size = 0
        
        # Create encrypted file header
        header = self._create_file_header(salt, iv, preserve_metadata, input_file_path)
        
        with open(input_file_path, 'rb') as infile, \
             open(output_file_path, 'wb') as outfile:
            
            # Write header
            outfile.write(header)
            
            # Encrypt file content in chunks
            chunk_size = 64 * 1024  # 64KB chunks
            while True:
                chunk = infile.read(chunk_size)
                if not chunk:
                    break
                
                processed_size += len(chunk)
                
                # Pad the last chunk if necessary
                if len(chunk) < chunk_size:
                    padded_chunk = padder.update(chunk)
                    padded_chunk += padder.finalize()
                    encrypted_chunk = encryptor.update(padded_chunk)
                    encrypted_chunk += encryptor.finalize()
                else:
                    padded_chunk = padder.update(chunk)
                    encrypted_chunk = encryptor.update(padded_chunk)
                
                outfile.write(encrypted_chunk)
                
                # Progress callback
                if hasattr(self, 'progress_callback'):
                    self.progress_callback(processed_size, file_size)
        
        # Calculate file hash for integrity verification
        file_hash = self._calculate_file_hash(output_file_path)
        
        return {
            'success': True,
            'input_file': input_file_path,
            'output_file': output_file_path,
            'file_size': file_size,
            'encrypted_size': os.path.getsize(output_file_path),
            'hash': file_hash
        }
        
    except Exception as e:
        return {
            'success': False,
            'error': str(e),
            'input_file': input_file_path
        }

4. Decryption Implementation

fileencryption.py
def decrypt_file(self, input_file_path: str, output_file_path: str, 
                password: str) -> dict:
    """Decrypt a file encrypted with AES-256-CBC"""
    try:
        with open(input_file_path, 'rb') as infile:
            # Read and parse header
            header_data = self._read_file_header(infile)
            salt = header_data['salt']
            iv = header_data['iv']
            
            # Derive key from password
            key = self.derive_key_from_password(password, salt)
            
            # Create cipher
            cipher = Cipher(
                self.algorithm(key),
                modes.CBC(iv),
                backend=self.backend
            )
            decryptor = cipher.decryptor()
            
            # Initialize unpadding
            unpadder = PKCS7(128).unpadder()
            
            file_size = os.path.getsize(input_file_path)
            processed_size = len(header_data['raw_header'])
            
            with open(output_file_path, 'wb') as outfile:
                # Decrypt file content in chunks
                chunk_size = 64 * 1024
                decrypted_chunks = []
                
                while True:
                    chunk = infile.read(chunk_size)
                    if not chunk:
                        break
                    
                    processed_size += len(chunk)
                    decrypted_chunk = decryptor.update(chunk)
                    decrypted_chunks.append(decrypted_chunk)
                    
                    # Progress callback
                    if hasattr(self, 'progress_callback'):
                        self.progress_callback(processed_size, file_size)
                
                # Finalize decryption
                decrypted_chunks.append(decryptor.finalize())
                
                # Combine all decrypted chunks
                all_decrypted = b''.join(decrypted_chunks)
                
                # Remove padding
                unpadded_data = unpadder.update(all_decrypted)
                unpadded_data += unpadder.finalize()
                
                outfile.write(unpadded_data)
        
        # Restore metadata if preserved
        if header_data.get('metadata'):
            self._restore_file_metadata(output_file_path, header_data['metadata'])
        
        return {
            'success': True,
            'input_file': input_file_path,
            'output_file': output_file_path,
            'original_size': os.path.getsize(output_file_path)
        }
        
    except Exception as e:
        return {
            'success': False,
            'error': str(e),
            'input_file': input_file_path
        }
fileencryption.py
def decrypt_file(self, input_file_path: str, output_file_path: str, 
                password: str) -> dict:
    """Decrypt a file encrypted with AES-256-CBC"""
    try:
        with open(input_file_path, 'rb') as infile:
            # Read and parse header
            header_data = self._read_file_header(infile)
            salt = header_data['salt']
            iv = header_data['iv']
            
            # Derive key from password
            key = self.derive_key_from_password(password, salt)
            
            # Create cipher
            cipher = Cipher(
                self.algorithm(key),
                modes.CBC(iv),
                backend=self.backend
            )
            decryptor = cipher.decryptor()
            
            # Initialize unpadding
            unpadder = PKCS7(128).unpadder()
            
            file_size = os.path.getsize(input_file_path)
            processed_size = len(header_data['raw_header'])
            
            with open(output_file_path, 'wb') as outfile:
                # Decrypt file content in chunks
                chunk_size = 64 * 1024
                decrypted_chunks = []
                
                while True:
                    chunk = infile.read(chunk_size)
                    if not chunk:
                        break
                    
                    processed_size += len(chunk)
                    decrypted_chunk = decryptor.update(chunk)
                    decrypted_chunks.append(decrypted_chunk)
                    
                    # Progress callback
                    if hasattr(self, 'progress_callback'):
                        self.progress_callback(processed_size, file_size)
                
                # Finalize decryption
                decrypted_chunks.append(decryptor.finalize())
                
                # Combine all decrypted chunks
                all_decrypted = b''.join(decrypted_chunks)
                
                # Remove padding
                unpadded_data = unpadder.update(all_decrypted)
                unpadded_data += unpadder.finalize()
                
                outfile.write(unpadded_data)
        
        # Restore metadata if preserved
        if header_data.get('metadata'):
            self._restore_file_metadata(output_file_path, header_data['metadata'])
        
        return {
            'success': True,
            'input_file': input_file_path,
            'output_file': output_file_path,
            'original_size': os.path.getsize(output_file_path)
        }
        
    except Exception as e:
        return {
            'success': False,
            'error': str(e),
            'input_file': input_file_path
        }

5. File Header Management

fileencryption.py
def _create_file_header(self, salt: bytes, iv: bytes, preserve_metadata: bool, 
                       original_file: str) -> bytes:
    """Create encrypted file header with metadata"""
    import struct
    import json
    
    header = b'PYENC'  # File signature
    header += struct.pack('<I', 1)  # Version number
    header += salt  # 32 bytes
    header += iv    # 16 bytes
    
    metadata = {}
    if preserve_metadata:
        stat = os.stat(original_file)
        metadata = {
            'original_name': os.path.basename(original_file),
            'size': stat.st_size,
            'mtime': stat.st_mtime,
            'mode': stat.st_mode
        }
    
    metadata_json = json.dumps(metadata).encode('utf-8')
    header += struct.pack('<I', len(metadata_json))
    header += metadata_json
    
    # Header checksum
    import hashlib
    checksum = hashlib.sha256(header).digest()[:8]
    header += checksum
    
    return header
 
def _read_file_header(self, file_handle) -> dict:
    """Read and validate encrypted file header"""
    import struct
    import json
    
    # Read signature
    signature = file_handle.read(5)
    if signature != b'PYENC':
        raise ValueError("Invalid encrypted file format")
    
    # Read version
    version = struct.unpack('<I', file_handle.read(4))[0]
    if version != 1:
        raise ValueError(f"Unsupported file version: {version}")
    
    # Read salt and IV
    salt = file_handle.read(32)
    iv = file_handle.read(16)
    
    # Read metadata
    metadata_length = struct.unpack('<I', file_handle.read(4))[0]
    metadata_json = file_handle.read(metadata_length)
    metadata = json.loads(metadata_json.decode('utf-8'))
    
    # Read and verify checksum
    stored_checksum = file_handle.read(8)
    
    # Calculate header for verification
    header_data = signature + struct.pack('<I', version) + salt + iv + \
                 struct.pack('<I', metadata_length) + metadata_json
    
    import hashlib
    calculated_checksum = hashlib.sha256(header_data).digest()[:8]
    
    if stored_checksum != calculated_checksum:
        raise ValueError("File header corruption detected")
    
    return {
        'salt': salt,
        'iv': iv,
        'metadata': metadata,
        'raw_header': header_data + stored_checksum
    }
fileencryption.py
def _create_file_header(self, salt: bytes, iv: bytes, preserve_metadata: bool, 
                       original_file: str) -> bytes:
    """Create encrypted file header with metadata"""
    import struct
    import json
    
    header = b'PYENC'  # File signature
    header += struct.pack('<I', 1)  # Version number
    header += salt  # 32 bytes
    header += iv    # 16 bytes
    
    metadata = {}
    if preserve_metadata:
        stat = os.stat(original_file)
        metadata = {
            'original_name': os.path.basename(original_file),
            'size': stat.st_size,
            'mtime': stat.st_mtime,
            'mode': stat.st_mode
        }
    
    metadata_json = json.dumps(metadata).encode('utf-8')
    header += struct.pack('<I', len(metadata_json))
    header += metadata_json
    
    # Header checksum
    import hashlib
    checksum = hashlib.sha256(header).digest()[:8]
    header += checksum
    
    return header
 
def _read_file_header(self, file_handle) -> dict:
    """Read and validate encrypted file header"""
    import struct
    import json
    
    # Read signature
    signature = file_handle.read(5)
    if signature != b'PYENC':
        raise ValueError("Invalid encrypted file format")
    
    # Read version
    version = struct.unpack('<I', file_handle.read(4))[0]
    if version != 1:
        raise ValueError(f"Unsupported file version: {version}")
    
    # Read salt and IV
    salt = file_handle.read(32)
    iv = file_handle.read(16)
    
    # Read metadata
    metadata_length = struct.unpack('<I', file_handle.read(4))[0]
    metadata_json = file_handle.read(metadata_length)
    metadata = json.loads(metadata_json.decode('utf-8'))
    
    # Read and verify checksum
    stored_checksum = file_handle.read(8)
    
    # Calculate header for verification
    header_data = signature + struct.pack('<I', version) + salt + iv + \
                 struct.pack('<I', metadata_length) + metadata_json
    
    import hashlib
    calculated_checksum = hashlib.sha256(header_data).digest()[:8]
    
    if stored_checksum != calculated_checksum:
        raise ValueError("File header corruption detected")
    
    return {
        'salt': salt,
        'iv': iv,
        'metadata': metadata,
        'raw_header': header_data + stored_checksum
    }

Batch Processing

1. Multiple File Encryption

fileencryption.py
def encrypt_batch(self, file_list: list, output_directory: str, 
                 password: str, preserve_metadata: bool = True) -> dict:
    """Encrypt multiple files in batch"""
    results = {
        'successful': [],
        'failed': [],
        'total_files': len(file_list),
        'total_size': 0,
        'start_time': time.time()
    }
    
    for i, file_path in enumerate(file_list):
        try:
            if not os.path.isfile(file_path):
                results['failed'].append({
                    'file': file_path,
                    'error': 'File not found'
                })
                continue
            
            # Generate output filename
            base_name = os.path.basename(file_path)
            output_file = os.path.join(output_directory, f"{base_name}.enc")
            
            # Progress callback for batch processing
            def batch_progress(processed, total):
                overall_progress = ((i * 100) + (processed / total * 100)) / len(file_list)
                if hasattr(self, 'batch_progress_callback'):
                    self.batch_progress_callback(overall_progress, i + 1, len(file_list))
            
            self.progress_callback = batch_progress
            
            # Encrypt file
            result = self.encrypt_file(file_path, output_file, password, preserve_metadata)
            
            if result['success']:
                results['successful'].append(result)
                results['total_size'] += result['file_size']
            else:
                results['failed'].append(result)
                
        except Exception as e:
            results['failed'].append({
                'file': file_path,
                'error': str(e)
            })
    
    results['end_time'] = time.time()
    results['duration'] = results['end_time'] - results['start_time']
    
    return results
 
def decrypt_batch(self, file_list: list, output_directory: str, 
                 password: str) -> dict:
    """Decrypt multiple files in batch"""
    results = {
        'successful': [],
        'failed': [],
        'total_files': len(file_list),
        'start_time': time.time()
    }
    
    for i, file_path in enumerate(file_list):
        try:
            if not os.path.isfile(file_path):
                results['failed'].append({
                    'file': file_path,
                    'error': 'File not found'
                })
                continue
            
            # Generate output filename
            base_name = os.path.basename(file_path)
            if base_name.endswith('.enc'):
                output_name = base_name[:-4]  # Remove .enc extension
            else:
                output_name = f"{base_name}.decrypted"
            
            output_file = os.path.join(output_directory, output_name)
            
            # Progress callback for batch processing
            def batch_progress(processed, total):
                overall_progress = ((i * 100) + (processed / total * 100)) / len(file_list)
                if hasattr(self, 'batch_progress_callback'):
                    self.batch_progress_callback(overall_progress, i + 1, len(file_list))
            
            self.progress_callback = batch_progress
            
            # Decrypt file
            result = self.decrypt_file(file_path, output_file, password)
            
            if result['success']:
                results['successful'].append(result)
            else:
                results['failed'].append(result)
                
        except Exception as e:
            results['failed'].append({
                'file': file_path,
                'error': str(e)
            })
    
    results['end_time'] = time.time()
    results['duration'] = results['end_time'] - results['start_time']
    
    return results
fileencryption.py
def encrypt_batch(self, file_list: list, output_directory: str, 
                 password: str, preserve_metadata: bool = True) -> dict:
    """Encrypt multiple files in batch"""
    results = {
        'successful': [],
        'failed': [],
        'total_files': len(file_list),
        'total_size': 0,
        'start_time': time.time()
    }
    
    for i, file_path in enumerate(file_list):
        try:
            if not os.path.isfile(file_path):
                results['failed'].append({
                    'file': file_path,
                    'error': 'File not found'
                })
                continue
            
            # Generate output filename
            base_name = os.path.basename(file_path)
            output_file = os.path.join(output_directory, f"{base_name}.enc")
            
            # Progress callback for batch processing
            def batch_progress(processed, total):
                overall_progress = ((i * 100) + (processed / total * 100)) / len(file_list)
                if hasattr(self, 'batch_progress_callback'):
                    self.batch_progress_callback(overall_progress, i + 1, len(file_list))
            
            self.progress_callback = batch_progress
            
            # Encrypt file
            result = self.encrypt_file(file_path, output_file, password, preserve_metadata)
            
            if result['success']:
                results['successful'].append(result)
                results['total_size'] += result['file_size']
            else:
                results['failed'].append(result)
                
        except Exception as e:
            results['failed'].append({
                'file': file_path,
                'error': str(e)
            })
    
    results['end_time'] = time.time()
    results['duration'] = results['end_time'] - results['start_time']
    
    return results
 
def decrypt_batch(self, file_list: list, output_directory: str, 
                 password: str) -> dict:
    """Decrypt multiple files in batch"""
    results = {
        'successful': [],
        'failed': [],
        'total_files': len(file_list),
        'start_time': time.time()
    }
    
    for i, file_path in enumerate(file_list):
        try:
            if not os.path.isfile(file_path):
                results['failed'].append({
                    'file': file_path,
                    'error': 'File not found'
                })
                continue
            
            # Generate output filename
            base_name = os.path.basename(file_path)
            if base_name.endswith('.enc'):
                output_name = base_name[:-4]  # Remove .enc extension
            else:
                output_name = f"{base_name}.decrypted"
            
            output_file = os.path.join(output_directory, output_name)
            
            # Progress callback for batch processing
            def batch_progress(processed, total):
                overall_progress = ((i * 100) + (processed / total * 100)) / len(file_list)
                if hasattr(self, 'batch_progress_callback'):
                    self.batch_progress_callback(overall_progress, i + 1, len(file_list))
            
            self.progress_callback = batch_progress
            
            # Decrypt file
            result = self.decrypt_file(file_path, output_file, password)
            
            if result['success']:
                results['successful'].append(result)
            else:
                results['failed'].append(result)
                
        except Exception as e:
            results['failed'].append({
                'file': file_path,
                'error': str(e)
            })
    
    results['end_time'] = time.time()
    results['duration'] = results['end_time'] - results['start_time']
    
    return results

2. Directory Encryption

fileencryption.py
def encrypt_directory(self, directory_path: str, output_directory: str, 
                     password: str, recursive: bool = True) -> dict:
    """Encrypt all files in a directory"""
    import glob
    
    if recursive:
        pattern = os.path.join(directory_path, '**', '*')
        file_list = [f for f in glob.glob(pattern, recursive=True) if os.path.isfile(f)]
    else:
        pattern = os.path.join(directory_path, '*')
        file_list = [f for f in glob.glob(pattern) if os.path.isfile(f)]
    
    # Create output directory structure
    for file_path in file_list:
        relative_path = os.path.relpath(file_path, directory_path)
        output_file_dir = os.path.join(output_directory, os.path.dirname(relative_path))
        os.makedirs(output_file_dir, exist_ok=True)
    
    return self.encrypt_batch(file_list, output_directory, password)
fileencryption.py
def encrypt_directory(self, directory_path: str, output_directory: str, 
                     password: str, recursive: bool = True) -> dict:
    """Encrypt all files in a directory"""
    import glob
    
    if recursive:
        pattern = os.path.join(directory_path, '**', '*')
        file_list = [f for f in glob.glob(pattern, recursive=True) if os.path.isfile(f)]
    else:
        pattern = os.path.join(directory_path, '*')
        file_list = [f for f in glob.glob(pattern) if os.path.isfile(f)]
    
    # Create output directory structure
    for file_path in file_list:
        relative_path = os.path.relpath(file_path, directory_path)
        output_file_dir = os.path.join(output_directory, os.path.dirname(relative_path))
        os.makedirs(output_file_dir, exist_ok=True)
    
    return self.encrypt_batch(file_list, output_directory, password)

Security Features

1. Password Strength Validation

fileencryption.py
def validate_password_strength(self, password: str) -> dict:
    """Validate password strength for encryption"""
    issues = []
    score = 0
    
    # Length check
    if len(password) < 8:
        issues.append("Password must be at least 8 characters long")
    elif len(password) >= 12:
        score += 2
    else:
        score += 1
    
    # Character variety checks
    if not any(c.isupper() for c in password):
        issues.append("Password should contain uppercase letters")
    else:
        score += 1
    
    if not any(c.islower() for c in password):
        issues.append("Password should contain lowercase letters")
    else:
        score += 1
    
    if not any(c.isdigit() for c in password):
        issues.append("Password should contain numbers")
    else:
        score += 1
    
    if not any(c in '!@#$%^&*()_+-=[]{}|;:,.<>?' for c in password):
        issues.append("Password should contain special characters")
    else:
        score += 1
    
    # Common password check
    common_passwords = ['password', '123456', 'qwerty', 'admin', 'letmein']
    if password.lower() in common_passwords:
        issues.append("Password is too common")
        score = 0
    
    strength_levels = {
        0: 'Very Weak',
        1: 'Weak', 
        2: 'Fair',
        3: 'Good',
        4: 'Strong',
        5: 'Very Strong',
        6: 'Excellent'
    }
    
    return {
        'score': score,
        'strength': strength_levels.get(score, 'Unknown'),
        'issues': issues,
        'is_acceptable': score >= 3 and len(issues) == 0
    }
fileencryption.py
def validate_password_strength(self, password: str) -> dict:
    """Validate password strength for encryption"""
    issues = []
    score = 0
    
    # Length check
    if len(password) < 8:
        issues.append("Password must be at least 8 characters long")
    elif len(password) >= 12:
        score += 2
    else:
        score += 1
    
    # Character variety checks
    if not any(c.isupper() for c in password):
        issues.append("Password should contain uppercase letters")
    else:
        score += 1
    
    if not any(c.islower() for c in password):
        issues.append("Password should contain lowercase letters")
    else:
        score += 1
    
    if not any(c.isdigit() for c in password):
        issues.append("Password should contain numbers")
    else:
        score += 1
    
    if not any(c in '!@#$%^&*()_+-=[]{}|;:,.<>?' for c in password):
        issues.append("Password should contain special characters")
    else:
        score += 1
    
    # Common password check
    common_passwords = ['password', '123456', 'qwerty', 'admin', 'letmein']
    if password.lower() in common_passwords:
        issues.append("Password is too common")
        score = 0
    
    strength_levels = {
        0: 'Very Weak',
        1: 'Weak', 
        2: 'Fair',
        3: 'Good',
        4: 'Strong',
        5: 'Very Strong',
        6: 'Excellent'
    }
    
    return {
        'score': score,
        'strength': strength_levels.get(score, 'Unknown'),
        'issues': issues,
        'is_acceptable': score >= 3 and len(issues) == 0
    }

2. Secure Memory Handling

fileencryption.py
class SecureString:
    """Secure string class for handling passwords in memory"""
    
    def __init__(self, data: str):
        self._data = bytearray(data.encode('utf-8'))
    
    def get(self) -> str:
        """Get the string value"""
        return bytes(self._data).decode('utf-8')
    
    def clear(self):
        """Securely clear the string from memory"""
        for i in range(len(self._data)):
            self._data[i] = 0
    
    def __del__(self):
        """Ensure cleanup on deletion"""
        if hasattr(self, '_data'):
            self.clear()
 
def get_secure_password() -> SecureString:
    """Securely prompt for password"""
    import getpass
    
    while True:
        password = getpass.getpass("Enter encryption password: ")
        confirm = getpass.getpass("Confirm password: ")
        
        if password != confirm:
            print("Passwords do not match. Please try again.")
            continue
        
        # Validate password strength
        validation = FileEncryption().validate_password_strength(password)
        
        if not validation['is_acceptable']:
            print(f"Password strength: {validation['strength']}")
            for issue in validation['issues']:
                print(f"- {issue}")
            
            continue_anyway = input("Continue with weak password? (y/N): ").lower()
            if continue_anyway != 'y':
                continue
        
        return SecureString(password)
fileencryption.py
class SecureString:
    """Secure string class for handling passwords in memory"""
    
    def __init__(self, data: str):
        self._data = bytearray(data.encode('utf-8'))
    
    def get(self) -> str:
        """Get the string value"""
        return bytes(self._data).decode('utf-8')
    
    def clear(self):
        """Securely clear the string from memory"""
        for i in range(len(self._data)):
            self._data[i] = 0
    
    def __del__(self):
        """Ensure cleanup on deletion"""
        if hasattr(self, '_data'):
            self.clear()
 
def get_secure_password() -> SecureString:
    """Securely prompt for password"""
    import getpass
    
    while True:
        password = getpass.getpass("Enter encryption password: ")
        confirm = getpass.getpass("Confirm password: ")
        
        if password != confirm:
            print("Passwords do not match. Please try again.")
            continue
        
        # Validate password strength
        validation = FileEncryption().validate_password_strength(password)
        
        if not validation['is_acceptable']:
            print(f"Password strength: {validation['strength']}")
            for issue in validation['issues']:
                print(f"- {issue}")
            
            continue_anyway = input("Continue with weak password? (y/N): ").lower()
            if continue_anyway != 'y':
                continue
        
        return SecureString(password)

3. File Integrity Verification

fileencryption.py
def _calculate_file_hash(self, file_path: str) -> str:
    """Calculate SHA-256 hash of file for integrity verification"""
    import hashlib
    
    hasher = hashlib.sha256()
    with open(file_path, 'rb') as f:
        for chunk in iter(lambda: f.read(4096), b""):
            hasher.update(chunk)
    
    return hasher.hexdigest()
 
def verify_file_integrity(self, file_path: str, expected_hash: str) -> bool:
    """Verify file integrity using SHA-256 hash"""
    actual_hash = self._calculate_file_hash(file_path)
    return actual_hash.lower() == expected_hash.lower()
 
def create_digital_signature(self, file_path: str, private_key_path: str) -> bytes:
    """Create digital signature for file integrity"""
    from cryptography.hazmat.primitives import serialization, hashes
    from cryptography.hazmat.primitives.asymmetric import rsa, padding
    
    # Load private key
    with open(private_key_path, 'rb') as key_file:
        private_key = serialization.load_pem_private_key(
            key_file.read(),
            password=None,
            backend=self.backend
        )
    
    # Calculate file hash
    file_hash = self._calculate_file_hash(file_path)
    
    # Create signature
    signature = private_key.sign(
        file_hash.encode('utf-8'),
        padding.PSS(
            mgf=padding.MGF1(hashes.SHA256()),
            salt_length=padding.PSS.MAX_LENGTH
        ),
        hashes.SHA256()
    )
    
    return signature
fileencryption.py
def _calculate_file_hash(self, file_path: str) -> str:
    """Calculate SHA-256 hash of file for integrity verification"""
    import hashlib
    
    hasher = hashlib.sha256()
    with open(file_path, 'rb') as f:
        for chunk in iter(lambda: f.read(4096), b""):
            hasher.update(chunk)
    
    return hasher.hexdigest()
 
def verify_file_integrity(self, file_path: str, expected_hash: str) -> bool:
    """Verify file integrity using SHA-256 hash"""
    actual_hash = self._calculate_file_hash(file_path)
    return actual_hash.lower() == expected_hash.lower()
 
def create_digital_signature(self, file_path: str, private_key_path: str) -> bytes:
    """Create digital signature for file integrity"""
    from cryptography.hazmat.primitives import serialization, hashes
    from cryptography.hazmat.primitives.asymmetric import rsa, padding
    
    # Load private key
    with open(private_key_path, 'rb') as key_file:
        private_key = serialization.load_pem_private_key(
            key_file.read(),
            password=None,
            backend=self.backend
        )
    
    # Calculate file hash
    file_hash = self._calculate_file_hash(file_path)
    
    # Create signature
    signature = private_key.sign(
        file_hash.encode('utf-8'),
        padding.PSS(
            mgf=padding.MGF1(hashes.SHA256()),
            salt_length=padding.PSS.MAX_LENGTH
        ),
        hashes.SHA256()
    )
    
    return signature

Command Line Interface

1. CLI Implementation

fileencryption.py
import argparse
import sys
from pathlib import Path
 
class FileEncryptionCLI:
    def __init__(self):
        self.encryptor = FileEncryption()
        self.setup_progress_callbacks()
    
    def setup_progress_callbacks(self):
        """Setup progress tracking for CLI"""
        try:
            from tqdm import tqdm
            self.use_tqdm = True
        except ImportError:
            self.use_tqdm = False
        
        self.current_progress_bar = None
    
    def progress_callback(self, processed: int, total: int):
        """Progress callback for single file operations"""
        if self.use_tqdm:
            if self.current_progress_bar is None:
                self.current_progress_bar = tqdm(total=total, unit='B', unit_scale=True)
            
            self.current_progress_bar.update(processed - self.current_progress_bar.n)
            
            if processed >= total:
                self.current_progress_bar.close()
                self.current_progress_bar = None
        else:
            percentage = (processed / total) * 100
            sys.stdout.write(f"\rProgress: {percentage:.1f}%")
            sys.stdout.flush()
            
            if processed >= total:
                print()  # New line when complete
    
    def main(self):
        """Main CLI entry point"""
        parser = argparse.ArgumentParser(description='File Encryption Tool')
        parser.add_argument('action', choices=['encrypt', 'decrypt', 'batch-encrypt', 'batch-decrypt'],
                          help='Action to perform')
        parser.add_argument('-i', '--input', required=True,
                          help='Input file or directory path')
        parser.add_argument('-o', '--output', required=True,
                          help='Output file or directory path')
        parser.add_argument('-p', '--password', 
                          help='Encryption password (will prompt if not provided)')
        parser.add_argument('--no-metadata', action='store_true',
                          help='Do not preserve file metadata')
        parser.add_argument('-r', '--recursive', action='store_true',
                          help='Process directories recursively')
        parser.add_argument('--verify', action='store_true',
                          help='Verify file integrity after operation')
        
        args = parser.parse_args()
        
        # Get password securely
        if args.password:
            password = args.password
        else:
            password = get_secure_password().get()
        
        # Set progress callback
        self.encryptor.progress_callback = self.progress_callback
        
        try:
            if args.action == 'encrypt':
                result = self.encrypt_single_file(args.input, args.output, password, 
                                                not args.no_metadata)
            elif args.action == 'decrypt':
                result = self.decrypt_single_file(args.input, args.output, password)
            elif args.action == 'batch-encrypt':
                result = self.batch_encrypt_files(args.input, args.output, password,
                                                not args.no_metadata, args.recursive)
            elif args.action == 'batch-decrypt':
                result = self.batch_decrypt_files(args.input, args.output, password,
                                                args.recursive)
            
            self.print_results(result)
            
        except KeyboardInterrupt:
            print("\nOperation cancelled by user")
            sys.exit(1)
        except Exception as e:
            print(f"Error: {e}")
            sys.exit(1)
    
    def encrypt_single_file(self, input_file: str, output_file: str, 
                          password: str, preserve_metadata: bool) -> dict:
        """Encrypt a single file"""
        print(f"Encrypting: {input_file}")
        result = self.encryptor.encrypt_file(input_file, output_file, password, preserve_metadata)
        
        if result['success']:
            print(f"✓ Successfully encrypted to: {output_file}")
            print(f"  Original size: {self.format_size(result['file_size'])}")
            print(f"  Encrypted size: {self.format_size(result['encrypted_size'])}")
            print(f"  File hash: {result['hash']}")
        else:
            print(f"✗ Encryption failed: {result['error']}")
        
        return result
    
    def decrypt_single_file(self, input_file: str, output_file: str, password: str) -> dict:
        """Decrypt a single file"""
        print(f"Decrypting: {input_file}")
        result = self.encryptor.decrypt_file(input_file, output_file, password)
        
        if result['success']:
            print(f"✓ Successfully decrypted to: {output_file}")
            print(f"  Decrypted size: {self.format_size(result['original_size'])}")
        else:
            print(f"✗ Decryption failed: {result['error']}")
        
        return result
    
    @staticmethod
    def format_size(size_bytes: int) -> str:
        """Format file size in human readable format"""
        for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
            if size_bytes < 1024.0:
                return f"{size_bytes:.1f} {unit}"
            size_bytes /= 1024.0
        return f"{size_bytes:.1f} PB"
    
    def print_results(self, result: dict):
        """Print operation results"""
        if isinstance(result, dict) and 'successful' in result:
            # Batch operation results
            print(f"\nBatch operation completed:")
            print(f"  Successful: {len(result['successful'])}")
            print(f"  Failed: {len(result['failed'])}")
            print(f"  Duration: {result['duration']:.2f} seconds")
            
            if result['failed']:
                print("\nFailed files:")
                for failed in result['failed']:
                    print(f"  ✗ {failed['file']}: {failed['error']}")
 
if __name__ == "__main__":
    cli = FileEncryptionCLI()
    cli.main()
fileencryption.py
import argparse
import sys
from pathlib import Path
 
class FileEncryptionCLI:
    def __init__(self):
        self.encryptor = FileEncryption()
        self.setup_progress_callbacks()
    
    def setup_progress_callbacks(self):
        """Setup progress tracking for CLI"""
        try:
            from tqdm import tqdm
            self.use_tqdm = True
        except ImportError:
            self.use_tqdm = False
        
        self.current_progress_bar = None
    
    def progress_callback(self, processed: int, total: int):
        """Progress callback for single file operations"""
        if self.use_tqdm:
            if self.current_progress_bar is None:
                self.current_progress_bar = tqdm(total=total, unit='B', unit_scale=True)
            
            self.current_progress_bar.update(processed - self.current_progress_bar.n)
            
            if processed >= total:
                self.current_progress_bar.close()
                self.current_progress_bar = None
        else:
            percentage = (processed / total) * 100
            sys.stdout.write(f"\rProgress: {percentage:.1f}%")
            sys.stdout.flush()
            
            if processed >= total:
                print()  # New line when complete
    
    def main(self):
        """Main CLI entry point"""
        parser = argparse.ArgumentParser(description='File Encryption Tool')
        parser.add_argument('action', choices=['encrypt', 'decrypt', 'batch-encrypt', 'batch-decrypt'],
                          help='Action to perform')
        parser.add_argument('-i', '--input', required=True,
                          help='Input file or directory path')
        parser.add_argument('-o', '--output', required=True,
                          help='Output file or directory path')
        parser.add_argument('-p', '--password', 
                          help='Encryption password (will prompt if not provided)')
        parser.add_argument('--no-metadata', action='store_true',
                          help='Do not preserve file metadata')
        parser.add_argument('-r', '--recursive', action='store_true',
                          help='Process directories recursively')
        parser.add_argument('--verify', action='store_true',
                          help='Verify file integrity after operation')
        
        args = parser.parse_args()
        
        # Get password securely
        if args.password:
            password = args.password
        else:
            password = get_secure_password().get()
        
        # Set progress callback
        self.encryptor.progress_callback = self.progress_callback
        
        try:
            if args.action == 'encrypt':
                result = self.encrypt_single_file(args.input, args.output, password, 
                                                not args.no_metadata)
            elif args.action == 'decrypt':
                result = self.decrypt_single_file(args.input, args.output, password)
            elif args.action == 'batch-encrypt':
                result = self.batch_encrypt_files(args.input, args.output, password,
                                                not args.no_metadata, args.recursive)
            elif args.action == 'batch-decrypt':
                result = self.batch_decrypt_files(args.input, args.output, password,
                                                args.recursive)
            
            self.print_results(result)
            
        except KeyboardInterrupt:
            print("\nOperation cancelled by user")
            sys.exit(1)
        except Exception as e:
            print(f"Error: {e}")
            sys.exit(1)
    
    def encrypt_single_file(self, input_file: str, output_file: str, 
                          password: str, preserve_metadata: bool) -> dict:
        """Encrypt a single file"""
        print(f"Encrypting: {input_file}")
        result = self.encryptor.encrypt_file(input_file, output_file, password, preserve_metadata)
        
        if result['success']:
            print(f"✓ Successfully encrypted to: {output_file}")
            print(f"  Original size: {self.format_size(result['file_size'])}")
            print(f"  Encrypted size: {self.format_size(result['encrypted_size'])}")
            print(f"  File hash: {result['hash']}")
        else:
            print(f"✗ Encryption failed: {result['error']}")
        
        return result
    
    def decrypt_single_file(self, input_file: str, output_file: str, password: str) -> dict:
        """Decrypt a single file"""
        print(f"Decrypting: {input_file}")
        result = self.encryptor.decrypt_file(input_file, output_file, password)
        
        if result['success']:
            print(f"✓ Successfully decrypted to: {output_file}")
            print(f"  Decrypted size: {self.format_size(result['original_size'])}")
        else:
            print(f"✗ Decryption failed: {result['error']}")
        
        return result
    
    @staticmethod
    def format_size(size_bytes: int) -> str:
        """Format file size in human readable format"""
        for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
            if size_bytes < 1024.0:
                return f"{size_bytes:.1f} {unit}"
            size_bytes /= 1024.0
        return f"{size_bytes:.1f} PB"
    
    def print_results(self, result: dict):
        """Print operation results"""
        if isinstance(result, dict) and 'successful' in result:
            # Batch operation results
            print(f"\nBatch operation completed:")
            print(f"  Successful: {len(result['successful'])}")
            print(f"  Failed: {len(result['failed'])}")
            print(f"  Duration: {result['duration']:.2f} seconds")
            
            if result['failed']:
                print("\nFailed files:")
                for failed in result['failed']:
                    print(f"  ✗ {failed['file']}: {failed['error']}")
 
if __name__ == "__main__":
    cli = FileEncryptionCLI()
    cli.main()

GUI Interface

1. Tkinter GUI Implementation

fileencryption.py
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import threading
 
class FileEncryptionGUI:
    def __init__(self):
        self.encryptor = FileEncryption()
        self.setup_gui()
        
    def setup_gui(self):
        """Setup the GUI interface"""
        self.root = tk.Tk()
        self.root.title("File Encryption Tool")
        self.root.geometry("600x500")
        self.root.resizable(True, True)
        
        # Create main frame
        main_frame = ttk.Frame(self.root, padding="10")
        main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        
        # Configure grid weights
        self.root.columnconfigure(0, weight=1)
        self.root.rowconfigure(0, weight=1)
        main_frame.columnconfigure(1, weight=1)
        
        # File selection
        ttk.Label(main_frame, text="Select Files:").grid(row=0, column=0, sticky=tk.W, pady=5)
        
        self.file_list = tk.Listbox(main_frame, height=8)
        self.file_list.grid(row=1, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=5)
        
        file_buttons_frame = ttk.Frame(main_frame)
        file_buttons_frame.grid(row=2, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=5)
        
        ttk.Button(file_buttons_frame, text="Add Files", 
                  command=self.add_files).pack(side=tk.LEFT, padx=5)
        ttk.Button(file_buttons_frame, text="Add Directory", 
                  command=self.add_directory).pack(side=tk.LEFT, padx=5)
        ttk.Button(file_buttons_frame, text="Clear List", 
                  command=self.clear_files).pack(side=tk.LEFT, padx=5)
        
        # Output directory
        ttk.Label(main_frame, text="Output Directory:").grid(row=3, column=0, sticky=tk.W, pady=5)
        
        self.output_var = tk.StringVar()
        ttk.Entry(main_frame, textvariable=self.output_var).grid(row=4, column=0, columnspan=2, 
                                                                sticky=(tk.W, tk.E), pady=5)
        ttk.Button(main_frame, text="Browse", 
                  command=self.browse_output).grid(row=4, column=2, padx=5)
        
        # Password
        ttk.Label(main_frame, text="Password:").grid(row=5, column=0, sticky=tk.W, pady=5)
        
        self.password_var = tk.StringVar()
        self.password_entry = ttk.Entry(main_frame, textvariable=self.password_var, show="*")
        self.password_entry.grid(row=6, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=5)
        
        self.show_password_var = tk.BooleanVar()
        ttk.Checkbutton(main_frame, text="Show Password", variable=self.show_password_var,
                       command=self.toggle_password).grid(row=6, column=2, padx=5)
        
        # Options
        options_frame = ttk.LabelFrame(main_frame, text="Options", padding="5")
        options_frame.grid(row=7, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=10)
        
        self.preserve_metadata_var = tk.BooleanVar(value=True)
        ttk.Checkbutton(options_frame, text="Preserve File Metadata", 
                       variable=self.preserve_metadata_var).pack(anchor=tk.W)
        
        self.recursive_var = tk.BooleanVar(value=False)
        ttk.Checkbutton(options_frame, text="Process Directories Recursively", 
                       variable=self.recursive_var).pack(anchor=tk.W)
        
        # Action buttons
        button_frame = ttk.Frame(main_frame)
        button_frame.grid(row=8, column=0, columnspan=3, pady=20)
        
        self.encrypt_button = ttk.Button(button_frame, text="Encrypt Files", 
                                       command=self.encrypt_files)
        self.encrypt_button.pack(side=tk.LEFT, padx=10)
        
        self.decrypt_button = ttk.Button(button_frame, text="Decrypt Files", 
                                       command=self.decrypt_files)
        self.decrypt_button.pack(side=tk.LEFT, padx=10)
        
        # Progress bar
        self.progress_var = tk.DoubleVar()
        self.progress_bar = ttk.Progressbar(main_frame, variable=self.progress_var, 
                                          maximum=100)
        self.progress_bar.grid(row=9, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=10)
        
        # Status label
        self.status_var = tk.StringVar(value="Ready")
        ttk.Label(main_frame, textvariable=self.status_var).grid(row=10, column=0, columnspan=3)
    
    def add_files(self):
        """Add files to the list"""
        files = filedialog.askopenfilenames(title="Select Files to Encrypt/Decrypt")
        for file in files:
            self.file_list.insert(tk.END, file)
    
    def add_directory(self):
        """Add directory to the list"""
        directory = filedialog.askdirectory(title="Select Directory")
        if directory:
            self.file_list.insert(tk.END, f"[DIR] {directory}")
    
    def clear_files(self):
        """Clear the file list"""
        self.file_list.delete(0, tk.END)
    
    def browse_output(self):
        """Browse for output directory"""
        directory = filedialog.askdirectory(title="Select Output Directory")
        if directory:
            self.output_var.set(directory)
    
    def toggle_password(self):
        """Toggle password visibility"""
        if self.show_password_var.get():
            self.password_entry.config(show="")
        else:
            self.password_entry.config(show="*")
    
    def encrypt_files(self):
        """Encrypt selected files"""
        self.process_files("encrypt")
    
    def decrypt_files(self):
        """Decrypt selected files"""
        self.process_files("decrypt")
    
    def process_files(self, action):
        """Process files in background thread"""
        files = list(self.file_list.get(0, tk.END))
        output_dir = self.output_var.get()
        password = self.password_var.get()
        
        if not files:
            messagebox.showerror("Error", "Please select files to process")
            return
        
        if not output_dir:
            messagebox.showerror("Error", "Please select output directory")
            return
        
        if not password:
            messagebox.showerror("Error", "Please enter a password")
            return
        
        # Disable buttons during processing
        self.encrypt_button.config(state="disabled")
        self.decrypt_button.config(state="disabled")
        
        # Start processing in background thread
        thread = threading.Thread(target=self._process_files_worker, 
                                 args=(action, files, output_dir, password))
        thread.daemon = True
        thread.start()
    
    def _process_files_worker(self, action, files, output_dir, password):
        """Background worker for file processing"""
        try:
            # Setup progress callbacks
            def progress_callback(processed, total):
                percentage = (processed / total) * 100
                self.root.after(0, lambda: self.progress_var.set(percentage))
            
            def batch_progress_callback(overall_progress, current_file, total_files):
                self.root.after(0, lambda: self.status_var.set(
                    f"Processing file {current_file}/{total_files} ({overall_progress:.1f}%)"))
                self.root.after(0, lambda: self.progress_var.set(overall_progress))
            
            self.encryptor.progress_callback = progress_callback
            self.encryptor.batch_progress_callback = batch_progress_callback
            
            # Process files
            if action == "encrypt":
                result = self.encryptor.encrypt_batch(files, output_dir, password, 
                                                    self.preserve_metadata_var.get())
            else:
                result = self.encryptor.decrypt_batch(files, output_dir, password)
            
            # Show results
            self.root.after(0, lambda: self._show_results(result))
            
        except Exception as e:
            self.root.after(0, lambda: messagebox.showerror("Error", str(e)))
        finally:
            # Re-enable buttons
            self.root.after(0, lambda: self.encrypt_button.config(state="normal"))
            self.root.after(0, lambda: self.decrypt_button.config(state="normal"))
            self.root.after(0, lambda: self.progress_var.set(0))
            self.root.after(0, lambda: self.status_var.set("Ready"))
    
    def _show_results(self, result):
        """Show processing results"""
        message = f"Operation completed!\n\n"
        message += f"Successful: {len(result['successful'])}\n"
        message += f"Failed: {len(result['failed'])}\n"
        message += f"Duration: {result['duration']:.2f} seconds"
        
        if result['failed']:
            message += f"\n\nFailed files:\n"
            for failed in result['failed'][:5]:  # Show first 5 failures
                message += f"- {failed['file']}: {failed['error']}\n"
            
            if len(result['failed']) > 5:
                message += f"... and {len(result['failed']) - 5} more"
        
        messagebox.showinfo("Results", message)
    
    def run(self):
        """Run the GUI application"""
        self.root.mainloop()
 
# GUI entry point
def run_gui():
    app = FileEncryptionGUI()
    app.run()
fileencryption.py
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import threading
 
class FileEncryptionGUI:
    def __init__(self):
        self.encryptor = FileEncryption()
        self.setup_gui()
        
    def setup_gui(self):
        """Setup the GUI interface"""
        self.root = tk.Tk()
        self.root.title("File Encryption Tool")
        self.root.geometry("600x500")
        self.root.resizable(True, True)
        
        # Create main frame
        main_frame = ttk.Frame(self.root, padding="10")
        main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        
        # Configure grid weights
        self.root.columnconfigure(0, weight=1)
        self.root.rowconfigure(0, weight=1)
        main_frame.columnconfigure(1, weight=1)
        
        # File selection
        ttk.Label(main_frame, text="Select Files:").grid(row=0, column=0, sticky=tk.W, pady=5)
        
        self.file_list = tk.Listbox(main_frame, height=8)
        self.file_list.grid(row=1, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=5)
        
        file_buttons_frame = ttk.Frame(main_frame)
        file_buttons_frame.grid(row=2, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=5)
        
        ttk.Button(file_buttons_frame, text="Add Files", 
                  command=self.add_files).pack(side=tk.LEFT, padx=5)
        ttk.Button(file_buttons_frame, text="Add Directory", 
                  command=self.add_directory).pack(side=tk.LEFT, padx=5)
        ttk.Button(file_buttons_frame, text="Clear List", 
                  command=self.clear_files).pack(side=tk.LEFT, padx=5)
        
        # Output directory
        ttk.Label(main_frame, text="Output Directory:").grid(row=3, column=0, sticky=tk.W, pady=5)
        
        self.output_var = tk.StringVar()
        ttk.Entry(main_frame, textvariable=self.output_var).grid(row=4, column=0, columnspan=2, 
                                                                sticky=(tk.W, tk.E), pady=5)
        ttk.Button(main_frame, text="Browse", 
                  command=self.browse_output).grid(row=4, column=2, padx=5)
        
        # Password
        ttk.Label(main_frame, text="Password:").grid(row=5, column=0, sticky=tk.W, pady=5)
        
        self.password_var = tk.StringVar()
        self.password_entry = ttk.Entry(main_frame, textvariable=self.password_var, show="*")
        self.password_entry.grid(row=6, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=5)
        
        self.show_password_var = tk.BooleanVar()
        ttk.Checkbutton(main_frame, text="Show Password", variable=self.show_password_var,
                       command=self.toggle_password).grid(row=6, column=2, padx=5)
        
        # Options
        options_frame = ttk.LabelFrame(main_frame, text="Options", padding="5")
        options_frame.grid(row=7, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=10)
        
        self.preserve_metadata_var = tk.BooleanVar(value=True)
        ttk.Checkbutton(options_frame, text="Preserve File Metadata", 
                       variable=self.preserve_metadata_var).pack(anchor=tk.W)
        
        self.recursive_var = tk.BooleanVar(value=False)
        ttk.Checkbutton(options_frame, text="Process Directories Recursively", 
                       variable=self.recursive_var).pack(anchor=tk.W)
        
        # Action buttons
        button_frame = ttk.Frame(main_frame)
        button_frame.grid(row=8, column=0, columnspan=3, pady=20)
        
        self.encrypt_button = ttk.Button(button_frame, text="Encrypt Files", 
                                       command=self.encrypt_files)
        self.encrypt_button.pack(side=tk.LEFT, padx=10)
        
        self.decrypt_button = ttk.Button(button_frame, text="Decrypt Files", 
                                       command=self.decrypt_files)
        self.decrypt_button.pack(side=tk.LEFT, padx=10)
        
        # Progress bar
        self.progress_var = tk.DoubleVar()
        self.progress_bar = ttk.Progressbar(main_frame, variable=self.progress_var, 
                                          maximum=100)
        self.progress_bar.grid(row=9, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=10)
        
        # Status label
        self.status_var = tk.StringVar(value="Ready")
        ttk.Label(main_frame, textvariable=self.status_var).grid(row=10, column=0, columnspan=3)
    
    def add_files(self):
        """Add files to the list"""
        files = filedialog.askopenfilenames(title="Select Files to Encrypt/Decrypt")
        for file in files:
            self.file_list.insert(tk.END, file)
    
    def add_directory(self):
        """Add directory to the list"""
        directory = filedialog.askdirectory(title="Select Directory")
        if directory:
            self.file_list.insert(tk.END, f"[DIR] {directory}")
    
    def clear_files(self):
        """Clear the file list"""
        self.file_list.delete(0, tk.END)
    
    def browse_output(self):
        """Browse for output directory"""
        directory = filedialog.askdirectory(title="Select Output Directory")
        if directory:
            self.output_var.set(directory)
    
    def toggle_password(self):
        """Toggle password visibility"""
        if self.show_password_var.get():
            self.password_entry.config(show="")
        else:
            self.password_entry.config(show="*")
    
    def encrypt_files(self):
        """Encrypt selected files"""
        self.process_files("encrypt")
    
    def decrypt_files(self):
        """Decrypt selected files"""
        self.process_files("decrypt")
    
    def process_files(self, action):
        """Process files in background thread"""
        files = list(self.file_list.get(0, tk.END))
        output_dir = self.output_var.get()
        password = self.password_var.get()
        
        if not files:
            messagebox.showerror("Error", "Please select files to process")
            return
        
        if not output_dir:
            messagebox.showerror("Error", "Please select output directory")
            return
        
        if not password:
            messagebox.showerror("Error", "Please enter a password")
            return
        
        # Disable buttons during processing
        self.encrypt_button.config(state="disabled")
        self.decrypt_button.config(state="disabled")
        
        # Start processing in background thread
        thread = threading.Thread(target=self._process_files_worker, 
                                 args=(action, files, output_dir, password))
        thread.daemon = True
        thread.start()
    
    def _process_files_worker(self, action, files, output_dir, password):
        """Background worker for file processing"""
        try:
            # Setup progress callbacks
            def progress_callback(processed, total):
                percentage = (processed / total) * 100
                self.root.after(0, lambda: self.progress_var.set(percentage))
            
            def batch_progress_callback(overall_progress, current_file, total_files):
                self.root.after(0, lambda: self.status_var.set(
                    f"Processing file {current_file}/{total_files} ({overall_progress:.1f}%)"))
                self.root.after(0, lambda: self.progress_var.set(overall_progress))
            
            self.encryptor.progress_callback = progress_callback
            self.encryptor.batch_progress_callback = batch_progress_callback
            
            # Process files
            if action == "encrypt":
                result = self.encryptor.encrypt_batch(files, output_dir, password, 
                                                    self.preserve_metadata_var.get())
            else:
                result = self.encryptor.decrypt_batch(files, output_dir, password)
            
            # Show results
            self.root.after(0, lambda: self._show_results(result))
            
        except Exception as e:
            self.root.after(0, lambda: messagebox.showerror("Error", str(e)))
        finally:
            # Re-enable buttons
            self.root.after(0, lambda: self.encrypt_button.config(state="normal"))
            self.root.after(0, lambda: self.decrypt_button.config(state="normal"))
            self.root.after(0, lambda: self.progress_var.set(0))
            self.root.after(0, lambda: self.status_var.set("Ready"))
    
    def _show_results(self, result):
        """Show processing results"""
        message = f"Operation completed!\n\n"
        message += f"Successful: {len(result['successful'])}\n"
        message += f"Failed: {len(result['failed'])}\n"
        message += f"Duration: {result['duration']:.2f} seconds"
        
        if result['failed']:
            message += f"\n\nFailed files:\n"
            for failed in result['failed'][:5]:  # Show first 5 failures
                message += f"- {failed['file']}: {failed['error']}\n"
            
            if len(result['failed']) > 5:
                message += f"... and {len(result['failed']) - 5} more"
        
        messagebox.showinfo("Results", message)
    
    def run(self):
        """Run the GUI application"""
        self.root.mainloop()
 
# GUI entry point
def run_gui():
    app = FileEncryptionGUI()
    app.run()

Running the Application

Command Line Usage

# Encrypt a single file
python fileencryption.py encrypt -i document.pdf -o document.pdf.enc
 
# Decrypt a single file
python fileencryption.py decrypt -i document.pdf.enc -o document_decrypted.pdf
 
# Batch encrypt directory
python fileencryption.py batch-encrypt -i /path/to/files -o /path/to/encrypted --recursive
 
# Batch decrypt with password
python fileencryption.py batch-decrypt -i /path/to/encrypted -o /path/to/decrypted -p mypassword
# Encrypt a single file
python fileencryption.py encrypt -i document.pdf -o document.pdf.enc
 
# Decrypt a single file
python fileencryption.py decrypt -i document.pdf.enc -o document_decrypted.pdf
 
# Batch encrypt directory
python fileencryption.py batch-encrypt -i /path/to/files -o /path/to/encrypted --recursive
 
# Batch decrypt with password
python fileencryption.py batch-decrypt -i /path/to/encrypted -o /path/to/decrypted -p mypassword

GUI Usage

fileencryption.py
# Run the GUI interface
if __name__ == "__main__":
    run_gui()
fileencryption.py
# Run the GUI interface
if __name__ == "__main__":
    run_gui()

API Usage

fileencryption.py
# Create encryptor instance
encryptor = FileEncryption()
 
# Encrypt a file
result = encryptor.encrypt_file(
    'important_document.pdf',
    'important_document.pdf.enc', 
    'my_secure_password'
)
 
# Decrypt a file
result = encryptor.decrypt_file(
    'important_document.pdf.enc',
    'important_document_decrypted.pdf',
    'my_secure_password'
)
fileencryption.py
# Create encryptor instance
encryptor = FileEncryption()
 
# Encrypt a file
result = encryptor.encrypt_file(
    'important_document.pdf',
    'important_document.pdf.enc', 
    'my_secure_password'
)
 
# Decrypt a file
result = encryptor.decrypt_file(
    'important_document.pdf.enc',
    'important_document_decrypted.pdf',
    'my_secure_password'
)

Sample Output

Successful Encryption

=== File Encryption Tool ===
Encrypting: important_document.pdf
Progress: 100.0%
✓ Successfully encrypted to: important_document.pdf.enc
  Original size: 2.3 MB
  Encrypted size: 2.3 MB
  File hash: a1b2c3d4e5f6789012345678901234567890abcdef
 
Encryption completed in 0.45 seconds
=== File Encryption Tool ===
Encrypting: important_document.pdf
Progress: 100.0%
✓ Successfully encrypted to: important_document.pdf.enc
  Original size: 2.3 MB
  Encrypted size: 2.3 MB
  File hash: a1b2c3d4e5f6789012345678901234567890abcdef
 
Encryption completed in 0.45 seconds

Batch Processing Results

Batch operation completed:
  Successful: 8
  Failed: 1
  Duration: 12.34 seconds
 
Failed files:
/path/to/corrupted_file.txt: Permission denied
Batch operation completed:
  Successful: 8
  Failed: 1
  Duration: 12.34 seconds
 
Failed files:
/path/to/corrupted_file.txt: Permission denied

Password Strength Validation

Password strength: Weak
- Password should contain uppercase letters
- Password should contain special characters
 
Continue with weak password? (y/N): n
 
Password strength: Strong
✓ Password accepted
Password strength: Weak
- Password should contain uppercase letters
- Password should contain special characters
 
Continue with weak password? (y/N): n
 
Password strength: Strong
✓ Password accepted

Security Considerations

1. Key Management Best Practices

fileencryption.py
def secure_key_storage():
    """Best practices for key storage"""
    # DO NOT store passwords in plaintext
    # DO NOT hardcode passwords in source code
    # DO use environment variables for automation
    # DO prompt for passwords interactively
    # DO validate password strength
    # DO use secure memory handling
    pass
 
# Example of secure environment-based password
import os
def get_password_from_env():
    password = os.environ.get('ENCRYPTION_PASSWORD')
    if not password:
        raise ValueError("ENCRYPTION_PASSWORD environment variable not set")
    return password
fileencryption.py
def secure_key_storage():
    """Best practices for key storage"""
    # DO NOT store passwords in plaintext
    # DO NOT hardcode passwords in source code
    # DO use environment variables for automation
    # DO prompt for passwords interactively
    # DO validate password strength
    # DO use secure memory handling
    pass
 
# Example of secure environment-based password
import os
def get_password_from_env():
    password = os.environ.get('ENCRYPTION_PASSWORD')
    if not password:
        raise ValueError("ENCRYPTION_PASSWORD environment variable not set")
    return password

2. Cryptographic Security Notes

fileencryption.py
# Security features implemented:
# - AES-256-CBC encryption (industry standard)
# - PBKDF2 key derivation with 100,000 iterations
# - Cryptographically secure random salt and IV generation
# - PKCS7 padding for proper block alignment
# - SHA-256 file integrity verification
# - Secure header format with versioning
# - Protection against common attacks:
#   - Dictionary attacks (strong KDF)
#   - Rainbow table attacks (unique salts)
#   - Padding oracle attacks (authenticated encryption recommended)
fileencryption.py
# Security features implemented:
# - AES-256-CBC encryption (industry standard)
# - PBKDF2 key derivation with 100,000 iterations
# - Cryptographically secure random salt and IV generation
# - PKCS7 padding for proper block alignment
# - SHA-256 file integrity verification
# - Secure header format with versioning
# - Protection against common attacks:
#   - Dictionary attacks (strong KDF)
#   - Rainbow table attacks (unique salts)
#   - Padding oracle attacks (authenticated encryption recommended)

3. File Security

fileencryption.py
def secure_file_handling():
    """Security considerations for file handling"""
    # - Original files are not modified during encryption
    # - Temporary files are avoided when possible
    # - File permissions are preserved
    # - Metadata can be optionally encrypted
    # - Secure deletion of temporary data
    # - Progress tracking without data leakage
    pass
fileencryption.py
def secure_file_handling():
    """Security considerations for file handling"""
    # - Original files are not modified during encryption
    # - Temporary files are avoided when possible
    # - File permissions are preserved
    # - Metadata can be optionally encrypted
    # - Secure deletion of temporary data
    # - Progress tracking without data leakage
    pass

Troubleshooting

Common Issues

1. Memory Issues with Large Files

fileencryption.py
# Solution: Process files in smaller chunks
def process_large_file(self, file_path, chunk_size=1024*1024):
    """Process large files in chunks to manage memory"""
    # Implementation uses streaming processing
    # Configurable chunk size based on available memory
    pass
fileencryption.py
# Solution: Process files in smaller chunks
def process_large_file(self, file_path, chunk_size=1024*1024):
    """Process large files in chunks to manage memory"""
    # Implementation uses streaming processing
    # Configurable chunk size based on available memory
    pass

2. Permission Errors

fileencryption.py
# Solution: Check file permissions before processing
def check_file_permissions(self, file_path):
    """Check if file can be read/written"""
    try:
        with open(file_path, 'rb') as f:
            pass
        return True
    except PermissionError:
        return False
fileencryption.py
# Solution: Check file permissions before processing
def check_file_permissions(self, file_path):
    """Check if file can be read/written"""
    try:
        with open(file_path, 'rb') as f:
            pass
        return True
    except PermissionError:
        return False

3. Corrupted Files

fileencryption.py
# Solution: Verify file integrity
def verify_file_before_processing(self, file_path):
    """Verify file is not corrupted before processing"""
    try:
        # Check file header for encrypted files
        with open(file_path, 'rb') as f:
            signature = f.read(5)
            if signature == b'PYENC':
                # Verify header checksum
                header_data = self._read_file_header(f)
                return True
    except Exception:
        return False
fileencryption.py
# Solution: Verify file integrity
def verify_file_before_processing(self, file_path):
    """Verify file is not corrupted before processing"""
    try:
        # Check file header for encrypted files
        with open(file_path, 'rb') as f:
            signature = f.read(5)
            if signature == b'PYENC':
                # Verify header checksum
                header_data = self._read_file_header(f)
                return True
    except Exception:
        return False

Performance Optimization

1. Multi-threading Support

fileencryption.py
import concurrent.futures
import threading
 
def encrypt_batch_parallel(self, file_list, output_directory, password, 
                          max_workers=4):
    """Encrypt multiple files in parallel"""
    results = {'successful': [], 'failed': []}
    
    with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
        # Submit all encryption tasks
        future_to_file = {}
        for file_path in file_list:
            output_file = os.path.join(output_directory, 
                                     f"{os.path.basename(file_path)}.enc")
            future = executor.submit(self.encrypt_file, file_path, output_file, password)
            future_to_file[future] = file_path
        
        # Collect results
        for future in concurrent.futures.as_completed(future_to_file):
            file_path = future_to_file[future]
            try:
                result = future.result()
                if result['success']:
                    results['successful'].append(result)
                else:
                    results['failed'].append(result)
            except Exception as e:
                results['failed'].append({
                    'file': file_path,
                    'error': str(e)
                })
    
    return results
fileencryption.py
import concurrent.futures
import threading
 
def encrypt_batch_parallel(self, file_list, output_directory, password, 
                          max_workers=4):
    """Encrypt multiple files in parallel"""
    results = {'successful': [], 'failed': []}
    
    with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
        # Submit all encryption tasks
        future_to_file = {}
        for file_path in file_list:
            output_file = os.path.join(output_directory, 
                                     f"{os.path.basename(file_path)}.enc")
            future = executor.submit(self.encrypt_file, file_path, output_file, password)
            future_to_file[future] = file_path
        
        # Collect results
        for future in concurrent.futures.as_completed(future_to_file):
            file_path = future_to_file[future]
            try:
                result = future.result()
                if result['success']:
                    results['successful'].append(result)
                else:
                    results['failed'].append(result)
            except Exception as e:
                results['failed'].append({
                    'file': file_path,
                    'error': str(e)
                })
    
    return results

2. Memory Optimization

fileencryption.py
def optimize_memory_usage(self):
    """Memory optimization strategies"""
    # Use streaming processing for large files
    # Process files in configurable chunks
    # Clear sensitive data from memory
    # Use memory-mapped files for very large files
    
    import mmap
    
    def process_with_mmap(self, file_path):
        """Use memory-mapped files for large file processing"""
        with open(file_path, 'rb') as f:
            with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as mm:
                # Process memory-mapped file
                pass
fileencryption.py
def optimize_memory_usage(self):
    """Memory optimization strategies"""
    # Use streaming processing for large files
    # Process files in configurable chunks
    # Clear sensitive data from memory
    # Use memory-mapped files for very large files
    
    import mmap
    
    def process_with_mmap(self, file_path):
        """Use memory-mapped files for large file processing"""
        with open(file_path, 'rb') as f:
            with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as mm:
                # Process memory-mapped file
                pass

Extensions and Improvements

1. Cloud Storage Integration

fileencryption.py
def encrypt_to_cloud(self, file_path, cloud_provider, credentials):
    """Encrypt and upload to cloud storage"""
    # Encrypt file locally
    encrypted_file = f"{file_path}.enc"
    result = self.encrypt_file(file_path, encrypted_file, password)
    
    if result['success']:
        # Upload to cloud (implementation depends on provider)
        cloud_upload(encrypted_file, cloud_provider, credentials)
        # Optionally remove local encrypted file
        os.remove(encrypted_file)
fileencryption.py
def encrypt_to_cloud(self, file_path, cloud_provider, credentials):
    """Encrypt and upload to cloud storage"""
    # Encrypt file locally
    encrypted_file = f"{file_path}.enc"
    result = self.encrypt_file(file_path, encrypted_file, password)
    
    if result['success']:
        # Upload to cloud (implementation depends on provider)
        cloud_upload(encrypted_file, cloud_provider, credentials)
        # Optionally remove local encrypted file
        os.remove(encrypted_file)

2. Key Escrow System

fileencryption.py
def setup_key_escrow(self, escrow_public_key):
    """Setup key escrow for enterprise environments"""
    # Encrypt master key with escrow public key
    # Store encrypted master key separately
    # Allow key recovery by authorized personnel
    pass
fileencryption.py
def setup_key_escrow(self, escrow_public_key):
    """Setup key escrow for enterprise environments"""
    # Encrypt master key with escrow public key
    # Store encrypted master key separately
    # Allow key recovery by authorized personnel
    pass

3. Compression Integration

fileencryption.py
def encrypt_with_compression(self, file_path, output_path, password, compress=True):
    """Encrypt with optional compression"""
    import gzip
    
    if compress:
        # Compress before encryption
        compressed_path = f"{file_path}.gz"
        with open(file_path, 'rb') as f_in:
            with gzip.open(compressed_path, 'wb') as f_out:
                f_out.writelines(f_in)
        
        # Encrypt compressed file
        result = self.encrypt_file(compressed_path, output_path, password)
        os.remove(compressed_path)  # Clean up
        return result
    else:
        return self.encrypt_file(file_path, output_path, password)
fileencryption.py
def encrypt_with_compression(self, file_path, output_path, password, compress=True):
    """Encrypt with optional compression"""
    import gzip
    
    if compress:
        # Compress before encryption
        compressed_path = f"{file_path}.gz"
        with open(file_path, 'rb') as f_in:
            with gzip.open(compressed_path, 'wb') as f_out:
                f_out.writelines(f_in)
        
        # Encrypt compressed file
        result = self.encrypt_file(compressed_path, output_path, password)
        os.remove(compressed_path)  # Clean up
        return result
    else:
        return self.encrypt_file(file_path, output_path, password)

Next Steps

After mastering this encryption tool, consider:

  1. Digital Signatures: Add RSA/ECDSA signature support
  2. Key Management: Implement proper key management system
  3. Cloud Integration: Support for cloud storage encryption
  4. Enterprise Features: Key escrow, policy management
  5. Mobile Apps: Create mobile versions for file encryption

Resources

Conclusion

This file encryption tool provides enterprise-grade security for protecting sensitive files using industry-standard encryption algorithms. It demonstrates advanced cryptographic concepts, secure programming practices, and professional software development techniques.

The tool combines security, usability, and performance to create a comprehensive solution for file protection. The modular design allows for easy extension and customization for specific security requirements. 🔒🐍

Was this page helpful?

Let us know how we did