Day 6: Credential Encryption & Doppler Integration
What I Built
- Fernet Credential Encryption: Symmetric encryption for sensitive data using cryptography library
- Doppler Secrets Integration: Environment-based configuration with Doppler secrets management
- Encrypted IBKR Credentials: Database storage of encrypted trading credentials with automatic decryption
- Pydantic Settings Configuration: Type-safe environment variable management
- Comprehensive Credential Tests: 11 unit tests covering encryption, decryption, and configuration
Code Highlight
# Fernet-based credential encryption
from cryptography.fernet import Fernet
class CredentialManager:
def __init__(self):
master_key = os.getenv("ENCRYPTION_MASTER_KEY")
if not master_key:
raise ValueError("ENCRYPTION_MASTER_KEY not set in environment")
self.cipher = Fernet(master_key.encode())
def encrypt(self, plaintext: str) -> str:
encrypted = self.cipher.encrypt(plaintext.encode())
return encrypted.decode()
def decrypt(self, encrypted: str) -> str:
decrypted = self.cipher.decrypt(encrypted.encode())
return decrypted.decode()
# Pydantic settings with environment variables
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
encryption_master_key: str # Required
api_secret_key: str = "dev_secret_key_change_in_prod"
ibkr_account_id: str = ""
database_url: str = "mysql+aiomysql://zephyr:zephyr_dev_password@localhost:3306/zephyrapex"
class Config:
env_file = ".env"
# Account model with encrypted credentials
class Account(Base):
# ... existing fields ...
ibkr_username_encrypted = Column(String(500), nullable=True)
ibkr_password_encrypted = Column(String(500), nullable=True)
ibkr_last_login = Column(DateTime(timezone=True), nullable=True)
def set_ibkr_credentials(self, username: str, password: str):
from security import encrypt_credential
self.ibkr_username_encrypted = encrypt_credential(username)
self.ibkr_password_encrypted = encrypt_credential(password)
def get_ibkr_credentials(self) -> tuple[str, str]:
from security import decrypt_credential
username = decrypt_credential(self.ibkr_username_encrypted)
password = decrypt_credential(self.ibkr_password_encrypted)
return username, password
Architecture Decision
Chose Fernet symmetric encryption over asymmetric crypto for performance and simplicity - the master key is managed via Doppler secrets manager. Pydantic Settings provides type safety and validation for all environment variables. IBKR credentials are encrypted at the application level before database storage, ensuring they're never stored in plaintext.
Testing Results
All 11 unit tests pass, covering critical credential scenarios:
- Fernet encryption round-trip with different keys
- Environment variable configuration loading
- Account credential encryption/decryption methods
- Error handling for missing encryption keys
- Validation of required vs optional settings
Next Steps
Day 7 will focus on HMAC request signing with replay protection and rate limiting to secure API communications.
Follow @therealkamba on X for regular updates. View all posts →