A simple and secure way to manage environment variables with AES encryption
Project description
๐ Secure-Env
A simple and secure environment variables manager with AES-256 encryption.
Secure-Env solves the critical problem of storing passwords, API keys, and other secrets in plain text within .env files or directly in source code. It provides an elegant solution with transparent encryption and a simple API.
๐ Why Secure-Env?
โ Current Problems$$
# Classic .env file - DANGEROUS!
DATABASE_PASSWORD=SuperSecretPassword123!
API_KEY=sk-1234567890abcdef
JWT_SECRET=my-jwt-signing-key
โ Secure-Env Solution
# Secure .env file
DATABASE_PASSWORD=ENC(gAAAAABh_9x2K8L5vV6K2oJ3cE...)
API_KEY=ENC(gAAAAABh_8y3L9M6wW7L3pK4dF...)
JWT_SECRET=ENC(gAAAAABh_7z4M0N7xX8M4qL5eG...)
DEBUG=true
๐ Installation
# Install from PyPI
pip install secure-env
# Install with development dependencies
pip install secure-env[dev]
# From source code
git clone https://github.com/Moesthetics-code/secure-env.git
cd secure-env
pip install -e .
Verify Installation
# Check version
secure-env --version
# Test Python import
python -c "import secure_env; print(secure_env.__version__)"
โก Quick Start
1. Initial Setup
# Initialize a secure environment
secure-env init --generate-key
# Output:
# ๐ Generated master key: 8mJeOQ-Xd9u2vFq4GhKlMpNo5r8t6w3z
# โ ๏ธ IMPORTANT: Save this key securely!
# Set master key (recommended)
export SECURE_ENV_MASTER_KEY='8mJeOQ-Xd9u2vFq4GhKlMpNo5r8t6w3z'
2. Add Secrets
# Add a password (encrypted)
secure-env set DATABASE_PASSWORD
๐ Enter value for DATABASE_PASSWORD: ****
# Or directly with value
secure-env set API_KEY 'sk-1234567890abcdef'
โ
Set API_KEY (encrypted)
# Add unencrypted variable
secure-env set DEBUG true --no-encrypt
โ
Set DEBUG (plain)
3. Usage
# List all variables
secure-env list
# Get a variable
secure-env get DATABASE_PASSWORD
# Export for usage
eval $(secure-env export --format env)
echo $DATABASE_PASSWORD # Shows decrypted value
4. Python Usage
from secure_env import Secrets
# Initialize with master key
secrets = Secrets(master_key="your-master-key")
# Or use SECURE_ENV_MASTER_KEY environment variable
secrets = Secrets()
# Store secrets
secrets.set("DATABASE_PASSWORD", "SuperSecretPassword123!")
secrets.set("API_KEY", "sk-1234567890abcdef")
# Retrieve secrets (automatically decrypted)
db_password = secrets.get("DATABASE_PASSWORD")
api_key = secrets.get("API_KEY")
print(f"DB Password: {db_password}")
print(f"API Key: {api_key}")
๐ CLI Commands Documentation
secure-env init
Description: Initialize a new secure environment.
Syntax:
secure-env init [OPTIONS]
Options:
--generate-key: Automatically generate a random master key--force: Force overwrite of existing .env file--env-file PATH,-f PATH: Specify .env file (default:.env)
Examples:
# Basic initialization (prompts for master key)
secure-env init
# Initialize with key generation
secure-env init --generate-key
# Initialize in specific file
secure-env init -f .env.production --generate-key
# Force overwrite
secure-env init --force
Example Output:
๐ง Initializing secure environment...
๐ Generated master key: 8mJeOQ-Xd9u2vFq4GhKlMpNo5r8t6w3z
โ ๏ธ IMPORTANT: Save this key securely! You'll need it to decrypt your secrets.
โ
Initialized secure environment in .env
๐ก To use this environment, set:
export SECURE_ENV_MASTER_KEY='8mJeOQ-Xd9u2vFq4GhKlMpNo5r8t6w3z'
secure-env set
Description: Set an environment variable (encrypted by default).
Syntax:
secure-env set KEY [VALUE] [OPTIONS]
Arguments:
KEY: Variable name (required)VALUE: Variable value (optional, prompts if omitted)
Options:
--no-encrypt: Store value as plain text--no-prompt: Don't prompt for value if omitted--env-file PATH,-f PATH: .env file to use--master-key KEY,-k KEY: Master key (not recommended)
Examples:
# Set with secure prompt
secure-env set DATABASE_PASSWORD
๐ Enter value for DATABASE_PASSWORD: ****
# Set with direct value
secure-env set API_KEY 'sk-1234567890abcdef'
# Set unencrypted variable
secure-env set DEBUG true --no-encrypt
secure-env set PORT 8080 --no-encrypt
# Set in specific file
secure-env set -f .env.prod SECRET_KEY 'prod-secret'
# Script usage (no prompt)
echo "secret-value" | secure-env set MY_SECRET --no-prompt
Supported Data Types:
# Strings
secure-env set MESSAGE "Hello World!"
# Numbers (stored as strings)
secure-env set PORT 8080
secure-env set RATE_LIMIT 100.5
# Booleans (stored as strings)
secure-env set ENABLED true
secure-env set DEBUG false
# JSON (stored as string)
secure-env set CONFIG '{"host": "localhost", "port": 5432}'
# URLs with special characters
secure-env set DATABASE_URL 'postgresql://user:pass@host:5432/db'
secure-env get
Description: Retrieve an environment variable value.
Syntax:
secure-env get KEY [OPTIONS]
Arguments:
KEY: Variable name to retrieve
Options:
--show-encrypted: Show both encrypted and decrypted versions--quiet,-q: Show only value (for scripts)--env-file PATH,-f PATH: .env file to use--master-key KEY,-k KEY: Master key
Examples:
# Normal retrieval
secure-env get DATABASE_PASSWORD
# Output: DATABASE_PASSWORD: SuperSecretPassword123!
# Quiet mode for scripts
DB_PASS=$(secure-env get DATABASE_PASSWORD --quiet)
echo "Password: $DB_PASS"
# Show encrypted and decrypted versions
secure-env get API_KEY --show-encrypted
# ๐ API_KEY (encrypted): ENC(gAAAAABh_8y3L9M6wW...)
# ๐ API_KEY (decrypted): sk-1234567890abcdef
# Get from specific file
secure-env get -f .env.staging SECRET_KEY
Return Codes:
0: Success1: Variable not found or decryption error
Error Handling:
# Non-existent variable
secure-env get NON_EXISTENT
# โ Variable 'NON_EXISTENT' not found
# Return code: 1
# Decryption error (wrong key)
secure-env get ENCRYPTED_VAR
# โ Failed to decrypt 'ENCRYPTED_VAR' - wrong master key?
# Return code: 1
secure-env list
Description: List all environment variables with their encryption status.
Syntax:
secure-env list [OPTIONS]
Options:
--show-values: Show variable values--format FORMAT: Output format (tableorjson)--env-file PATH,-f PATH: .env file to use--master-key KEY,-k KEY: Master key
Examples:
# Basic list
secure-env list
# Key | Value/Status
# -----------------|-------------
# API_KEY | ๐ encrypted
# DATABASE_PASSWORD| ๐ encrypted
# DEBUG | ๐ plain
# PORT | ๐ plain
# List with values
secure-env list --show-values
# Key | Value/Status
# -----------------|-------------
# API_KEY ๐ | sk-1234567890abcdef
# DATABASE_PASSWORD๐| SuperSecretPassword123!
# DEBUG ๐ | true
# PORT ๐ | 8080
# JSON format
secure-env list --format json
# {
# "API_KEY": true,
# "DATABASE_PASSWORD": true,
# "DEBUG": false,
# "PORT": false
# }
# JSON format with values
secure-env list --show-values --format json
# {
# "API_KEY ๐": "sk-1234567890abcdef",
# "DATABASE_PASSWORD ๐": "SuperSecretPassword123!",
# "DEBUG ๐": "true",
# "PORT ๐": "8080"
# }
Icon Legend:
- ๐ : Encrypted variable
- ๐ : Plain text variable
- โ : Decryption error
secure-env delete
Description: Delete an environment variable.
Syntax:
secure-env delete KEY [OPTIONS]
Arguments:
KEY: Variable name to delete
Options:
--force: Delete without confirmation--env-file PATH,-f PATH: .env file to use--master-key KEY,-k KEY: Master key
Examples:
# Delete with confirmation
secure-env delete OLD_API_KEY
โ Delete 'OLD_API_KEY'? (y/N): y
โ
Deleted 'OLD_API_KEY'
# Force delete (for scripts)
secure-env delete TEMP_SECRET --force
โ
Deleted 'TEMP_SECRET'
# Delete from specific file
secure-env delete -f .env.test TEST_VAR --force
# Attempt to delete non-existent variable
secure-env delete NON_EXISTENT --force
โ Variable 'NON_EXISTENT' not found
secure-env export
Description: Export variables in different formats for external use.
Syntax:
secure-env export [OPTIONS]
Options:
--format FORMAT: Export format (env,json,dotenv)--skip-failed: Skip variables that cannot be decrypted--env-file PATH,-f PATH: .env file to use--master-key KEY,-k KEY: Master key
Available Formats:
1. Format env (default) - Shell variables
secure-env export --format env
# export DATABASE_PASSWORD="SuperSecretPassword123!"
# export API_KEY="sk-1234567890abcdef"
# export DEBUG="true"
# export PORT="8080"
# Shell usage
eval $(secure-env export --format env)
echo $DATABASE_PASSWORD # SuperSecretPassword123!
2. Format json - JSON object
secure-env export --format json
# {
# "DATABASE_PASSWORD": "SuperSecretPassword123!",
# "API_KEY": "sk-1234567890abcdef",
# "DEBUG": "true",
# "PORT": "8080"
# }
# Usage with jq
secure-env export --format json | jq -r '.DATABASE_PASSWORD'
3. Format dotenv - Decrypted .env format
secure-env export --format dotenv
# DATABASE_PASSWORD="SuperSecretPassword123!"
# API_KEY=sk-1234567890abcdef
# DEBUG=true
# PORT=8080
# Redirect to file
secure-env export --format dotenv > .env.decrypted
Advanced Examples:
# Export with error handling
secure-env export --format env --skip-failed
# Export from production environment
secure-env export -f .env.prod --format json
# Pipeline usage
secure-env export --format env | grep DATABASE
# Export for Docker
secure-env export --format env > docker.env
docker run --env-file docker.env myapp
secure-env import
Description: Import variables from an external file.
Syntax:
secure-env import FILE [OPTIONS]
Arguments:
FILE: File to import (.env format)
Options:
--overwrite: Overwrite existing variables--no-encrypt: Import variables as plain text--env-file PATH,-f PATH: Destination .env file--master-key KEY,-k KEY: Master key
Supported File Formats:
# Standard .env format
KEY1=value1
KEY2="value with spaces"
KEY3='single quoted'
# With comments (ignored)
# Database configuration
DATABASE_URL=postgresql://localhost/myapp
DATABASE_PASSWORD=secret123
# Empty variables (ignored)
EMPTY_VAR=
Examples:
# Basic import (encrypts variables)
secure-env import backup.env
โ
Imported 5 variables
โ ๏ธ Skipped 2 items
# Import as plain text
secure-env import config.env --no-encrypt
# Import with overwrite
secure-env import new-secrets.env --overwrite
# Import to specific file
secure-env import -f .env.staging staging-config.env
# Import with detailed report
secure-env import large-config.env
# โ ๏ธ Skipping line 15: Invalid format
# โ ๏ธ Skipping DATABASE_URL: already exists (use --overwrite)
# โ
Imported 23 variables
# โ ๏ธ Skipped 3 items
Conflict Handling:
# Without --overwrite: skip existing variables
secure-env import config.env
# โ ๏ธ Skipping API_KEY: already exists (use --overwrite)
# With --overwrite: replace existing variables
secure-env import config.env --overwrite
# โ
Overwritten API_KEY
secure-env rotate-key
Description: Rotate the master key (re-encrypt all variables).
Syntax:
secure-env rotate-key [OPTIONS]
Options:
--new-key KEY: Specify new master key--generate-new: Automatically generate new key--env-file PATH,-f PATH: .env file to process--master-key KEY,-k KEY: Current master key
Examples:
# Rotate with automatic generation
secure-env rotate-key --generate-new
๐ Rotating master key...
๐ Generated new master key: nTq7sU-Yg0v3xGr5IlPnOqNo6t9w4z2A
โ
Master key rotated successfully!
๐ก Update your environment:
export SECURE_ENV_MASTER_KEY='nTq7sU-Yg0v3xGr5IlPnOqNo6t9w4z2A'
# Rotate to specific key
secure-env rotate-key --new-key "my-new-master-key-2024"
๐ Rotating master key...
โ
Master key rotated successfully!
# Interactive rotation (prompts for new key)
secure-env rotate-key
๐ Rotating master key...
๐ Enter new master key: ****
โ
Master key rotated successfully!
Rotation Process:
- Decrypt all variables with old key
- Generate/get new key
- Re-encrypt all variables with new key
- Update .env file
Security:
- Rotation is atomic (all or nothing)
- Automatic backup on error
- New key validation before applying
secure-env check
Description: Check environment file integrity and decryption validity.
Syntax:
secure-env check [OPTIONS]
Options:
--env-file PATH,-f PATH: .env file to check--master-key KEY,-k KEY: Master key
Examples:
# Basic check
secure-env check
๐ Checking .env...
๐ Total variables: 10
๐ Encrypted: 7
๐ Plain text: 3
โ
All encrypted variables can be decrypted successfully!
# Check with problems
secure-env check
๐ Checking .env...
๐ Total variables: 8
๐ Encrypted: 5
๐ Plain text: 3
โ Failed to decrypt 2 variables:
- OLD_API_KEY
- CORRUPTED_SECRET
# Check specific file
secure-env check -f .env.production
Reported Information:
- Total number of variables
- Number of encrypted vs plain text variables
- Variables that cannot be decrypted
- Overall integrity status
Return Codes:
0: All decryptions successful1: One or more variables cannot be decrypted
๐ Detailed Python API
Secrets Class
Constructor
class Secrets:
def __init__(
self,
master_key: Optional[str] = None,
env_file: str = '.env',
auto_create: bool = True
)
Parameters:
master_key: Master key for encryption. IfNone, usesSECURE_ENV_MASTER_KEYenv_file: Path to .env file (default:.env)auto_create: Create file if it doesn't exist (default:True)
Exceptions:
InvalidKeyError: Invalid or missing master keyFileAccessError: File access problem
Example:
from secure_env import Secrets
# With explicit key
secrets = Secrets(master_key="my-master-key")
# With environment variable
import os
os.environ['SECURE_ENV_MASTER_KEY'] = 'my-key'
secrets = Secrets()
# Custom file
secrets = Secrets(
master_key="key",
env_file=".env.production",
auto_create=False
)
Main Methods
set(key, value, encrypt=True)
Set an environment variable.
def set(self, key: str, value: Union[str, int, float, bool], encrypt: bool = True) -> None
Parameters:
key: Variable namevalue: Variable value (converted to string)encrypt: IfTrue, encrypt the value
Examples:
# Automatic encryption
secrets.set("DATABASE_PASSWORD", "secret123")
secrets.set("API_KEY", "sk-1234567890abcdef")
# Different types (converted to strings)
secrets.set("PORT", 8080)
secrets.set("RATE_LIMIT", 100.5)
secrets.set("DEBUG", True)
# Plain text
secrets.set("PUBLIC_URL", "https://myapp.com", encrypt=False)
# Complex data
secrets.set("CONFIG", '{"host": "localhost", "port": 5432}')
get(key, default=None, auto_decrypt=True)
Retrieve an environment variable.
def get(self, key: str, default: Any = None, auto_decrypt: bool = True) -> Any
Parameters:
key: Variable namedefault: Default value if key doesn't existauto_decrypt: Automatically decrypt encrypted values
Examples:
# Normal retrieval
password = secrets.get("DATABASE_PASSWORD") # "secret123"
# With default value
port = secrets.get("PORT", default=8080)
# Without auto-decryption
encrypted_value = secrets.get("API_KEY", auto_decrypt=False)
# Returns: "ENC(gAAAAABh...)"
# Type handling
port = int(secrets.get("PORT", "8080"))
debug = secrets.get("DEBUG", "false").lower() == "true"
config = json.loads(secrets.get("CONFIG", "{}"))
delete(key)
Delete a variable.
def delete(self, key: str) -> bool
Returns: True if key existed and was deleted, False otherwise.
# Deletion
if secrets.delete("OLD_API_KEY"):
print("Key deleted")
else:
print("Key not found")
list_keys(show_encrypted=True)
List all keys with their encryption status.
def list_keys(self, show_encrypted: bool = True) -> Dict[str, bool]
Examples:
# With encryption status
keys = secrets.list_keys()
# {"API_KEY": True, "DEBUG": False, "PORT": False}
for key, is_encrypted in keys.items():
status = "๐" if is_encrypted else "๐"
print(f"{key} {status}")
# Simple list
keys = secrets.list_keys(show_encrypted=False)
# ["API_KEY", "DEBUG", "PORT"]
export_to_os_environ(decrypt_all=True)
Export to os.environ.
def export_to_os_environ(self, decrypt_all: bool = True) -> None
Example:
import os
# Export with decryption
secrets.export_to_os_environ()
print(os.environ['DATABASE_PASSWORD']) # "secret123"
# Export without decryption (keep encrypted values)
secrets.export_to_os_environ(decrypt_all=False)
print(os.environ['API_KEY']) # "ENC(gAAAAA...)"
change_master_key(new_master_key)
Change master key and re-encrypt all variables.
def change_master_key(self, new_master_key: str) -> None
Example:
# Key rotation
secrets.change_master_key("new-master-key-2024")
# Encrypted variables are automatically re-encrypted
# with the new key
Utility Methods
encrypt(plaintext) and decrypt(encrypted_data)
Manual encryption/decryption.
# Manual encryption
encrypted = secrets.encrypt("secret-data")
print(encrypted) # "gAAAAABh..."
# Manual decryption
decrypted = secrets.decrypt(encrypted)
print(decrypted) # "secret-data"
reload()
Reload variables from file.
# External modification of .env file
# then reload
secrets.reload()
Exceptions
from secure_env.exceptions import (
SecureEnvError, # Base exception
InvalidKeyError, # Invalid master key
DecryptionError, # Decryption failure
FileAccessError # File access problem
)
try:
secrets = Secrets(master_key="wrong-key")
value = secrets.get("ENCRYPTED_VAR")
except InvalidKeyError:
print("Invalid master key")
except DecryptionError:
print("Cannot decrypt")
except FileAccessError:
print("File access problem")
except SecureEnvError as e:
print(f"General error: {e}")
๐ง Integrations and Use Cases
Django Integration
# settings.py
import os
from secure_env import Secrets
# Initialize
secrets = Secrets()
# Django configuration
SECRET_KEY = secrets.get('DJANGO_SECRET_KEY')
DEBUG = secrets.get('DJANGO_DEBUG', 'False').lower() == 'true'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': secrets.get('DB_NAME'),
'USER': secrets.get('DB_USER'),
'PASSWORD': secrets.get('DB_PASSWORD'),
'HOST': secrets.get('DB_HOST', 'localhost'),
'PORT': secrets.get('DB_PORT', '5432'),
}
}
# Email configuration
EMAIL_HOST_PASSWORD = secrets.get('EMAIL_PASSWORD')
# External APIs
STRIPE_SECRET_KEY = secrets.get('STRIPE_SECRET_KEY')
AWS_SECRET_ACCESS_KEY = secrets.get('AWS_SECRET_KEY')
Flask Integration
# config.py
from secure_env import Secrets
class Config:
def __init__(self):
self.secrets = Secrets()
SECRET_KEY = property(lambda self: self.secrets.get('FLASK_SECRET_KEY'))
DATABASE_URI = property(lambda self: self.secrets.get('DATABASE_URL'))
MAIL_PASSWORD = property(lambda self: self.secrets.get('MAIL_PASSWORD'))
# app.py
from flask import Flask
from config import Config
app = Flask(__name__)
app.config.from_object(Config())
Docker Deployment
# Dockerfile
FROM python:3.11-slim
# Install application
COPY . /app
WORKDIR /app
RUN pip install secure-env
# Startup script
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
#!/bin/bash
# entrypoint.sh
# Check for master key
if [ -z "$SECURE_ENV_MASTER_KEY" ]; then
echo "โ SECURE_ENV_MASTER_KEY is required"
exit 1
fi
# Check integrity
secure-env check
if [ $? -ne 0 ]; then
echo "โ Environment check failed"
exit 1
fi
# Export variables
eval $(secure-env export --format env)
# Start application
exec python app.py
CI/CD with GitHub Actions
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install secure-env
run: pip install secure-env
- name: Setup secrets
env:
SECURE_ENV_MASTER_KEY: ${{ secrets.PRODUCTION_MASTER_KEY }}
run: |
# Check secrets integrity
secure-env check -f .env.production
# Export for application
secure-env export -f .env.production --format dotenv > .env.deploy
- name: Deploy application
run: |
# Deploy with secrets
./deploy.sh
Multi-Environment Management
# File structure
.env.development # Local development
.env.staging # Testing environment
.env.production # Production
# Management scripts
#!/bin/bash
# setup-env.sh
ENV=${1:-development}
ENV_FILE=".env.${ENV}"
if [ ! -f "$ENV_FILE" ]; then
echo "โ Environment file $ENV_FILE not found"
exit 1
fi
echo "๐ง Setting up $ENV environment..."
# Check integrity
secure-env check -f "$ENV_FILE"
# Export variables
eval $(secure-env export -f "$ENV_FILE" --format env)
echo "โ
Environment $ENV ready"
FastAPI Integration
# app.py
from fastapi import FastAPI
from secure_env import Secrets
import uvicorn
app = FastAPI()
secrets = Secrets()
# Configuration
DATABASE_URL = secrets.get("DATABASE_URL")
JWT_SECRET = secrets.get("JWT_SECRET_KEY")
REDIS_URL = secrets.get("REDIS_URL")
@app.get("/health")
async def health_check():
return {"status": "healthy", "secrets_loaded": True}
if __name__ == "__main__":
# Server configuration
host = secrets.get("HOST", "0.0.0.0")
port = int(secrets.get("PORT", "8000"))
uvicorn.run(
"app:app",
host=host,
port=port,
reload=secrets.get("DEBUG", "false").lower() == "true"
)
Migration Scripts
# migrate_secrets.py
"""Script to migrate unencrypted secrets to secure-env."""
import os
from secure_env import Secrets
def migrate_from_env_file(source_file: str, target_file: str, master_key: str):
"""Migrate a .env file to secure-env."""
print(f"๐ Migrating {source_file} to {target_file}")
# Initialize secure-env
secrets = Secrets(master_key=master_key, env_file=target_file)
# Read source file
migrated_count = 0
with open(source_file, 'r') as f:
for line_num, line in enumerate(f, 1):
line = line.strip()
if not line or line.startswith('#'):
continue
if '=' not in line:
print(f"โ ๏ธ Line {line_num}: Invalid format")
continue
key, value = line.split('=', 1)
key = key.strip()
# Clean quotes
value = value.strip()
if value.startswith('"') and value.endswith('"'):
value = value[1:-1]
elif value.startswith("'") and value.endswith("'"):
value = value[1:-1]
# Determine if should encrypt
sensitive_keywords = ['password', 'secret', 'key', 'token', 'api']
should_encrypt = any(keyword in key.lower() for keyword in sensitive_keywords)
# Migrate
secrets.set(key, value, encrypt=should_encrypt)
migrated_count += 1
status = "๐" if should_encrypt else "๐"
print(f" {key} {status}")
print(f"โ
Migrated {migrated_count} variables")
if __name__ == "__main__":
import sys
if len(sys.argv) != 4:
print("Usage: python migrate_secrets.py <source.env> <target.env> <master_key>")
sys.exit(1)
source_file, target_file, master_key = sys.argv[1:4]
migrate_from_env_file(source_file, target_file, master_key)
๐ Security Guide
Secure Key Generation
from secure_env.utils import generate_random_key
import secrets
import base64
# Method 1: Use built-in utility
master_key = generate_random_key()
print(f"Generated key: {master_key}")
# Method 2: Manual generation
random_bytes = secrets.token_bytes(32)
manual_key = base64.urlsafe_b64encode(random_bytes).decode()
print(f"Manual key: {manual_key}")
Secure Key Storage
# โ NEVER in source code
secrets = Secrets(master_key="hardcoded-key")
# โ NEVER in versioned config files
# config.py
MASTER_KEY = "exposed-key"
# โ
System environment variable
export SECURE_ENV_MASTER_KEY="secure-key"
# โ
Cloud secret managers (AWS Secrets Manager, Azure Key Vault)
# โ
Hashicorp Vault
# โ
Kubernetes Secrets
# โ
Secure local file (development only)
echo "my-master-key" > ~/.secure-env-key
chmod 600 ~/.secure-env-key
export SECURE_ENV_MASTER_KEY=$(cat ~/.secure-env-key)
Regular Key Rotation
#!/bin/bash
# rotate_keys.sh - Automatic rotation script
# Backup current state
secure-env export --format dotenv > backup-$(date +%Y%m%d).env
# Generate new key
NEW_KEY=$(python -c "from secure_env.utils import generate_random_key; print(generate_random_key())")
# Perform rotation
secure-env rotate-key --new-key "$NEW_KEY"
# Update systems
echo "๐ Update SECURE_ENV_MASTER_KEY in all systems:"
echo " New key: $NEW_KEY"
# Notify teams
echo "๐ง Notify team members about key rotation"
Audit and Monitoring
# audit.py - Secrets audit script
from secure_env import Secrets
import datetime
import json
def audit_secrets(env_file: str):
"""Perform secrets audit."""
secrets = Secrets(env_file=env_file)
keys_info = secrets.list_keys()
audit_report = {
"timestamp": datetime.datetime.now().isoformat(),
"env_file": env_file,
"total_variables": len(keys_info),
"encrypted_count": sum(1 for is_enc in keys_info.values() if is_enc),
"plain_count": sum(1 for is_enc in keys_info.values() if not is_enc),
"variables": {}
}
# Analyze each variable
for key, is_encrypted in keys_info.items():
audit_report["variables"][key] = {
"encrypted": is_encrypted,
"potential_secret": any(word in key.lower() for word in
['password', 'secret', 'key', 'token', 'api'])
}
return audit_report
# Usage
report = audit_secrets(".env.production")
print(json.dumps(report, indent=2))
Security Testing and Validation
# security_tests.py
import unittest
from secure_env import Secrets
from secure_env.exceptions import DecryptionError
class SecurityTests(unittest.TestCase):
def test_wrong_key_fails(self):
"""Verify wrong key fails."""
secrets1 = Secrets(master_key="key1", env_file="test1.env")
secrets1.set("TEST", "secret")
secrets2 = Secrets(master_key="key2", env_file="test1.env")
with self.assertRaises(DecryptionError):
secrets2.get("TEST")
def test_encrypted_at_rest(self):
"""Verify secrets are encrypted on disk."""
secrets = Secrets(master_key="test-key", env_file="test2.env")
secrets.set("PASSWORD", "secret123")
# Read file directly
with open("test2.env", 'r') as f:
content = f.read()
# Secret should not appear in plain text
self.assertNotIn("secret123", content)
self.assertIn("ENC(", content)
def test_key_rotation(self):
"""Test key rotation."""
secrets = Secrets(master_key="old-key", env_file="test3.env")
secrets.set("TEST", "value")
# Rotation
secrets.change_master_key("new-key")
# Verify value is still accessible
self.assertEqual(secrets.get("TEST"), "value")
# Verify old key can't decrypt anymore
old_secrets = Secrets(master_key="old-key", env_file="test3.env")
with self.assertRaises(DecryptionError):
old_secrets.get("TEST")
if __name__ == '__main__':
unittest.main()
๐ Performance and Optimization
Performance Benchmarks
# benchmark.py
import time
from secure_env import Secrets
def benchmark_operations(num_operations: int = 1000):
"""Benchmark basic operations."""
secrets = Secrets(master_key="benchmark-key", env_file="benchmark.env")
# Test set (encryption)
start = time.time()
for i in range(num_operations):
secrets.set(f"KEY_{i}", f"value_{i}")
set_time = time.time() - start
# Test get (decryption)
start = time.time()
for i in range(num_operations):
_ = secrets.get(f"KEY_{i}")
get_time = time.time() - start
print(f"๐ Benchmark Results ({num_operations} operations):")
print(f" Set operations: {set_time:.2f}s ({num_operations/set_time:.1f} ops/sec)")
print(f" Get operations: {get_time:.2f}s ({num_operations/get_time:.1f} ops/sec)")
# Cleanup
import os
os.remove("benchmark.env")
if __name__ == "__main__":
benchmark_operations()
Recommended Optimizations
# Single initialization (recommended)
class AppConfig:
_secrets = None
@classmethod
def get_secrets(cls):
if cls._secrets is None:
cls._secrets = Secrets()
return cls._secrets
# Usage
config = AppConfig()
secrets = config.get_secrets()
db_password = secrets.get("DATABASE_PASSWORD")
# Cache frequently used values
class CachedSecrets:
def __init__(self, master_key: str):
self.secrets = Secrets(master_key=master_key)
self._cache = {}
def get(self, key: str, default=None):
if key not in self._cache:
self._cache[key] = self.secrets.get(key, default)
return self._cache[key]
def invalidate_cache(self):
self._cache.clear()
๐ Troubleshooting and FAQ
Common Issues
1. "InvalidKeyError: Master key is required"
# Cause: No master key provided
# Solutions:
export SECURE_ENV_MASTER_KEY='your-key'
# or
secure-env set KEY value --master-key 'your-key'
2. "DecryptionError: Invalid encrypted data or wrong master key"
# Cause: Wrong key or corrupted data
# Diagnosis:
secure-env check
# Solutions:
# - Verify master key
# - Restore from backup
# - Re-encrypt with correct key
3. "FileAccessError: Cannot read .env"
# Cause: Insufficient permissions
# Solution:
chmod 600 .env
chown $USER:$USER .env
4. Variables not loaded in application
# Problem: Variables not in os.environ
# Solution:
from secure_env import Secrets
secrets = Secrets()
secrets.export_to_os_environ()
# or in shell:
eval $(secure-env export --format env)
FAQ
Q: Can I use secure-env without CLI? A: Yes, the Python API works independently of the CLI.
from secure_env import Secrets
secrets = Secrets(master_key="key")
secrets.set("VAR", "value")
Q: How to share secrets between teams? A: Share the encrypted .env file + master key via separate secure channels.
Q: What happens if I lose the master key? A: Encrypted secrets are permanently lost. Keep backups of the key!
Q: Can I use multiple .env files?
A: Yes, specify the file with -f or env_file parameter.
secure-env -f .env.production set API_KEY 'prod-key'
Q: How to integrate with Docker Secrets? A: Mount the secret as environment variable:
# docker-compose.yml
services:
app:
environment:
- SECURE_ENV_MASTER_KEY_FILE=/run/secrets/master_key
secrets:
- master_key
Q: Performance with many variables? A: Secure-env is optimized for hundreds of variables. For more, use caching.
Q: Kubernetes compatible? A: Yes, use ConfigMaps for .env and Secrets for master key.
apiVersion: v1
kind: Secret
metadata:
name: secure-env-key
data:
master-key: <base64-encoded-key>
---
apiVersion: v1
kind: ConfigMap
metadata:
name: app-env
data:
.env: |
API_KEY=ENC(gAAAAA...)
DB_PASSWORD=ENC(gAAAAA...)
๐ Roadmap and Evolution
Current Version (2.0.0)
- โ AES-256 encryption
- โ Complete CLI
- โ Python API
- โ Multi-file support
- โ Import/Export
- โ Key rotation
Future Versions
v1.1.0 (Planned)
- ๐ Shared keys support (team keys)
- ๐ Cloud integration (AWS, Azure, GCP)
- ๐ Automatic backup
- ๐ Rotation webhooks
v1.2.0 (Under consideration)
- ๐ค Web management interface
- ๐ค RBAC support (Role-Based Access)
- ๐ค Complete audit trail
- ๐ค Plugin system
๐ค Contributing
How to Contribute
- Fork the repository
- Clone your fork
- Create a feature branch
- Code with best practices
- Test your changes
- Commit and push
- Create a Pull Request
# Development setup
git clone https://github.com/Moesthetics-code/secure-env.git
cd secure-env
pip install -e ".[dev]"
# Run tests
make test
# Check style
make lint format-check
# Local build
make build
Contribution Standards
- Tests : Minimum 85% coverage
- Style : Black + flake8
- Types : Type hints required
- Docs : Docstrings + examples
- Changelog : Entry for each PR
Areas Looking for Improvement
- ๐ Security : Audits, vulnerabilities
- ๐ Performance : Optimizations
- ๐ฑ UX : CLI/API improvements
- ๐ Integrations : New frameworks
- ๐ Documentation : Examples, guides
๐ Support and Community
Support Channels
- ๐ง Email: support@secure-env.dev
- ๐ Issues: GitHub Issues
- ๐ฌ Discussions: GitHub Discussions
- ๐ Documentation: docs.secure-env.dev
Community
- โญ GitHub: Starred by X developers
- ๐ฆ PyPI: X downloads/month
- ๐ฆ Twitter: @secure_env
- ๐ผ LinkedIn: Secure-Env Community
Sponsoring
Secure-Env is an open source project. Support development:
- โ Buy me a coffee: buymeacoffee.com/secure-env
- ๐ GitHub Sponsors: GitHub Sponsors
- ๐ข Enterprise Support: Contact us for enterprise support
๐ License and Credits
License
This project is licensed under MIT License. See LICENSE for details.
Credits
- Encryption: Cryptography library
- Inspiration: Real-world production security problems
- Community: Contributors and users
Acknowledgments
Thanks to all contributors, testers, and users who make this project possible!
๐ Secure-Env - Secure your secrets, simplify your life!
Made with โค๏ธ for the Python community
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file secure_env-2.0.0.tar.gz.
File metadata
- Download URL: secure_env-2.0.0.tar.gz
- Upload date:
- Size: 48.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
67d45b5fe03c53d6a1392284d31a5a32d59e52b7a42ec5095d3ebd2667e170b1
|
|
| MD5 |
698fb840126149ea63880e70683a5e6f
|
|
| BLAKE2b-256 |
e8afb2fd1f8f3cea8541b1f50ccceaaf895db9a8e3d2fe7b0256e2e0a3b4fe15
|
File details
Details for the file secure_env-2.0.0-py3-none-any.whl.
File metadata
- Download URL: secure_env-2.0.0-py3-none-any.whl
- Upload date:
- Size: 27.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
17cc48f0f89b4f775d21dc9388c16e22ffbba0feeb201313fe8cd2173574f1e8
|
|
| MD5 |
1668bad095cd6bf41a4ad82892fcdf8a
|
|
| BLAKE2b-256 |
52cf3f64cbbf9e5ef4872e92ce7768dc7463bb6ea11f3edad2f6d18e37cdb7d3
|