193 lines
No EOL
6.8 KiB
Python
193 lines
No EOL
6.8 KiB
Python
import ssl
|
|
|
|
def create_tls_context(purpose=ssl.Purpose.CLIENT_AUTH):
|
|
"""
|
|
Creates an SSL context configured for TLS 1.3.
|
|
|
|
Args:
|
|
purpose: The SSL purpose (e.g., CLIENT_AUTH, SERVER_AUTH).
|
|
|
|
Returns:
|
|
An SSLContext object configured for TLS 1.3.
|
|
"""
|
|
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT if purpose == ssl.Purpose.CLIENT_AUTH else ssl.PROTOCOL_TLS_SERVER)
|
|
|
|
# Require TLS 1.3
|
|
context.minimum_version = ssl.TLSVersion.TLSv1_3
|
|
|
|
# TLS 1.3 cipher suites are handled automatically by the underlying SSL library
|
|
# when minimum_version is set to TLSv1_3. Explicitly setting them via
|
|
# set_ciphers can cause issues. The required suites (AES-256-GCM, CHACHA20)
|
|
# are typically included and preferred by default in modern OpenSSL.
|
|
# context.set_ciphers('...') # Removed
|
|
|
|
# Configure certificate loading and verification
|
|
if purpose == ssl.Purpose.SERVER_AUTH:
|
|
# Server context: Load server cert/key and require client certs for RBAC
|
|
# context.load_cert_chain(certfile="path/to/server_cert.pem", keyfile="path/to/server_key.pem") # Placeholder: Needs actual paths
|
|
context.verify_mode = ssl.CERT_REQUIRED
|
|
# context.load_verify_locations(cafile="path/to/trusted_client_ca.pem") # Placeholder: Needs actual CA path for client cert validation
|
|
elif purpose == ssl.Purpose.CLIENT_AUTH:
|
|
# Client context: Load client cert/key and verify server cert against CA
|
|
# context.load_cert_chain(certfile="path/to/client_cert.pem", keyfile="path/to/client_key.pem") # Placeholder: Needs actual paths
|
|
# context.load_verify_locations(cafile="path/to/trusted_server_ca.pem") # Placeholder: Needs actual CA path
|
|
context.verify_mode = ssl.CERT_REQUIRED # Verify the server certificate
|
|
|
|
# minimum_version = TLSv1_3 implicitly disables older protocols.
|
|
# Explicit OP_NO flags are redundant but harmless; removed for clarity.
|
|
# context.options |= ssl.OP_NO_SSLv2 # Redundant
|
|
# context.options |= ssl.OP_NO_SSLv3 # Redundant
|
|
# context.options |= ssl.OP_NO_TLSv1 # Redundant
|
|
# context.options |= ssl.OP_NO_TLSv1_1 # Redundant
|
|
# context.options |= ssl.OP_NO_TLSv1_2 # Redundant
|
|
|
|
# Options for perfect forward secrecy are generally enabled by default with TLS 1.3 ciphers
|
|
# context.options |= ssl.OP_SINGLE_DH_USE
|
|
# context.options |= ssl.OP_SINGLE_ECDH_USE
|
|
|
|
# Ensure TLS 1.3 is the minimum (already set, but good to be explicit)
|
|
context.minimum_version = ssl.TLSVersion.TLSv1_3
|
|
|
|
return context
|
|
|
|
# Example usage (can be removed or kept for demonstration)
|
|
if __name__ == '__main__':
|
|
client_context = create_tls_context(ssl.Purpose.CLIENT_AUTH)
|
|
print(f"Client Context Minimum TLS Version: {client_context.minimum_version}")
|
|
# print(f"Client Context Ciphers: {client_context.get_ciphers()}") # Requires OpenSSL 1.1.1+
|
|
|
|
server_context = create_tls_context(ssl.Purpose.SERVER_AUTH)
|
|
print(f"Server Context Minimum TLS Version: {server_context.minimum_version}")
|
|
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
|
from cryptography.hazmat.backends import default_backend
|
|
import os
|
|
|
|
class AES256Cipher:
|
|
"""AES-256-GCM encryption/decryption wrapper class."""
|
|
|
|
def __init__(self, key: bytes = None):
|
|
"""
|
|
Initialize cipher with optional key.
|
|
|
|
Args:
|
|
key: Optional 32-byte AES-256 key. If None, generates new key.
|
|
"""
|
|
self.key = key if key is not None else self.generate_key()
|
|
|
|
@staticmethod
|
|
def generate_key() -> bytes:
|
|
"""Generate a secure 256-bit AES key for encryption/decryption.
|
|
|
|
Returns:
|
|
bytes: 32-byte AES-256 key
|
|
"""
|
|
return os.urandom(32)
|
|
|
|
def encrypt(self, plaintext: bytes) -> bytes:
|
|
"""Encrypt data using AES-256-GCM.
|
|
|
|
Args:
|
|
plaintext: Data to encrypt
|
|
|
|
Returns:
|
|
bytes: Encrypted data in format (nonce + ciphertext + tag)
|
|
"""
|
|
return encrypt_data(plaintext, self.key)
|
|
|
|
def decrypt(self, encrypted_data: bytes) -> bytes:
|
|
"""Decrypt data using AES-256-GCM.
|
|
|
|
Args:
|
|
encrypted_data: Data in format (nonce + ciphertext + tag)
|
|
|
|
Returns:
|
|
bytes: Decrypted plaintext
|
|
"""
|
|
return decrypt_data(encrypted_data, self.key)
|
|
|
|
def generate_key():
|
|
"""Generate a secure 256-bit AES key for encryption/decryption.
|
|
|
|
Returns:
|
|
bytes: 32-byte AES-256 key
|
|
"""
|
|
return os.urandom(32)
|
|
|
|
def encrypt_data(plaintext: bytes, key: bytes) -> bytes:
|
|
"""Encrypt data using AES-256-GCM.
|
|
|
|
Args:
|
|
plaintext: Data to encrypt
|
|
key: 32-byte AES-256 key
|
|
|
|
Returns:
|
|
bytes: Encrypted data in format (nonce + ciphertext + tag)
|
|
|
|
Raises:
|
|
ValueError: If key length is invalid
|
|
"""
|
|
if len(key) != 32:
|
|
raise ValueError("Key must be 32 bytes for AES-256")
|
|
|
|
# Generate random 96-bit nonce
|
|
nonce = os.urandom(12)
|
|
|
|
# Create cipher and encrypt
|
|
cipher = Cipher(
|
|
algorithms.AES(key),
|
|
modes.GCM(nonce),
|
|
backend=default_backend()
|
|
)
|
|
encryptor = cipher.encryptor()
|
|
ciphertext = encryptor.update(plaintext) + encryptor.finalize()
|
|
|
|
# Return nonce + ciphertext + tag
|
|
return nonce + ciphertext + encryptor.tag
|
|
|
|
def decrypt_data(encrypted_data: bytes, key: bytes) -> bytes:
|
|
"""Decrypt data using AES-256-GCM.
|
|
|
|
Args:
|
|
encrypted_data: Data in format (nonce + ciphertext + tag)
|
|
key: 32-byte AES-256 key
|
|
|
|
Returns:
|
|
bytes: Decrypted plaintext
|
|
|
|
Raises:
|
|
ValueError: If key length is invalid or data is malformed
|
|
"""
|
|
if len(key) != 32:
|
|
raise ValueError("Key must be 32 bytes for AES-256")
|
|
if len(encrypted_data) < 28: # Minimum: 12 nonce + 16 tag
|
|
raise ValueError("Encrypted data too short")
|
|
|
|
# Split into components
|
|
nonce = encrypted_data[:12]
|
|
ciphertext = encrypted_data[12:-16]
|
|
tag = encrypted_data[-16:]
|
|
|
|
# Create cipher and decrypt
|
|
cipher = Cipher(
|
|
algorithms.AES(key),
|
|
modes.GCM(nonce, tag),
|
|
backend=default_backend()
|
|
)
|
|
decryptor = cipher.decryptor()
|
|
return decryptor.update(ciphertext) + decryptor.finalize()
|
|
|
|
# Example usage for AES-256-GCM functions
|
|
if __name__ == '__main__':
|
|
# Generate key
|
|
key = generate_key()
|
|
print(f"Generated AES-256 key: {key.hex()}")
|
|
|
|
# Encrypt test data
|
|
plaintext = b"Test message for AES-256-GCM implementation"
|
|
encrypted = encrypt_data(plaintext, key)
|
|
print(f"Encrypted data (hex): {encrypted.hex()}")
|
|
|
|
# Decrypt test data
|
|
decrypted = decrypt_data(encrypted, key)
|
|
print(f"Decrypted data: {decrypted.decode()}")
|
|
# print(f"Server Context Ciphers: {server_context.get_ciphers()}") # Requires OpenSSL 1.1.1+ |