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.