Do tema da revista à prática do projeto
Páginas de serviços e técnicas correspondentes ao artigo
Na prática, em criptação AES Delphi raramente o problema é a “AES em si”, e sim as condições periféricas: os dados precisam ser processados como fluxo (arquivos, BLOBs, backups), formatos antigos devem permanecer legíveis e, em operação, é necessária capacidade de depuração (cabeçalhos, versionamento) e padrões seguros (Salt/IV aleatórios, sem reutilização). Este trecho de código-fonte mostra, portanto, não só «criptografar/descriptografar», mas um pequeno formato robusto com cabeçalho, versão, Salt e IV – além de PBKDF2 para a derivação de chave e um ponto onde a integridade pode ser adicionada de forma adequada.
Por que “criptografar uma string AES” quase nunca basta
Em software empresarial personalizado, a criptografia costuma aparecer tipicamente em três lugares: (1) configuração/secrets (por ex. credenciais), (2) ficheiros de intercâmbio/exportação e (3) dados em repouso (por ex. arquivos, contentores de documentos). A abordagem ingênua “senha → chave AES → string para dentro/fora” falha rapidamente:
- Reutilização de IV: Em modos como CBC ou GCM, um vetor de inicialização (IV) deve ser único por cifra. Um IV constante é um vazamento, mesmo que a senha seja forte.
- Chave derivada da senha sem KDF: Usar a senha diretamente como chave (ou apenas hasheá‑la uma vez) abre espaço para ataques offline. Uma KDF (Key Derivation Function) como PBKDF2 desacelera o atacante de forma direcionada.
- Sem versão de formato: Sem cabeçalho/versionamento será difícil alterar depois o número de iterações, algoritmo ou parâmetros sem deixar dados antigos órfãos.
- Sem integridade: AES‑CBC cifra, mas não previne manipulação. Sem autenticação (por ex. HMAC ou AEAD como GCM) você fica sujeito a ataques de bitflipping/padding e a erros de diagnóstico difíceis de resolver.
O ponto central deste artigo: um pequeno formato de contentor que suporta streaming, é versionável e evita os erros comuns.
Criptografia AES Delphi com cabeçalho, Salt, IV e PBKDF2
Definimos um formato de contentor simples que também pode ser usado em BLOBs de base de dados ou payloads de mensagens:
- Magic: 4 bytes, p.ex.
NBAE(verificação rápida “É o nosso formato?”) - Versão: 1 byte (permite migração)
- Parâmetros KDF: número de iterações (4 bytes)
- Salt: 16 bytes (aleatório por arquivo)
- IV: 16 bytes (aleatório por arquivo para AES‑CBC)
- Ciphertext: dados cifrados (compatível com streaming)
Importante: Salt e IV não são secretos. Eles apenas precisam ser novos para cada cifragem. A senha permanece confidencial; a chave derivada não é armazenada.
Criptografia AES Delphi em fluxo: escrever/ler o contentor
O código está propositadamente escrito como um “plano”: funções claramente separadas, cabeçalhos verificáveis, sem variáveis globais ocultas. Para AES e PBKDF2 muitas equipas usam uma biblioteca de cripto consolidada (p. ex. DEC). O trecho mostra o formato e o padrão de streaming; as chamadas AES/PBKDF2 são encapsuláveis de forma a poderem ser substituídas conforme a biblioteca utilizada.
unit Nb.AesContainer;
interface
uses
System.SysUtils, System.Classes, System.NetEncoding;
type
ENbCryptoError = class(Exception);
TNbAesContainer = class
public
class procedure EncryptStreamToStream(const AIn: TStream; const AOut: TStream;
const APassword: string; const AIterations: Cardinal = 200000);
class procedure DecryptStreamToStream(const AIn: TStream; const AOut: TStream;
const APassword: string);
class function EncryptBytesToBase64(const APlain: TBytes; const APassword: string): string;
class function DecryptBase64ToBytes(const ACipherB64: string; const APassword: string): TBytes;
end;
implementation
const
CMagic: array[0..3] of AnsiChar = ('N','B','A','E');
CVersion: Byte = 1;
CSaltLen = 16;
CIvLen = 16;
type
TNbHeaderV1 = packed record
Magic: array[0..3] of AnsiChar;
Version: Byte;
Iterations: Cardinal; // little endian
Salt: array[0..CSaltLen-1] of Byte;
IV: array[0..CIvLen-1] of Byte;
end;
// --- Abhängigkeiten, die Sie je nach Crypto-Stack implementieren ---
procedure FillRandomBytes(var B: TBytes);
begin
// Para entropia criptográfica: use um CSPRNG do SO (Windows BCryptGenRandom,
// Linux getrandom/urandom). Aqui propositalmente como espaço reservado.
raise ENbCryptoError.Create('FillRandomBytes: CSPRNG não integrado');
end;
function PBKDF2_HMAC_SHA256(const APassword: string; const ASalt: TBytes;
const AIterations, AKeyLen: Cardinal): TBytes;
begin
// Implementação, p.ex., com DEC (PBKDF2) ou outra biblioteca.
// Resultado: AKeyLen Bytes.
raise ENbCryptoError.Create('PBKDF2_HMAC_SHA256: não integrado');
end;
procedure AES256_CBC_EncryptStream(const AKey, AIV: TBytes; const AIn, AOut: TStream);
begin
// Implementação via biblioteca:
// - KeyLen = 32 Bytes
// - IVLen = 16 Bytes
// - PKCS#7 Padding
// Importante: processar orientado a stream, não carregar tudo na memória.
raise ENbCryptoError.Create('AES256_CBC_EncryptStream: não integrado');
end;
procedure AES256_CBC_DecryptStream(const AKey, AIV: TBytes; const AIn, AOut: TStream);
begin
raise ENbCryptoError.Create('AES256_CBC_DecryptStream: não integrado');
end;
// --- Helper ---
procedure WriteHeaderV1(const AOut: TStream; const H: TNbHeaderV1);
begin
if AOut.Write(H, SizeOf(H)) <> SizeOf(H) then
raise ENbCryptoError.Create('Cabeçalho não pôde ser escrito');
end;
function ReadHeaderV1(const AIn: TStream): TNbHeaderV1;
var
H: TNbHeaderV1;
begin
if AIn.Read(H, SizeOf(H)) <> SizeOf(H) then
raise ENbCryptoError.Create('Cabeçalho incompleto');
if (H.Magic[0] <> CMagic[0]) or (H.Magic[1] <> CMagic[1]) or
(H.Magic[2] <> CMagic[2]) or (H.Magic[3] <> CMagic[3]) then
raise ENbCryptoError.Create('Container inválido (Magic não confere)');
if H.Version <> CVersion then
raise ENbCryptoError.CreateFmt('Versão de container desconhecida: %d', [H.Version]);
if (H.Iterations < 10000) or (H.Iterations > 5000000) then
raise ENbCryptoError.Create('Número de iterações fora de limites plausíveis');
Result := H;
end;
class procedure TNbAesContainer.EncryptStreamToStream(const AIn, AOut: TStream;
const APassword: string; const AIterations: Cardinal);
var
H: TNbHeaderV1;
Salt, IV, Key: TBytes;
begin
if APassword = '' then
raise ENbCryptoError.Create('A senha não pode estar vazia');
// Salt/IV erzeugen
SetLength(Salt, CSaltLen);
SetLength(IV, CIvLen);
FillRandomBytes(Salt);
FillRandomBytes(IV);
// Header befüllen
Move(CMagic[0], H.Magic[0], Length(CMagic));
H.Version := CVersion;
H.Iterations := AIterations;
Move(Salt[0], H.Salt[0], CSaltLen);
Move(IV[0], H.IV[0], CIvLen);
WriteHeaderV1(AOut, H);
// Key ableiten (32 Bytes für AES-256)
Key := PBKDF2_HMAC_SHA256(APassword, Salt, AIterations, 32);
// Nutzdaten verschlüsseln (Ciphertext folgt direkt nach Header)
AES256_CBC_EncryptStream(Key, IV, AIn, AOut);
end;
class procedure TNbAesContainer.DecryptStreamToStream(const AIn, AOut: TStream;
const APassword: string);
var
H: TNbHeaderV1;
Salt, IV, Key: TBytes;
begin
if APassword = '' then
raise ENbCryptoError.Create('A senha não pode estar vazia');
H := ReadHeaderV1(AIn);
SetLength(Salt, CSaltLen);
SetLength(IV, CIvLen);
Move(H.Salt[0], Salt[0], CSaltLen);
Move(H.IV[0], IV[0], CIvLen);
Key := PBKDF2_HMAC_SHA256(APassword, Salt, H.Iterations, 32);
// Entschlüsseln ab aktueller Stream-Position (nach Header)
AES256_CBC_DecryptStream(Key, IV, AIn, AOut);
end;
class function TNbAesContainer.EncryptBytesToBase64(const APlain: TBytes;
const APassword: string): string;
var
InS, OutS: TBytesStream;
begin
InS := TBytesStream.Create(APlain);
try
OutS := TBytesStream.Create;
try
EncryptStreamToStream(InS, OutS, APassword);
Result := TNetEncoding.Base64.EncodeBytesToString(OutS.Bytes, 0, OutS.Size);
finally
OutS.Free;
end;
finally
InS.Free;
end;
end;
class function TNbAesContainer.DecryptBase64ToBytes(const ACipherB64,
APassword: string): TBytes;
var
Cipher: TBytes;
InS, OutS: TBytesStream;
begin
Cipher := TNetEncoding.Base64.DecodeStringToBytes(ACipherB64);
InS := TBytesStream.Create(Cipher);
try
OutS := TBytesStream.Create;
try
DecryptStreamToStream(InS, OutS, APassword);
Result := OutS.Bytes;
SetLength(Result, OutS.Size);
finally
OutS.Free;
end;
finally
InS.Free;
end;
end;
end.Propósito: Um contêiner mínimo adequado para ficheiros e BLOBs, incluindo versionamento e parâmetros KDF. Restrições: Deve ligar-se a um CSPRNG real (aleatoriedade criptograficamente segura do sistema operativo) e usar por baixo uma implementação robusta de AES/PBKDF2. Armadilhas: Não use um gerador “qualquer” de random (não Random()), não utilize IVs fixos, e planeje um tratamento de erros distinto no Decrypt (senha errada vs. dados corrompidos). Variantes: em vez de CBC prefira AEAD (ver abaixo), ou expanda o header com ID do algoritmo e HMAC.
Integridade: por que AES-CBC sozinho é arriscado em operação
AES-CBC ainda está presente em muitos contextos legacy e pode funcionar, se você usar adicionalmente uma garantia de integridade. Sem integridade, um atacante pode manipular o ciphertext; mesmo sem um atacante ativo, erros de transmissão ou camadas de armazenamento defeituosas geram erros de „padding“ difíceis de diagnosticar.
Opções pragmáticas:
- Encrypt-then-HMAC: Após o ciphertext escreva um HMAC (p.ex. HMAC-SHA-256) sobre Header+Ciphertext. Na leitura, valide primeiro o HMAC e só então descriptografe. Para isso, derive idealmente dois keys a partir de PBKDF2 (p.ex. 64 bytes: 32 para AES, 32 para HMAC), em vez de reutilizar a mesma chave para ambos.
- AES-GCM: modo AEAD (Authenticated Encryption with Associated Data). Fornece ciphertext + auth-tag. Hoje é frequentemente a escolha mais limpa, se a sua biblioteca Delphi suportar GCM de forma estável. Campos do header podem ser autenticados como „AAD“ sem que você precise criptografá-los.
Se tiver de permanecer com CBC (p.ex. por interoperabilidade), Encrypt-then-HMAC é a complementação robusta. Para formatos novos vale a pena usar GCM, porque já incorpora autenticação e torna os padrões de erro mais claros.
Particularmente importante: „Aleatoriedade criptográfica“ e por que System.Hash não basta
Um reflexo legacy comum em projetos Delphi: „vamos só fazer SHA256 sobre timestamp + qualquer coisa e já temos Random.“ Isso não é uma base confiável. Para salt e IV você precisa de um CSPRNG (Cryptographically Secure Pseudo Random Number Generator) do sistema operativo. Em Windows isso é tipicamente a API BCrypt (CNG), em Linux um gerador do kernel como getrandom() ou /dev/urandom. A diferença prática: um CSPRNG é projetado para que, a partir de valores observados, não seja possível prever valores futuros.
Truque de arquitetura: encapsule isso numa pequena unit „RandomProvider“ que você possa mockar em testes. Assim resolve dois casos: testes reproduzíveis (com seed fixo no mock) e segurança real em produção (com OS-CSPRNG). Isso evita que num hotfix se introduza novamente Random() por ser mais rápido.
Depuração e migração legacy: versionamento não é luxo
O header não serve só para „beleza criptográfica“, mas para manutenibilidade:
- Ajuste de iterações: os counts de iteração do PBKDF2 mudam ao longo dos anos. Com um campo no header você pode aumentar mais tarde sem tornar dados antigos ilegíveis.
- Mudança de formato: a Versão 2 pode, por exemplo, trocar para AES-GCM ou acrescentar um HMAC.
- Diagnóstico em campo: Magic/Version permitem checagens rápidas em logs e ferramentas sem precisar descriptografar os dados.
Dica prática: Implemente um pequeno „Inspector“, que leia apenas o cabeçalho (Magic/Version/Iterations) e o registre em um log. Assim você resolve muitos casos de suporte („Qual versão está aqui?“) sem lidar com senhas.
Migrar de forma limpa: „Read old, write new“ em vez de Big Bang
Ao substituir um formato antigo (p.ex. IV fixo, sem KDF, Blowfish/3DES, ou XOR caseiro), em projetos Delphi mostrou-se um padrão eficaz: na leitura você reconhece múltiplos formatos (Magic/Version ou heurística de fallback), na escrita você passa a gerar somente o novo formato. Adicionalmente, ao descriptografar com sucesso, pode re-encryptar em segundo plano („lazy migration“), se isso se encaixar no processo. Assim você reduz o risco no rollout e evita “criptografar tudo de novo de uma vez” como janela de manutenção.
Threading und Streaming: typische Kanten in Delphi
A criptografia frequentemente roda em worker-threads (p.ex. na exportação, no upload para um portal do cliente, na escrita de grandes arquivos). Dois pontos que aparecem regularmente em projetos Delphi:
- Posições do stream: Antes de criptografar/descriptografar, contratos claros: o Input-Stream é lido a partir da posição atual, o Output-Stream é escrito a partir da posição atual. Ao reutilizar streams, certifique-se de definir conscientemente
Position := 0. - Picos de memória: Evite „tudo em TBytes“. A abordagem por streams é especialmente importante para arquivos grandes. Se sua biblioteca de crypto aceitar apenas arrays de bytes, vale a pena o trabalho adicional para migrar para uma implementação compatível com streams ou construir um adaptador com buffer.
Ao criptografar em serviços (Windows- ou Linux-Services), pRESTe também atenção a um logging de exceções claro: „senha errada“, „cabeçalho corrompido“, „Tag/HMAC inválido“ são casos operacionais distintos e devem ser distinguíveis. Importante: mensagens de erro não devem ser detalhadas demais externamente (nada como „Padding errado no bloco 7“ como erro de API), internamente no log porém podem ser detalhadas.
Quando a abordagem compensa — e onde pode falhar
Compensa, se você: (a) armazena dados de exportação/importação criptografados por longos períodos, (b) opera diferentes versões do programa em paralelo, (c) processa dados como streams, ou (d) precisa de uma interface de cripto limpa para múltiplos módulos (Client/Server/Tooling).
Falha, quando você tenta resolver „tudo“ com isso: para transporte é responsabilidade do TLS, não de um wrapper AES caseiro. Para segredos (senhas, tokens) muitas vezes um OS-Secret-Store ou um Vault é mais adequado. E se precisar de interoperabilidade com outras linguagens, documente com precisão header, endianness e encoding (ou utilize um formato estabelecido).
Conclusão: AES in Delphi é menos algoritmo, mais engenharia
O ganho real deste trecho não é „AES läuft“, mas sim um formato operacional: salt e IV aleatórios, header versionado, parâmetros PBKDF2 no payload e processamento compatível com streams. Para novos formatos, complemente preferencialmente com integridade (AES-GCM ou Encrypt-then-HMAC). Assim, de „nós criptografamos qualquer coisa“ surge um componente que, em soluções empresariais digitais, continua mantível e migrável mesmo após anos.
Se precisar integrar esse tipo de container em um ambiente Delphi consolidado ou migrar de um formato legado de forma limpa, vale a pena realizar uma verificação rápida de arquitetura (gestão de chaves, versões de formato, operação/Logging). Esclarecemos os detalhes, se necessário, em conversa:
No contexto técnico, Delphi Aes e Pbkdf2 Delphi também desempenham um papel importante quando integrações, fluxos de dados e evolução precisam funcionar de forma coordenada.
Discutir projeto ou iniciativa de modernização com Net-Base.
Próximo passo
Quando um tema se torna um projeto real, arquitetura, sistemas existentes e operação devem ser considerados em conjunto desde o início.
Não apenas apoiamos questões pontuais, mas também quando fragmentos de código-fonte, temas legados ou ideias de portais precisam evoluir para um projeto empresarial robusto.
- Estado atual, estado-alvo e riscos técnicos são avaliados em conjunto.
- REST, o acesso a dados, os portais e o Rollout não são adiados para uma fase posterior.
- Você vê cedo qual caminho é economicamente e operacionalmente viável.