# Шифрование и управление ключами ## Навигация - [🏠 Главная страница курса](../../../README.md) - [📚 Модуль 6](../README.md) --- ## Типы шифрования ### 1. Symmetric Encryption (Симметричное шифрование) **Симметричное шифрование** использует один ключ для шифрования и расшифровки данных. **Преимущества:** - Быстрое выполнение - Подходит для больших объемов данных - Простота реализации **Недостатки:** - Проблема распределения ключей - Сложность масштабирования **Технический пример - Advanced Encryption Service:** ```java @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 { @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 примеры - Симметричное шифрование:** ```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> { let key = Key::from_slice(key); let cipher = Aes256Gcm::new(key); Ok(Self { cipher }) } pub fn encrypt(&self, plaintext: &str) -> Result> { 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> { 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 примеры - Симметричное шифрование:** ```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 примеры - Симметричное шифрование:** ```typescript 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 (Асимметричное шифрование) **Асимметричное шифрование** использует пару ключей: публичный для шифрования и приватный для расшифровки. **Преимущества:** - Безопасное распределение ключей - Цифровые подписи - Масштабируемость **Недостатки:** - Медленнее симметричного шифрования - Сложность реализации ```java @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 (Хеширование) **Хеширование** — одностороннее преобразование данных в фиксированную длину. **Применения:** - Хранение паролей - Проверка целостности данных - Цифровые подписи ```java @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 (Ротация ключей) **Ротация ключей** — регулярная смена ключей шифрования для повышения безопасности. ```java @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 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** — аппаратный модуль безопасности для безопасного хранения и управления ключами. ```java @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 **Интеграция с хранилищем ключей** для централизованного управления ключами. ```java @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 (Хранение ключей) **Принципы безопасного хранения ключей:** - Никогда не храните ключи в коде - Используйте переменные окружения - Шифруйте ключи при хранении - Разделяйте ключи по окружениям ```java @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 (Контроль доступа к ключам) ```java @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 (Мониторинг ключей) ```java @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 Правильная реализация этих компонентов обеспечивает защиту данных на всех уровнях приложения.