Anak Krakatoa Defined Crypt
Escrito por Carlos B. Feitoza Filho | |
Categoria: Componentes | |
Categoria Pai: Delphi | |
Acessos: 6562 |
História (22/11/2004)
Um dia precisei de um algoritmo simples de criptografia para usar em um de meus programas, com o intuito de encriptar strings de texto. Como não tive paciência para criar um, resolvi procurar na Internet. Achei o que eu estava procurando, mas ocorreu uma coisa muito inesperada... O algoritmo era genérico, ou seja, ele manipulava a sequência de texto informada e fazia um cálculo simples, alterando os caracteres um a um. Isso seria o ideal, mas infelizmente ele utilizava todos os 256 caracteres do conjunto ASCII, quer dizer, ele também incluía em seus cálculos todos os caracteres de controle (DEL, CR, LF, ACK, etc.). Descobri isso quando encriptava uma string que seria salva em um INI. Quando eu a desencriptava ela sempre vinha pela metade e dava um puta erro! Foi então que eu percebi o que estava acontecendo: A String que eu estava processando gerava após ser encriptada um caractere especial (CR). Como nos INIs se salvam uma linha de cada vez, eu só estava salvando a string até o CR e o que vinha após ele era sumariamente ignorado. Foi então que eu lembrei do JavaScript! Em JS existem muitos exemplos pra fazer criptografia de strings e, além disso, como o JS não é uma linguagem compilada seria muito mais fácil encontrar o que eu procurava.
Este componente, portanto, foi INTEIRAMENTE convertido e otimizado a partir do Script VIRGENERE ENCRYPTION criado por Francisco Ryan Tolmasky (Este endereço de email está sendo protegido de spambots. Você precisa do JavaScript ativado para vê-lo.). Esse Script usa uma tabela fixa (Array de Strings) contendo pares de caracteres comuns em meu idioma, nunca saindo desse intervalo, ou seja, as funções só usam esses caracteres para criptografar ficando de fora caracteres indesejados! O objetivo do Anak Krakatoa Defined Crypt é portanto codificar um texto usando sua própria tabela interna de pares de caracteres. Isso evita o aparecimento de caracteres indesejados no texto codificado, porém PODE TORNAR O PROCESSO DE (DES)CRIPTOGRAFIA MUITO LENTO mesmo em computadores rápidos, mas nada que impeça o uso do componente.
Outras característica adicionada por mim foram a limitação de caracteres utilizados e a possibilidade de gerar uma criptografia assimétrica. A primeira característica utiliza 3 tabelas de criptografia diferentes, uma completa (256 caracteres), uma sem os caracteres de controle (244 caracteres) e uma "segura" com apenas os 96 caracteres básicos da tabela ASCII. Já a criptografia assimétrica gera senhas aleatórias diferentes para criptografia e descriptografia, o que torna o conteúdo cifrado mais seguro para ser transferido em meios eletrônicos.
Métodos de TKRKDefinedCrypt
- function SingleEncryptDecrypt(APassword: String; ASubject: TSubject; AFunction: TFunction): TSubject;
Esta função encripta/decripta um array de bytes mediante o uso de uma senha e devolve um outro array de bytes com o resultado da (des)criptografia.
- APassword: A senha pretendida;
- ASubject: Um TSubject (array of bytes) que será (des)criptografado. Existem funções específicas para converter uma string comum em TSubject e de TSubject em string (veja mais adiante);
- AFunction: Função que será utilizada. Este parâmetro pode receber dois valores possíveis: fEncrypt e fDecrypt.
Exemplo (KRDC é o nome do componente TKRKDefinedCrypt):var Entrada: TSubject; Saida: TSubject; begin Entrada := KRDC.StringToSubject('Texto a ser encriptado'); // Criptografando Saida := KRDC.SingleEncryptDecrypt('A senha!',Entrada,fEncrypt); // Neste ponto "Saida" contém um array de bytes que corresponde a // "Entrada" criptografada. Abaixo vamos usar "Saida" como "Entrada" // a fim de descriptografar Entrada := Saida; // Descriptografando Saida := KRDC.SingleEncryptDecrypt('A senha!',Entrada,fDecrypt); // Neste ponto "Saida" contém um array de bytes que corresponde a // "Entrada" descriptografada. end;
- function PublicKeyEncrypt(APublicKey: String; ASubject: TSubject): TSubject;
function PrivateKeyDecrypt(APrivateKey: String; ASubject: TSubject): TSubject;
Estas funções encriptam/decriptam um array de bytes segundo uma chave pública ou privada (dependendo da função). Este tipo de criptografia é mais seguro do que SingleEncryptDecrypt pois usa um sistema assimétrico, ou seja, aquele onde existe uma chave que encripta os dados (PublicKey) e outra diferente para desencriptá-los (PrivateKey)
- APublicKey: A chave pública representada por um hash SHA512. Esta chave é gerada juntamente com a chave privada por uma função especializada (GenerateKeyPair), a qual será explicada posteriormente;
- APrivateKey: A chave privada representada por um hash SHA512. Esta chave é gerada juntamente com a chave pública por uma função especializada (GenerateKeyPair), a qual será explicada posteriormente;
- ASubject: Um TSubject (array of bytes) que será criptografado/descriptografado. Existem funções específicas para converter uma string comum em TSubject e de TSubject em string (veja mais adiante);
Exemplo (KRDC é o nome do componente TKRKDefinedCrypt):
var Entrada: TSubject; Saida: TSubject; KeyPair: TKeyPair; begin Entrada := KRDC.StringToSubject('Texto a ser encriptado'); // Gerando um par de chaves. É importante guardar a chave privada, // pois apenas ela poderá ser usada para descriptografar KeyPair := KRDC.GenerateKeyPair; // Criptografando Saida := KRDC.PublicKeyEncrypt(KeyPair.PublicKey,Entrada); // "Saida" contém "Entrada" criptografada Entrada := Saida; // Descriptografando Saida := KRDC.PrivateKeyDecrypt(KeyPair.PrivateKey,Entrada); // Saida contém "Entrada" descriptografada end.
- function GenerateKeyPair: TKeyPair;
Esta função gera um par de chaves (uma pública e outra privada) únicas, que podem ser usadas com as funções PrivateKeyDecrypt e PublicKeyEncrypt. A função retorna um record do tipo TKeyPair com dois membros, PrivateKey e PublicKey, ambos String. A função gera dois hashes SHA512 diferentes a cada execução. É importante manter o par de chaves gerado em local seguro. O procedimento correto seria entregar a chave privada ao destinatário que fará a descriptografia e manter com o remetente apenas a chave pública. O meio de entrega da chave privada, claro, precisa ser seguro. Se for garantido que apenas o destinatário possui a chave privada, apenas ele poderá descriptografar algo que foi criptografado com a chave pública, no entanto, qualquer pessoa que possua a chave pública poderá gerar conteúdo "legível" apenas pelo detentor da chave privada. - function SubjectToString(const AResult: TSubject): String;
function StringToSubject(const AString: String): TSubject;
Estas funções convertem um TSubject em String e vice-versa. As funções do Defined Crypt utilizam arrays de bytes porque a criptografia atua sobre bytes e não sobre caracteres e isso foi feito com o intuito de evitar confusão ao se criptografar strings Unicode ou ANSI. Strings unicode possuem até dois bytes por caractere enquanto strings ANSI possuem apenas um byte por caractere. Usando arrays de bytes fica mais explícito o que está sendo criptografado ou descriptografado porque o que será visto pelas funções serão os bytes da string e não seus caracteres. A conversão de string em TSubject (e vice-versa) é sempre feita byte a byte, de forma que a construção, tanto de TSubject como de uma string a partir de um TSubject gerem resultados coerentes, mesmo quando um executável é compilado em um Delphi ANSI ou em um Delphi Unicode. - procedure SaveToFile(AFileName: TFileName; ASubject: TSubject);
function LoadFromFile(AFileName: TFileName): TSubject;
Estas funções foram criadas para facilitar o salvamento e a carga de conteúdo criptografado. Foi necessário manter a mesma estrutura de dados ao salvar conteúdo criptografado porque muitas vezes este conteúdo gerava caracteres que eram afetados ao se carregar um arquivo de forma tradicional. O sistema interno de salvamento e carga de arquivos do Defined Crypt salva um TSubject exatamente da forma em que ele se encontra e carrega byte a byte novamente quando solicitado!
Eventos de TKRKDefinedCrypt
- property OnUpdateProgress: TUpdateProgress read FUpdateProgress write FUpdateProgress;
TUpdateProgress = procedure (ASender: TObject; APosition: Integer) of object;
Retorna a cada (des)encriptação de um único byte o número deste byte dentro de TSubject. O primeiro byte é 1, o segundo é 2 e assim por diante até que APosition se torne igual ao total de byes em TSubject sendo (des)encriptado, indicando o fim do processo de (des)encriptação. O objetivo desse evento é retornar um valor que aumenta a media que o processo está sendo executado, para ser usado em um retorno visual como em barras de progresso. ATENÇÃO! O uso deste evento aumenta consideravelmente o tempo de encriptação/decriptação. Seu uso só é recomendável em conteúdos pequenos e quando o tempo de operação não é um fator crítico.
Propriedades de TKRKDefinedCrypt
- property CryptTable: TCryptTable read FCryptTable write FCryptTable default ctFull;
A propriedade CrypTable, informa ao algoritmo do Defined Crypt quais os bytes ele deverá usar para realizar a criptografia/descriptografia. Essa propriedade é ctFull por padrão e neste caso, TODOS OS BYTES da tabela ASCII (256) serão usados na criptografia de textos. Esse método é preferível para a encriptação de textos que serão salvos em arquivos ou em um banco de dados e é terminantemente desaconselhada quando o resultado de uma criptografia deverá ser exibido em componentes visuais como Memos. Isto porque muitas vezes o processo de criptografia nesse modo gera caracteres de controle que os componentes de exibição de texto interpretam erroneamente alterando o texto criptografado.
Quando CryptTable tem o valor ctNoControl, nenhum caractere de controle será utilizado, logo o texto resultante de uma criptografia pode ser exibido em componentes visuais ou mesmo enviado via e-mail para uma posterior descriptografia. Esta tabela possui 224 bytes para realizar a criptografia.
Mesmo usando CryptTable = ctNoControl pode haver problemas de exibição e se este for o caso, é possível usar o valor ctSafe, que vai forçar o algoritmo de criptografia a usar apenas os 96 caracteres mais básicos da tabela ASCII.
O CryptTable Maker
O Anak Krakatoa DefinedCrypt utiliza tabelas de caracteres ASCII fixos que são usados na criptografia. Os caracteres ASCII são incluídos aos pares na sua representação hexadecimal como elementos dos vetores "CryptTableFull", "CryptTableNoControl" e "CryptTableSafe" dentro de CryptTable.inc.
Todos os 256 caracteres ASCII são usados pelo vetor "CryptTableFull". Já o vetor "CryptTableNoControl" não utiliza os caracteres de controle ASCII (0 a 31) enquanto o vetor "CryptTableSafe" possui apenas os caracteres básicos do conjunto ASCII sem os caracteres de controle (0 a 31).
O Crypt Table Maker tem por finalidade gerar cada um desses 3 vetores com uma combinação única de pares de bytes, alterando 100% a criptografia e possibilitando uma segurança muito maior.
A utilização do Crypt Table Maker é simples e é altamente recomendável que cada usuário (usuário do componente) crie seu próprio conjunto de tabelas de criptografia (não se deve usar a tabela que vem junto com o componente por motivos de segurança). Abra o executável e explore suas funcionalidades. Após gerar os 3 vetores de criptografia, salve o arquivo resultante por cima do arquivo CryptTables.inc, localizado na pasta inc do componente e reinstale o componente no Delphi. Guarde uma cópia do arquivo CryptTables.inc em local seguro, do contrário não será mais possível realizar a descriptografia.
Direitos autorais
Este componente está sendo disponibilizado de forma gratuita e com código-fonte completo. É permitido que ele seja modificado de qualquer forma, mas derivações do mesmo não podem ser vendidas. Tais derivações, se distribuídas, precisam manter-se gratuitas, com código-fonte completo e fazer referência a versão original (esta que eu desenvolvi) por meio de uma menção a esta página incluindo um link para ela. Qualquer desrespeito a estas normas simples tornará você um grandessíssimo filho da puta que merece morrer empalado por mil "negões da picona".
Se este componente for usado ou mencionado em algum outro artigo, seja ele virtual ou não, a regra de menção a mim e ao componente original também se aplica, bem como a maldição do "negão", caso você a ignore.
Garantias
Apesar de eu utilizar este componente com sucesso em minhas implementações eu não posso garantir seu pleno funcionamento em todos os ambientes e plataformas, por isso, use-o por sua conta e risco. Eu sempre recomendo que se teste bastante um componente antes de usá-lo e eu só não fiz isso porque não possuo uma grande quantidade de plataformas e ambientes. Considere inicialmente seu uso para fins didáticos e somente o use em implementações comerciais quando estiver certo de que ele está funcionando como você imagina que ele deve funcionar. Não me responsabilizo por qualquer tipo de dano que esse componente possa causar, seja ele material ou moral.
Peço que leia com muita atenção a seção "O Crypt Table Maker" deste artigo, pois ela explica algo a respeito de impossibilidade de descriptografia. Não me responsabilizo a respeito desta impossibilidade, já que está bem explicado nesta seção que você deve fazer backup das tabelas de criptografia (CryptTables.inc).
Histórico de versões
- 1.0: Nada foi acrescentado. Essa versão é apenas uma conversão total do JavaScript para pascal. Apenas uma unit simples;
- 2.0: Após algum tempo de uso descobri que as senhas não eram case sensitive! Isso está longe de ser seguro! Alterei a inclusão de senha para que a mesma seja encriptada sozinha (pseudo hash) antes de ser efetuada a criptografia do texto. Dessa forma cada senha irá gerar um texto (des)encriptado completamente diferente mesmo se apenas 1 caractere da senha tiver sido alterado, seja quanto ao caso, ou seja quanto a quantidade de caracteres na senha;
- 3.0: Transformação da unit simples em um componente não visual instalável na paleta de componentes do Delphi que oferece um evento (OnUpdateProgress) para ser usado na atualização de barras de progresso;
- 4.0: Descobri que o encriptador de senha que usava a fim de obter textos (des)encriptados 100% diferentes quando se mudava apenas 1 caractere (v2.0) não estava funcionando corretamente. Reescrevi a função chamando-a de MakeHashFromKey e passei a usar um gerador de hash verdadeiro (Microsoft Crypto API). Nesta versão foram incluídas mais duas funções para encriptar e desencriptar com chaves privadas e públicas respectivamente. Uma nova propriedade foi incluída (FullCharacterSet) que limita o uso de caracteres pelo componente;
- 5.0: Ajustes para Delphi XE5. Foram removidas todas as referências ao objeto Application e consequente remoção da dependência com a unit Forms. A função para gerar hashes foi simplificada e agora usa as funções do Krakatoa, que já usava o Microsoft Crypto API. A propriedade FullCharset foi removida. Em seu lugar foi criada uma nova propriedade CryptTable para indicar a tabela de criptografia a ser usada (ctFull, ctNoControl, ctSafe);
- 6.0: Mais ajustes para compilação tanto em Delphis ANSI como em Delphis Unicode, bem como ajustes para que o algoritmo funcione corretamente em Windows XP. Os algoritmos foram reescritos para que utilizem arrays de bytes, chamados de TSubject. Funções acessórias para conversão de, e para, String a partir de TSubject também foram incluídas, bem como a capacidade de salvar e carregar tais TSubjects em arquivos específicos para este fim.