Implementación de Cifrado AES en Go

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.

Etiquetas: golang AES cifrado cbc gcm

Publicado el 7-19 21:20