- 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
213 lines
5.4 KiB
Go
213 lines
5.4 KiB
Go
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
|
||
}
|