Implementando Criptografia AES em Go

O AES (Advanced Encryption Standard) é um algoritmo de criptografia simétrica amplamente utilizado para proteger dados sensíveis. Em Go, o pacote crypto/aes fornece funcionalidades para essa criptografia, frequentemente em conjunto com o pacote crypto/cipher para modos de operação.

Geração de Chave

Para AES, a chave deve ter um comprimento específico: 16 bytes (para AES-128), 24 bytes (para AES-192) ou 32 bytes (para AES-256).


// Exemplo para AES-256
segredo := []byte("uma-chave-de-32-bytes-para-aes-256-aqui")

Criptografia com Modo CBC

O modo CBC (Cipher Block Chaining) requer que os dados sejam preenchidos para corresponder ao tamanho do bloco do AES (geralmente 16 bytes). Um Vetor de Inicialização (IV) aleatório é essecnial e deve ser único para cada operação de criptografia.


import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"bytes"
	"fmt"
	"io"
)

// pkcs7Padding adiciona preenchimento aos dados para que seu comprimento seja um múltiplo do tamanho do bloco.
func pkcs7Padding(data []byte, blockSize int) []byte {
	paddingCount := blockSize - (len(data) % blockSize)
	padding := bytes.Repeat([]byte{byte(paddingCount)}, paddingCount)
	return append(data, padding...)
}

// Criptografa dados usando AES em modo CBC.
func encryptAES_CBC(dadosClaros []byte, chave []byte) ([]byte, error) {
	bloco, err := aes.NewCipher(chave)
	if err != nil {
		return nil, fmt.Errorf("erro ao criar cifra AES: %w", err)
	}

	// Garante que os dados sejam preenchidos corretamente.
	dadosClaros = pkcs7Padding(dadosClaros, bloco.BlockSize())

	// Cria um buffer para o IV e os dados criptografados. O IV precede os dados criptografados.
	dadosCifrados := make([]byte, bloco.BlockSize()+len(dadosClaros))
	vetorInicializacao := dadosCifrados[:bloco.BlockSize()]

	// Gera um IV aleatório.
	if _, err := io.ReadFull(rand.Reader, vetorInicializacao); err != nil {
		return nil, fmt.Errorf("erro ao gerar vetor de inicialização: %w", err)
	}

	// Configura o modo de criptografia CBC.
	modoCBC := cipher.NewCBCEncrypter(bloco, vetorInicializacao)
	// Realiza a criptografia. Os dados criptografados são colocados após o IV.
	modoCBC.CryptBlocks(dadosCifrados[bloco.BlockSize():], dadosClaros)

	return dadosCifrados, nil
}

Descriptografia com Modo CBC

A descriptografia em modo CBC envolve extrair o IV, descriptografar os dados e, em seguida, remover o preenchimento.


// pkcs7Unpadding remove o preenchimento PKCS7 dos dados descriptografados.
func pkcs7Unpadding(data []byte) ([]byte, error) {
	length := len(data)
	if length == 0 {
		return nil, fmt.Errorf("dados vazios para remover preenchimento")
	}
	unpaddingCount := int(data[length-1])
	if unpaddingCount > length || unpaddingCount == 0 {
		return nil, fmt.Errorf("preenchimento inválido")
	}
	return data[:length-unpaddingCount], nil
}

// Descriptografa dados usando AES em modo CBC.
func decryptAES_CBC(dadosCifrados []byte, chave []byte) ([]byte, error) {
	bloco, err := aes.NewCipher(chave)
	if err != nil {
		return nil, fmt.Errorf("erro ao criar cifra AES: %w", err)
	}

	// Verifica se os dados cifrados são longos o suficiente para conter o IV.
	blockSize := bloco.BlockSize()
	if len(dadosCifrados) < blockSize {
		return nil, fmt.Errorf("dados cifrados muito curtos para o IV")
	}

	// Extrai o IV e os dados cifrados.
	vetorInicializacao := dadosCifrados[:blockSize]
	dadosCifrados = dadosCifrados[blockSize:]

	// Verifica se o comprimento dos dados cifrados é um múltiplo do tamanho do bloco.
	if len(dadosCifrados)%blockSize != 0 {
		return nil, fmt.Errorf("tamanho dos dados cifrados não é múltiplo do tamanho do bloco")
	}

	// Configura o modo de descriptografia CBC.
	modoCBC := cipher.NewCBCDecrypter(bloco, vetorInicializacao)
	// Realiza a descriptografia. Os dados descriptografados sobrescrevem os dados cifrados originais.
	modoCBC.CryptBlocks(dadosCifrados, dadosCifrados)

	// Remove o preenchimento.
	return pkcs7Unpadding(dadosCifrados)
}

Criptografia Autenticada com Modo GCM

O modo GCM (Galois/Counter Mode) oferece criptografia autenticada, o que significa que ele não apenas garante a confidencialidade, mas também a integridade e a autenticidade dos dados. Ele não requer preenchimento manual.


// encryptAES_GCM criptografa dados usando AES em modo GCM.
func encryptAES_GCM(dadosClaros []byte, chave []byte) ([]byte, error) {
	bloco, err := aes.NewCipher(chave)
	if err != nil {
		return nil, fmt.Errorf("erro ao criar cifra AES: %w", err)
	}

	gcm, err := cipher.NewGCM(bloco)
	if err != nil {
		return nil, fmt.Errorf("erro ao criar cifra GCM: %w", err)
	}

	// O Nonce (Number used once) deve ser único para cada operação. Seu tamanho é determinado pelo modo GCM.
	nonce := make([]byte, gcm.NonceSize())
	if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
		return nil, fmt.Errorf("erro ao gerar nonce: %w", err)
	}

	// Seal combina o nonce, os dados claros e opcionalmente dados autenticados adicionais (AAD).
	// O resultado é o nonce seguido pelos dados criptografados e o tag de autenticação.
	dadosCifrados := gcm.Seal(nonce, nonce, dadosClaros, nil)
	return dadosCifrados, nil
}

Descriptografia com Modo GCM

A descriptografia GCM recupera o nonce, descriptografa os dados e verifica a autenticidade.


// decryptAES_GCM descriptografa dados usando AES em modo GCM.
func decryptAES_GCM(dadosCifrados []byte, chave []byte) ([]byte, error) {
	bloco, err := aes.NewCipher(chave)
	if err != nil {
		return nil, fmt.Errorf("erro ao criar cifra AES: %w", err)
	}

	gcm, err := cipher.NewGCM(bloco)
	if err != nil {
		return nil, fmt.Errorf("erro ao criar cifra GCM: %w", err)
	}

	nonceSize := gcm.NonceSize()
	// Verifica se os dados cifrados são longos o suficiente para conter o nonce.
	if len(dadosCifrados) < nonceSize {
		return nil, fmt.Errorf("dados cifrados muito curtos para o nonce")
	}

	// Separa o nonce dos dados cifrados.
	nonce, dadosCifradosRestantes := dadosCifrados[:nonceSize], dadosCifrados[nonceSize:]

	// Open descriptografa os dados e verifica o tag de autenticação.
	// Se a autenticidade falhar, retorna um erro.
	dadosDecifrados, err := gcm.Open(nil, nonce, dadosCifradosRestantes, nil)
	if err != nil {
		return nil, fmt.Errorf("falha na descriptografia ou autenticação GCM: %w", err)
	}
	return dadosDecifrados, nil
}

Considerações Importantes

  • Gerenciamento de Chaves: Chaves de criptografia devem ser gerenciadas com segurança. Evite codificá-las diretamente no código-fonte.
  • IV/Nonce: É crucial gerar um IV (para CBC) ou Nonce (para GCM) aleatório e único para cada operação de criptografia. A reutilização compromete a segurança.
  • Modos de Operação: O modo GCM é geralmente preferível por oferecer criptografia autenticada, simplificando o processo e melhorando a segurança. O modo CBC exige tratamento explícito de preenchimneto e não fornece autenticidade nativamente.

Ao utilizar essas funções em Go, é possível implementar operações de criptografia e descriptografia AES de forma eficaz, escolhendo o modo mais adequado às necessidades de segurança e desempenho da sua aplicação.

Tags: go AES criptografia cbc gcm

Publicado em 7-12 21:00