Files
sa-lessons/modules/module6/sections/03-encryption-key-management.md
Alexander Abakumov 15e744da49 Initial commit: Complete Software Architecture course with 11 modules
- Added comprehensive course structure with 11 modules
- Included theoretical content, practical examples, and tasks
- Multi-language code examples (Java, Rust, Node.js, Python)
- Detailed analysis of architectural approaches and trade-offs
- Complete caching strategies documentation
- Architecture examples from real-world systems (Twitter, Netflix, Uber)
- Structured navigation and learning path
- Ready for educational use
2025-09-09 22:08:07 +03:00

25 KiB

Шифрование и управление ключами

Навигация


Типы шифрования

1. Symmetric Encryption (Симметричное шифрование)

Симметричное шифрование использует один ключ для шифрования и расшифровки данных.

Преимущества:

  • Быстрое выполнение
  • Подходит для больших объемов данных
  • Простота реализации

Недостатки:

  • Проблема распределения ключей
  • Сложность масштабирования

Технический пример - Advanced Encryption Service:

@Service
public class SymmetricEncryptionService {
    
    private final SecretKey secretKey;
    private final String algorithm = "AES/GCM/NoPadding";
    private final int GCM_IV_LENGTH = 12;
    private final int GCM_TAG_LENGTH = 16;
    
    @Value("${encryption.key}")
    private String base64Key;
    
    public SymmetricEncryptionService() throws Exception {
        // Генерируем ключ из конфигурации или создаем новый
        if (base64Key != null && !base64Key.isEmpty()) {
            byte[] keyBytes = Base64.getDecoder().decode(base64Key);
            this.secretKey = new SecretKeySpec(keyBytes, "AES");
        } else {
            KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
            keyGenerator.init(256);
            this.secretKey = keyGenerator.generateKey();
        }
    }
    
    public EncryptedData encrypt(String plainText) throws Exception {
        if (plainText == null || plainText.isEmpty()) {
            throw new IllegalArgumentException("Plain text cannot be null or empty");
        }
        
        // Генерируем случайный IV
        byte[] iv = new byte[GCM_IV_LENGTH];
        SecureRandom.getInstanceStrong().nextBytes(iv);
        
        // Настраиваем шифр
        Cipher cipher = Cipher.getInstance(algorithm);
        GCMParameterSpec parameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
        
        // Шифруем данные
        byte[] encryptedBytes = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
        
        // Объединяем IV и зашифрованные данные
        byte[] encryptedWithIv = new byte[GCM_IV_LENGTH + encryptedBytes.length];
        System.arraycopy(iv, 0, encryptedWithIv, 0, GCM_IV_LENGTH);
        System.arraycopy(encryptedBytes, 0, encryptedWithIv, GCM_IV_LENGTH, encryptedBytes.length);
        
        return new EncryptedData(
            Base64.getEncoder().encodeToString(encryptedWithIv),
            System.currentTimeMillis()
        );
    }
    
    public String decrypt(EncryptedData encryptedData) throws Exception {
        if (encryptedData == null || encryptedData.getData() == null) {
            throw new IllegalArgumentException("Encrypted data cannot be null");
        }
        
        byte[] encryptedWithIv = Base64.getDecoder().decode(encryptedData.getData());
        
        if (encryptedWithIv.length < GCM_IV_LENGTH) {
            throw new IllegalArgumentException("Invalid encrypted data length");
        }
        
        // Извлекаем IV
        byte[] iv = new byte[GCM_IV_LENGTH];
        System.arraycopy(encryptedWithIv, 0, iv, 0, GCM_IV_LENGTH);
        
        // Извлекаем зашифрованные данные
        byte[] encryptedBytes = new byte[encryptedWithIv.length - GCM_IV_LENGTH];
        System.arraycopy(encryptedWithIv, GCM_IV_LENGTH, encryptedBytes, 0, encryptedBytes.length);
        
        // Настраиваем шифр для расшифровки
        Cipher cipher = Cipher.getInstance(algorithm);
        GCMParameterSpec parameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv);
        cipher.init(Cipher.DECRYPT_MODE, secretKey, parameterSpec);
        
        // Расшифровываем данные
        byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
        return new String(decryptedBytes, StandardCharsets.UTF_8);
    }
    
    public String encryptSensitiveField(String value) throws Exception {
        if (value == null) {
            return null;
        }
        return encrypt(value).getData();
    }
    
    public String decryptSensitiveField(String encryptedValue) throws Exception {
        if (encryptedValue == null) {
            return null;
        }
        return decrypt(new EncryptedData(encryptedValue, 0L));
    }
}

// Encrypted Data Wrapper
public class EncryptedData {
    private final String data;
    private final long timestamp;
    
    public EncryptedData(String data, long timestamp) {
        this.data = data;
        this.timestamp = timestamp;
    }
    
    public String getData() {
        return data;
    }
    
    public long getTimestamp() {
        return timestamp;
    }
}

// Field-level Encryption
@Entity
public class User {
    @Id
    private Long id;
    
    private String name;
    
    @Convert(converter = EncryptedStringConverter.class)
    private String email;
    
    @Convert(converter = EncryptedStringConverter.class)
    private String phoneNumber;
    
    @Convert(converter = EncryptedStringConverter.class)
    private String ssn;
    
    // Getters and setters
}

// Custom Converter for JPA
@Converter
public class EncryptedStringConverter implements AttributeConverter<String, String> {
    
    @Autowired
    private SymmetricEncryptionService encryptionService;
    
    @Override
    public String convertToDatabaseColumn(String attribute) {
        if (attribute == null) {
            return null;
        }
        try {
            return encryptionService.encryptSensitiveField(attribute);
        } catch (Exception e) {
            throw new RuntimeException("Failed to encrypt field", e);
        }
    }
    
    @Override
    public String convertToEntityAttribute(String dbData) {
        if (dbData == null) {
            return null;
        }
        try {
            return encryptionService.decryptSensitiveField(dbData);
        } catch (Exception e) {
            throw new RuntimeException("Failed to decrypt field", e);
        }
    }
}

Rust примеры - Симметричное шифрование:

use aes_gcm::{Aes256Gcm, Key, Nonce};
use aes_gcm::aead::{Aead, NewAead};
use rand::Rng;

pub struct SymmetricEncryptionService {
    cipher: Aes256Gcm,
}

impl SymmetricEncryptionService {
    pub fn new(key: &[u8]) -> Result<Self, Box<dyn std::error::Error>> {
        let key = Key::from_slice(key);
        let cipher = Aes256Gcm::new(key);
        
        Ok(Self { cipher })
    }
    
    pub fn encrypt(&self, plaintext: &str) -> Result<EncryptedData, Box<dyn std::error::Error>> {
        let nonce = self.generate_nonce();
        let ciphertext = self.cipher.encrypt(&nonce, plaintext.as_bytes())?;
        
        Ok(EncryptedData {
            data: base64::encode(ciphertext),
            nonce: base64::encode(nonce),
            timestamp: std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)?
                .as_secs(),
        })
    }
    
    pub fn decrypt(&self, encrypted_data: &EncryptedData) -> Result<String, Box<dyn std::error::Error>> {
        let nonce = base64::decode(&encrypted_data.nonce)?;
        let ciphertext = base64::decode(&encrypted_data.data)?;
        
        let plaintext = self.cipher.decrypt(&nonce.into(), ciphertext.as_ref())?;
        Ok(String::from_utf8(plaintext)?)
    }
    
    fn generate_nonce(&self) -> Nonce {
        let mut nonce = [0u8; 12];
        rand::thread_rng().fill(&mut nonce);
        nonce.into()
    }
}

#[derive(Debug, Clone)]
pub struct EncryptedData {
    pub data: String,
    pub nonce: String,
    pub timestamp: u64,
}

Python примеры - Симметричное шифрование:

from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
import base64
import os

class SymmetricEncryptionService:
    def __init__(self, password: str):
        # Генерируем ключ из пароля
        salt = os.urandom(16)
        kdf = PBKDF2HMAC(
            algorithm=hashes.SHA256(),
            length=32,
            salt=salt,
            iterations=100000,
        )
        key = base64.urlsafe_b64encode(kdf.derive(password.encode()))
        self.cipher = Fernet(key)
    
    def encrypt(self, plaintext: str) -> EncryptedData:
        if not plaintext:
            raise ValueError("Plaintext cannot be empty")
        
        encrypted_data = self.cipher.encrypt(plaintext.encode())
        
        return EncryptedData(
            data=base64.b64encode(encrypted_data).decode(),
            timestamp=int(time.time())
        )
    
    def decrypt(self, encrypted_data: EncryptedData) -> str:
        if not encrypted_data.data:
            raise ValueError("Encrypted data cannot be empty")
        
        encrypted_bytes = base64.b64decode(encrypted_data.data)
        decrypted_bytes = self.cipher.decrypt(encrypted_bytes)
        
        return decrypted_bytes.decode()

@dataclass
class EncryptedData:
    data: str
    timestamp: int

Node.js примеры - Симметричное шифрование:

import crypto from 'crypto';

interface EncryptedData {
    data: string;
    iv: string;
    timestamp: number;
}

class SymmetricEncryptionService {
    private algorithm = 'aes-256-gcm';
    private keyLength = 32;
    private ivLength = 16;
    private tagLength = 16;
    
    constructor(private key: Buffer) {
        if (key.length !== this.keyLength) {
            throw new Error(`Key must be ${this.keyLength} bytes long`);
        }
    }
    
    encrypt(plaintext: string): EncryptedData {
        if (!plaintext) {
            throw new Error('Plaintext cannot be empty');
        }
        
        const iv = crypto.randomBytes(this.ivLength);
        const cipher = crypto.createCipher(this.algorithm, this.key);
        cipher.setAAD(iv);
        
        let encrypted = cipher.update(plaintext, 'utf8', 'hex');
        encrypted += cipher.final('hex');
        
        const tag = cipher.getAuthTag();
        
        return {
            data: encrypted,
            iv: iv.toString('hex'),
            timestamp: Date.now()
        };
    }
    
    decrypt(encryptedData: EncryptedData): string {
        if (!encryptedData.data || !encryptedData.iv) {
            throw new Error('Invalid encrypted data');
        }
        
        const iv = Buffer.from(encryptedData.iv, 'hex');
        const decipher = crypto.createDecipher(this.algorithm, this.key);
        decipher.setAAD(iv);
        
        let decrypted = decipher.update(encryptedData.data, 'hex', 'utf8');
        decrypted += decipher.final('utf8');
        
        return decrypted;
    }
}

2. Asymmetric Encryption (Асимметричное шифрование)

Асимметричное шифрование использует пару ключей: публичный для шифрования и приватный для расшифровки.

Преимущества:

  • Безопасное распределение ключей
  • Цифровые подписи
  • Масштабируемость

Недостатки:

  • Медленнее симметричного шифрования
  • Сложность реализации
@Service
public class AsymmetricEncryptionService {
    private final KeyPair keyPair;
    
    public AsymmetricEncryptionService() throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        keyPairGenerator.initialize(2048);
        this.keyPair = keyPairGenerator.generateKeyPair();
    }
    
    public String encrypt(String plainText, PublicKey publicKey) throws Exception {
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        
        byte[] encryptedBytes = cipher.doFinal(plainText.getBytes());
        return Base64.getEncoder().encodeToString(encryptedBytes);
    }
    
    public String decrypt(String encryptedText, PrivateKey privateKey) throws Exception {
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        
        byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedText));
        return new String(decryptedBytes);
    }
    
    public String sign(String data, PrivateKey privateKey) throws Exception {
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(privateKey);
        signature.update(data.getBytes());
        
        byte[] signatureBytes = signature.sign();
        return Base64.getEncoder().encodeToString(signatureBytes);
    }
    
    public boolean verify(String data, String signature, PublicKey publicKey) throws Exception {
        Signature sig = Signature.getInstance("SHA256withRSA");
        sig.initVerify(publicKey);
        sig.update(data.getBytes());
        
        return sig.verify(Base64.getDecoder().decode(signature));
    }
}

3. Hashing (Хеширование)

Хеширование — одностороннее преобразование данных в фиксированную длину.

Применения:

  • Хранение паролей
  • Проверка целостности данных
  • Цифровые подписи
@Service
public class HashingService {
    
    private final int BCRYPT_ROUNDS = 12;
    private final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA256";
    private final int PBKDF2_ITERATIONS = 100000;
    private final int PBKDF2_KEY_LENGTH = 256;
    
    public String hashPassword(String password) {
        if (password == null || password.isEmpty()) {
            throw new IllegalArgumentException("Password cannot be null or empty");
        }
        return BCrypt.hashpw(password, BCrypt.gensalt(BCRYPT_ROUNDS));
    }
    
    public boolean verifyPassword(String password, String hash) {
        if (password == null || hash == null) {
            return false;
        }
        return BCrypt.checkpw(password, hash);
    }
    
    public String generatePBKDF2Hash(String password, String salt) throws Exception {
        if (password == null || salt == null) {
            throw new IllegalArgumentException("Password and salt cannot be null");
        }
        
        PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt.getBytes(), 
            PBKDF2_ITERATIONS, PBKDF2_KEY_LENGTH);
        SecretKeyFactory skf = SecretKeyFactory.getInstance(PBKDF2_ALGORITHM);
        byte[] hash = skf.generateSecret(spec).getEncoded();
        
        return Base64.getEncoder().encodeToString(hash);
    }
    
    public String generateSalt() {
        SecureRandom random = new SecureRandom();
        byte[] salt = new byte[16];
        random.nextBytes(salt);
        return Base64.getEncoder().encodeToString(salt);
    }
    
    public String generateHash(String input, String algorithm) throws Exception {
        if (input == null) {
            throw new IllegalArgumentException("Input cannot be null");
        }
        
        MessageDigest digest = MessageDigest.getInstance(algorithm);
        byte[] hash = digest.digest(input.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(hash);
    }
    
    public String generateHMAC(String data, String key, String algorithm) throws Exception {
        if (data == null || key == null) {
            throw new IllegalArgumentException("Data and key cannot be null");
        }
        
        Mac mac = Mac.getInstance(algorithm);
        SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), algorithm);
        mac.init(secretKeySpec);
        
        byte[] hmacBytes = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(hmacBytes);
    }
}

Управление ключами

1. Key Rotation (Ротация ключей)

Ротация ключей — регулярная смена ключей шифрования для повышения безопасности.

@Service
public class KeyManagementService {
    
    @Value("${encryption.key.rotation.enabled:true}")
    private boolean keyRotationEnabled;
    
    @Value("${encryption.key.rotation.interval:30}")
    private int rotationIntervalDays;
    
    private final Map<String, SecretKey> keyVersions = new ConcurrentHashMap<>();
    private final AtomicInteger currentKeyVersion = new AtomicInteger(1);
    
    @PostConstruct
    public void initializeKeys() throws Exception {
        // Создаем начальный ключ
        SecretKey initialKey = generateAESKey();
        keyVersions.put("v1", initialKey);
        
        if (keyRotationEnabled) {
            scheduleKeyRotation();
        }
    }
    
    @Scheduled(cron = "0 0 2 * * ?") // Каждый день в 2:00
    public void rotateKeys() throws Exception {
        if (!keyRotationEnabled) {
            return;
        }
        
        log.info("Starting key rotation process");
        
        // Генерируем новый ключ
        int newVersion = currentKeyVersion.incrementAndGet();
        SecretKey newKey = generateAESKey();
        keyVersions.put("v" + newVersion, newKey);
        
        // Перешифровываем критически важные данные
        reencryptCriticalData(newKey);
        
        // Удаляем старые ключи (оставляем последние 3 версии)
        cleanupOldKeys();
        
        log.info("Key rotation completed. New version: v{}", newVersion);
    }
    
    public SecretKey getCurrentKey() {
        return keyVersions.get("v" + currentKeyVersion.get());
    }
    
    public SecretKey getKeyByVersion(int version) {
        return keyVersions.get("v" + version);
    }
    
    private SecretKey generateAESKey() throws Exception {
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        keyGenerator.init(256);
        return keyGenerator.generateKey();
    }
    
    private void reencryptCriticalData(SecretKey newKey) {
        // Логика перешифровки критически важных данных
        // Это может включать перешифровку данных в БД
    }
    
    private void cleanupOldKeys() {
        int currentVersion = currentKeyVersion.get();
        int keepVersions = 3;
        
        for (int i = 1; i <= currentVersion - keepVersions; i++) {
            keyVersions.remove("v" + i);
        }
    }
}

2. Hardware Security Module (HSM)

HSM — аппаратный модуль безопасности для безопасного хранения и управления ключами.

@Service
public class HSMService {
    private final HSMClient hsmClient;
    
    public String encryptWithHSM(String data) {
        return hsmClient.encrypt(data);
    }
    
    public String decryptWithHSM(String encryptedData) {
        return hsmClient.decrypt(encryptedData);
    }
    
    public String signWithHSM(String data) {
        return hsmClient.sign(data);
    }
    
    public boolean verifyWithHSM(String data, String signature) {
        return hsmClient.verify(data, signature);
    }
    
    public String generateKeyInHSM(String keyId) {
        return hsmClient.generateKey(keyId);
    }
    
    public void deleteKeyFromHSM(String keyId) {
        hsmClient.deleteKey(keyId);
    }
}

3. Key Vault Integration

Интеграция с хранилищем ключей для централизованного управления ключами.

@Service
public class KeyVaultService {
    
    @Value("${azure.keyvault.url}")
    private String keyVaultUrl;
    
    @Value("${azure.keyvault.client-id}")
    private String clientId;
    
    @Value("${azure.keyvault.client-secret}")
    private String clientSecret;
    
    private KeyVaultClient keyVaultClient;
    
    @PostConstruct
    public void initialize() {
        // Инициализация клиента Key Vault
        this.keyVaultClient = new KeyVaultClient(keyVaultUrl, clientId, clientSecret);
    }
    
    public String getSecret(String secretName) {
        return keyVaultClient.getSecret(secretName);
    }
    
    public void setSecret(String secretName, String secretValue) {
        keyVaultClient.setSecret(secretName, secretValue);
    }
    
    public void deleteSecret(String secretName) {
        keyVaultClient.deleteSecret(secretName);
    }
    
    public String getKey(String keyName) {
        return keyVaultClient.getKey(keyName);
    }
    
    public void createKey(String keyName, String keyType) {
        keyVaultClient.createKey(keyName, keyType);
    }
}

Безопасность ключей

1. Key Storage (Хранение ключей)

Принципы безопасного хранения ключей:

  • Никогда не храните ключи в коде
  • Используйте переменные окружения
  • Шифруйте ключи при хранении
  • Разделяйте ключи по окружениям
@Configuration
public class KeyStorageConfig {
    
    @Bean
    public SecretKey encryptionKey() {
        String keyString = System.getenv("ENCRYPTION_KEY");
        if (keyString == null) {
            throw new IllegalStateException("ENCRYPTION_KEY environment variable not set");
        }
        
        byte[] keyBytes = Base64.getDecoder().decode(keyString);
        return new SecretKeySpec(keyBytes, "AES");
    }
    
    @Bean
    public String jwtSecret() {
        String secret = System.getenv("JWT_SECRET");
        if (secret == null) {
            throw new IllegalStateException("JWT_SECRET environment variable not set");
        }
        return secret;
    }
}

2. Key Access Control (Контроль доступа к ключам)

@Service
public class KeyAccessControlService {
    
    @Autowired
    private AuditService auditService;
    
    public SecretKey getKeyWithAccessControl(String keyId, String userId, String operation) {
        // Проверяем права доступа
        if (!hasKeyAccess(userId, keyId, operation)) {
            auditService.logUnauthorizedKeyAccess(userId, keyId, operation);
            throw new AccessDeniedException("Access denied to key: " + keyId);
        }
        
        // Логируем доступ к ключу
        auditService.logKeyAccess(userId, keyId, operation);
        
        // Возвращаем ключ
        return keyManagementService.getKey(keyId);
    }
    
    private boolean hasKeyAccess(String userId, String keyId, String operation) {
        // Логика проверки прав доступа к ключу
        return userService.hasPermission(userId, "key:" + keyId + ":" + operation);
    }
}

3. Key Monitoring (Мониторинг ключей)

@Service
public class KeyMonitoringService {
    
    @Autowired
    private MetricsService metricsService;
    
    @Autowired
    private AlertService alertService;
    
    public void monitorKeyUsage(String keyId, String operation) {
        // Увеличиваем счетчик использования ключа
        metricsService.incrementCounter("key.usage", 
            Tags.of("key_id", keyId, "operation", operation));
        
        // Проверяем на подозрительную активность
        if (isSuspiciousActivity(keyId, operation)) {
            alertService.sendKeySecurityAlert(keyId, operation);
        }
    }
    
    private boolean isSuspiciousActivity(String keyId, String operation) {
        // Проверка на необычное использование ключа
        long usageCount = metricsService.getCounterValue("key.usage", 
            Tags.of("key_id", keyId, "operation", operation));
        
        return usageCount > 1000; // Подозрительно много использований
    }
}

Заключение

Шифрование и управление ключами являются критически важными компонентами безопасности:

  1. Выбор алгоритма — AES для симметричного, RSA/ECC для асимметричного
  2. Управление ключами — ротация, безопасное хранение, контроль доступа
  3. Мониторинг — отслеживание использования и подозрительной активности
  4. Соответствие стандартам — PCI DSS, GDPR, SOX

Правильная реализация этих компонентов обеспечивает защиту данных на всех уровнях приложения.