Files

172 lines
5.0 KiB
Go
Raw Permalink 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 crypto
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"fmt"
"io"
)
type Encryptor struct {
key []byte // 32 bytes for AES-256
}
// NewEncryptor создает новый шифровальщик с заданным ключом
// key должен быть 32 байта для AES-256
func NewEncryptor(key []byte) (*Encryptor, error) {
if len(key) != 32 {
return nil, fmt.Errorf("encryption key must be 32 bytes, got %d", len(key))
}
return &Encryptor{
key: key,
}, nil
}
// Encrypt шифрует plaintext с использованием AES-GCM
// Возвращает base64 encoded ciphertext с appended nonce
func (e *Encryptor) Encrypt(plaintext []byte) (string, error) {
if len(plaintext) == 0 {
return "", nil
}
// Создаем блок AES
block, err := aes.NewCipher(e.key)
if err != nil {
return "", fmt.Errorf("failed to create cipher: %w", err)
}
// Создаем GCM режим
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", fmt.Errorf("failed to create GCM: %w", err)
}
// Генерируем случайный nonce
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return "", fmt.Errorf("failed to generate nonce: %w", err)
}
// Шифруем: ciphertext = nonce + encrypted_data
ciphertext := gcm.Seal(nonce, nonce, plaintext, nil)
// Кодируем в base64 для хранения в БД
encoded := base64.StdEncoding.EncodeToString(ciphertext)
return encoded, nil
}
// Decrypt расшифровывает base64 encoded ciphertext
// Ожидает формат: nonce + encrypted_data
func (e *Encryptor) Decrypt(encodedCiphertext string) ([]byte, error) {
if encodedCiphertext == "" {
return []byte{}, nil
}
// Декодируем из base64
ciphertext, err := base64.StdEncoding.DecodeString(encodedCiphertext)
if err != nil {
return nil, fmt.Errorf("failed to decode base64: %w", err)
}
if len(ciphertext) == 0 {
return []byte{}, nil
}
// Создаем блок AES
block, err := aes.NewCipher(e.key)
if err != nil {
return nil, fmt.Errorf("failed to create cipher: %w", err)
}
// Создаем GCM режим
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, fmt.Errorf("failed to create GCM: %w", err)
}
nonceSize := gcm.NonceSize()
if len(ciphertext) < nonceSize {
return nil, fmt.Errorf("ciphertext too short")
}
// Извлекаем nonce и зашифрованные данные
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
// Расшифровываем
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return nil, fmt.Errorf("failed to decrypt: %w", err)
}
return plaintext, nil
}
// EncryptString удобная обертка для шифрования строк
func (e *Encryptor) EncryptString(plaintext string) (string, error) {
return e.Encrypt([]byte(plaintext))
}
// DecryptString удобная обертка для расшифровки в строку
func (e *Encryptor) DecryptString(encodedCiphertext string) (string, error) {
plaintext, err := e.Decrypt(encodedCiphertext)
if err != nil {
return "", err
}
return string(plaintext), nil
}
// MustEncrypt паникует при ошибке шифрования (для инициализации)
func (e *Encryptor) MustEncrypt(plaintext string) string {
encrypted, err := e.EncryptString(plaintext)
if err != nil {
panic(fmt.Sprintf("encryption failed: %v", err))
}
return encrypted
}
// MustDecrypt паникует при ошибке расшифровки (для инициализации)
func (e *Encryptor) MustDecrypt(encodedCiphertext string) string {
decrypted, err := e.DecryptString(encodedCiphertext)
if err != nil {
panic(fmt.Sprintf("decryption failed: %v", err))
}
return decrypted
}
// GenerateRandomKey генерирует случайный 32-байтный ключ для AES-256
func GenerateRandomKey() ([]byte, error) {
key := make([]byte, 32)
if _, err := io.ReadFull(rand.Reader, key); err != nil {
return nil, fmt.Errorf("failed to generate random key: %w", err)
}
return key, nil
}
// GenerateRandomKeyString генерирует случайный ключ в виде hex строки
func GenerateRandomKeyString() (string, error) {
key, err := GenerateRandomKey()
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(key), nil
}
// KeyFromString создает ключ из строки (ожидается 32 байта в base64 или plain)
func KeyFromString(keyStr string) ([]byte, error) {
// Пробуем декодировать как base64
key, err := base64.StdEncoding.DecodeString(keyStr)
if err == nil && len(key) == 32 {
return key, nil
}
// Если не base64, используем как есть (должно быть 32 байта)
if len(keyStr) == 32 {
return []byte(keyStr), nil
}
return nil, fmt.Errorf("invalid key length: expected 32 bytes, got %d", len(keyStr))
}