Files
Go-VPN-Client/internal/security/encryption.go
arkonsadter b809e84220 feat: add security system with system-wide proxy, DNS protection and encryption
- System-wide proxy: automatic Windows proxy configuration for all apps
- DNS leak protection: force all DNS queries through VPN
- Config encryption: AES-256-GCM encryption for all config files
- File protection: strict access permissions for config directory
- Leak detection: built-in security check system
- Kill Switch: temporarily disabled (will be improved in next version)

Security features:
✓ Automatic system proxy setup
✓ DNS leak protection (optional)
✓ AES-256-GCM config encryption
✓ File and directory protection
✓ Security leak checker
⚠ Kill Switch disabled (caused internet blocking issues)

Emergency recovery scripts included:
- ОТКЛЮЧИТЬ_KILLSWITCH.bat
- EMERGENCY_FIX_INTERNET.bat
- ЕСЛИ_СЛОМАЛСЯ_ИНТЕРНЕТ.txt

Documentation:
- Markdown/SECURITY_GUIDE.md - full security guide
- БЕЗОПАСНОСТЬ_БЫСТРЫЙ_СТАРТ.md - quick start guide
- CHANGELOG_SECURITY.md - detailed changelog
2026-04-12 19:01:24 +06:00

213 lines
5.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package security
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"fmt"
"io"
"os"
"path/filepath"
"golang.org/x/crypto/pbkdf2"
)
const (
saltSize = 32
iterations = 100000
keySize = 32
)
// GetMachineKey генерирует ключ на основе уникальных характеристик машины
func GetMachineKey() ([]byte, error) {
// Используем hostname и другие системные параметры
hostname, err := os.Hostname()
if err != nil {
return nil, err
}
// Добавляем путь к исполняемому файлу для уникальности
exePath, err := os.Executable()
if err != nil {
return nil, err
}
// Комбинируем для создания уникального ключа
combined := hostname + exePath
hash := sha256.Sum256([]byte(combined))
return hash[:], nil
}
// Encrypt шифрует данные с использованием AES-256-GCM
func Encrypt(plaintext []byte, password string) (string, error) {
// Генерируем соль
salt := make([]byte, saltSize)
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
return "", err
}
// Получаем машинный ключ
machineKey, err := GetMachineKey()
if err != nil {
return "", err
}
// Комбинируем пароль с машинным ключом
combinedPassword := append([]byte(password), machineKey...)
// Генерируем ключ шифрования
key := pbkdf2.Key(combinedPassword, salt, iterations, keySize, sha256.New)
// Создаем AES cipher
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
// Используем GCM для аутентифицированного шифрования
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
// Генерируем nonce
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return "", err
}
// Шифруем
ciphertext := gcm.Seal(nonce, nonce, plaintext, nil)
// Комбинируем соль и зашифрованные данные
result := append(salt, ciphertext...)
return base64.StdEncoding.EncodeToString(result), nil
}
// Decrypt расшифровывает данные
func Decrypt(encryptedData string, password string) ([]byte, error) {
// Декодируем из base64
data, err := base64.StdEncoding.DecodeString(encryptedData)
if err != nil {
return nil, err
}
if len(data) < saltSize {
return nil, fmt.Errorf("неверный формат зашифрованных данных")
}
// Извлекаем соль
salt := data[:saltSize]
ciphertext := data[saltSize:]
// Получаем машинный ключ
machineKey, err := GetMachineKey()
if err != nil {
return nil, err
}
// Комбинируем пароль с машинным ключом
combinedPassword := append([]byte(password), machineKey...)
// Генерируем ключ
key := pbkdf2.Key(combinedPassword, salt, iterations, keySize, sha256.New)
// Создаем AES cipher
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
// Используем GCM
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonceSize := gcm.NonceSize()
if len(ciphertext) < nonceSize {
return nil, fmt.Errorf("неверный размер зашифрованных данных")
}
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
// Расшифровываем
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return nil, fmt.Errorf("ошибка расшифровки: неверный пароль или поврежденные данные")
}
return plaintext, nil
}
// SecureDelete безопасно удаляет файл (перезаписывает случайными данными)
func SecureDelete(filepath string) error {
// Получаем размер файла
info, err := os.Stat(filepath)
if err != nil {
return err
}
size := info.Size()
// Открываем файл для записи
file, err := os.OpenFile(filepath, os.O_WRONLY, 0)
if err != nil {
return err
}
defer file.Close()
// Перезаписываем случайными данными 3 раза
for i := 0; i < 3; i++ {
randomData := make([]byte, size)
if _, err := rand.Read(randomData); err != nil {
return err
}
if _, err := file.WriteAt(randomData, 0); err != nil {
return err
}
if err := file.Sync(); err != nil {
return err
}
}
// Удаляем файл
return os.Remove(filepath)
}
// SetFilePermissions устанавливает строгие права доступа к файлу
func SetFilePermissions(path string) error {
// Только владелец может читать и писать
return os.Chmod(path, 0600)
}
// ProtectDirectory защищает директорию и все файлы в ней
func ProtectDirectory(dirPath string) error {
// Устанавливаем права на директорию
if err := os.Chmod(dirPath, 0700); err != nil {
return err
}
// Защищаем все файлы в директории
entries, err := os.ReadDir(dirPath)
if err != nil {
return err
}
for _, entry := range entries {
if !entry.IsDir() {
filePath := filepath.Join(dirPath, entry.Name())
if err := SetFilePermissions(filePath); err != nil {
return err
}
}
}
return nil
}