El cifrado AES (Advanced Encryption Standard) es fundamental para proteger datos sensibles. Go ofrece herramientas robustas para esta tarea a través de sus paquetes crypto/aes y crypto/cipher.
Generación de Claves Seguras
Las claves para AES deben tener una longitud específica: 16 bytes para AES-128, 24 bytes para AES-192 o 32 bytes para AES-256. Es crucial generar claves aleatorias y mantener su confidencialidad.
import "crypto/aes"
// Ejemplo para una clave de 32 bytes (AES-256)
// Una clave real debería ser generada de forma segura, no hardcodeada.
claveSecreta := []byte("unaClaveMuySeguraDe32BytesParaAES256") // 32 bytes
_, err := aes.NewCipher(claveSecreta)
if err != nil {
// Manejar error
}
Cifrado con Modo CBC
El modo CBC (Cipher Block Chaining) requiere un vector de inicialización (IV) y un esquema de relleno para asegurar que los datos de entrada sean múltiplos del tamaño del bloque.
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"errors"
"fmt"
"io"
"bytes"
)
// paddingPKCS7 aplica el relleno PKCS7 a los datos.
func paddingPKCS7(data []byte, blockSize int) []byte {
paddingCount := blockSize - len(data)%blockSize
padding := bytes.Repeat([]byte{byte(paddingCount)}, paddingCount)
return append(data, padding...)
}
// unpaddingPKCS7 remueve el relleno PKCS7 de los datos descifrados.
func unpaddingPKCS7(data []byte) ([]byte, error) {
length := len(data)
if length == 0 {
return nil, errors.New("datos vacíos para quitar relleno")
}
paddingCount := int(data[length-1])
if paddingCount > length || paddingCount == 0 {
return nil, errors.New("relleno inválido")
}
return data[:length-paddingCount], nil
}
// cifrarCBC cifra datos usando AES en modo CBC.
func cifrarCBC(datosClaros []byte, clave []byte) ([]byte, error) {
bloque, err := aes.NewCipher(clave)
if err != nil {
return nil, fmt.Errorf("error al crear el cifrador de bloque: %w", err)
}
// Asegurar que la longitud de los datos sea múltiplo del tamaño del bloque
datosClarosRellenados := paddingPKCS7(datosClaros, bloque.BlockSize())
// Crear un buffer para el IV y el texto cifrado
tamanoIV := bloque.BlockSize()
textoCifradoCompleto := make([]byte, tamanoIV+len(datosClarosRellenados))
vectorInicializacion := textoCifradoCompleto[:tamanoIV]
// Generar un IV aleatorio
if _, err := io.ReadFull(rand.Reader, vectorInicializacion); err != nil {
return nil, fmt.Errorf("error al generar el vector de inicialización: %w", err)
}
// Crear el cifrador CBC y cifrar
modoCBC := cipher.NewCBCEncrypter(bloque, vectorInicializacion)
modoCBC.CryptBlocks(textoCifradoCompleto[tamanoIV:], datosClarosRellenados)
return textoCifradoCompleto, nil
}
// descifrarCBC descifra datos cifrados con AES en modo CBC.
func descifrarCBC(textoCifrado []byte, clave []byte) ([]byte, error) {
bloque, err := aes.NewCipher(clave)
if err != nil {
return nil, fmt.Errorf("error al crear el cifrador de bloque: %w", err)
}
tamanoIV := bloque.BlockSize()
if len(textoCifrado) < tamanoIV {
return nil, errors.New("texto cifrado demasiado corto para contener un IV")
}
// Extraer el IV y el texto cifrado real
vectorInicializacion := textoCifrado[:tamanoIV]
textoCifradoReal := textoCifrado[tamanoIV:]
// Asegurar que el texto cifrado tenga el tamaño correcto del bloque
if len(textoCifradoReal)%bloque.BlockSize() != 0 {
return nil, errors.New("texto cifrado tiene un tamaño incorrecto")
}
// Crear el descifrador CBC y descifrar
modoCBC := cipher.NewCBCDecrypter(bloque, vectorInicializacion)
modoCBC.CryptBlocks(textoCifradoReal, textoCifradoReal) // Descifra in-place
// Remover el relleno PKCS7
datosDescifrados, err := unpaddingPKCS7(textoCifradoReal)
if err != nil {
return nil, fmt.Errorf("error al quitar el relleno: %w", err)
}
return datosDescifrados, nil
}
Cifrado con Modo GCM (Recomendado)
El modo GCM (Galois/Counter Mode) ofrece cifrado autenticado, lo que significa que además de la confidencialidad, proporciona integridad y autenticidad a los datos. No requiere relleno manual.
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"errors"
"fmt"
"io"
)
// cifrarGCM cifra y autentica datos usando AES en modo GCM.
func cifrarGCM(datosClaros []byte, clave []byte) ([]byte, error) {
bloque, err := aes.NewCipher(clave)
if err != nil {
return nil, fmt.Errorf("error al crear el cifrador de bloque: %w", err)
}
gcm, err := cipher.NewGCM(bloque)
if err != nil {
return nil, fmt.Errorf("error al crear el cifrador GCM: %w", err)
}
// El Nonce debe ser único para cada operación de cifrado con la misma clave.
tamanoNonce := gcm.NonceSize()
nonce := make([]byte, tamanoNonce)
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, fmt.Errorf("error al generar el nonce: %w", err)
}
// Seal cifra los datos y anexa el nonce al principio del resultado.
// El último argumento (nil) es para datos autenticados adicionales (AAD),
// que no usamos en este ejemplo simple.
textoCifrado := gcm.Seal(nonce, nonce, datosClaros, nil)
return textoCifrado, nil
}
// descifrarGCM descifra y verifica la autenticidad de datos cifrados con GCM.
func descifrarGCM(textoCifrado []byte, clave []byte) ([]byte, error) {
bloque, err := aes.NewCipher(clave)
if err != nil {
return nil, fmt.Errorf("error al crear el cifrador de bloque: %w", err)
}
gcm, err := cipher.NewGCM(bloque)
if err != nil {
return nil, fmt.Errorf("error al crear el cifrador GCM: %w", err)
}
tamanoNonce := gcm.NonceSize()
if len(textoCifrado) < tamanoNonce {
return nil, errors.New("texto cifrado demasiado corto para contener un nonce")
}
// Separar el nonce del texto cifrado
nonce := textoCifrado[:tamanoNonce]
datosCifradosReales := textoCifrado[tamanoNonce:]
// Open descifra y verifica la autenticidad. Si falla la verificación, devuelve un error.
datosDescifrados, err := gcm.Open(nil, nonce, datosCifradosReales, nil)
if err != nil {
return nil, fmt.Errorf("error al descifrar o verificar datos GCM: %w", err)
}
return datosDescifrados, nil
}
Consideraciones Importantes
- Gestión de Claves: Nunca codifique las claves directamnete en el código fuente. Utilice métodos seguros para su almacenamiento y recuperación (variables de entorno, gestores de secretos).
- IVs y Nonces: Es vital generar un IV o Nonce aleatorio y único para cada operación de cifrado realizada con la misma clave. La reutilización compromete gravemente la seguridad.
- Modo de Operación: GCM es generalmente preferible por su seguridad y randimiento, ya que incluye autenticación y evita la necesidad de implementar rellenos manualmente.
Implementar cifrado AES correctamente en Go es esencial para la protección de datos. La elección entre CBC y GCM dependerá de los requisitos específicos de seguridad y rendimiento de su aplicación.