Criptografia em AdvPL – Parte 12

Introdução

No post anterior vimos as dicas de ouro de uso da OpenSSL !! Agora, vamos ver um pouco mais sobre os certificados digitais, e as funções do AdvPL de manipulação / conversão de certificados e chaves. Para ver tudo o que já foi publicado nessa série de posts, acesse o link Criptografia e Segurança.

Funções AdvPL para manipulação de Certificados Digitais

No Post 01, vimos uma lista de funções do AdvPL para manipulação de certificados digitais e chaves criptográficas. O objetivo desse grupo de funções é obter algumas informações dos certificados, e realizar a conversão de certificados e chaves de diversos formatos para o formato PEM — usado e suportado pelo TOTVS Application Server e seus componentes.

Função AdvPL PEMInfo()

Documentada no TDN no link http://tdn.totvs.com/display/tec/PEMInfo, essa função permite recuperar algumas informações de um certificado digital armazenado no formado codificado em Base64  (formato PEM/CRT), como a versão do certificado, validade (de-até), emissor, destinatário ou assunto, número serial e Thumbprint ou Fingerprint do certificado (representados em Base64 e hexadecimal). Vamos ver o que ela retorna quando executamos um fonte de testes que abre aquele certificado digital que geramos no Post 06 usando o fonte abaixo:

User Function PemInfo()
Local nI,nJ
Local cFile := '\certificates\note-juliow-ssd.cer'
Local aRet
Conout('Arquivo ......... '+cFile)
aRet := PemInfo(cFile)
Conout('Certificados .... '+cValToChar(len(aRet)))
For nI := 1 to len(aRet)
  Conout('Certificado ['+cValToChar(nI)+']')
  For nJ := 1 to len(aRet[nI])
    conout('['+cValToChar(nJ)+'] '+cValToChar(aRet[nI][nJ]))
  Next 
Next
Return

Antes de executar este código, crie uma pasta chamada “certificates” a partir do rootpath do ambiente, e copie o certificado a ser verificado para dentro dela. Não precisa copiar a chave (key), apenas o certificado. Ao executar o programa usando o certificado gerado anteriormente, o resultado no log de console foi:

Arquivo ......... \certificates\note-juliow-ssd.cer
Certificados .... 1
Certificado [1]
[1] 2
[2] /C=BR/ST=SP/L=Sao Paulo/O=Tudo em AdvPL/CN=note-juliow-ssd
[3] /C=BR/ST=SP/L=Sao Paulo/O=Tudo em AdvPL/CN=note-juliow-ssd
[4] 190816000619Z
[5] 200815000619Z
[6] 540424256993644162400613528322498278498171836889
[7] UVSmBFw2nXziHOJ2wivsBfqRjwg=
[8] 5154a6045c369d7ce21ce276c22bec05fa918f08

Um arquivo pode conter mais de um certificado. Logo, o array retornado sempre é multi-dimensional, onde cada elemento contém um array com 8 propriedades do certificado. No nosso caso, o arquivo contém apenas um certificado. Cada elemento retornado no array indica uma propriedade do certificado:

Índice

Tipo

Dado

1 N Versão (0=Versão 1, 1=Versão 2, 2=Versão 3)
2 C Destinatário (Subject)
3 C Emissor (Issuer)
4 C Data de Validade Inicial
5 C Data de Validade Final
6 C Número serial
7 C Fingerprint/Thumbprint (Base64)
8 C Fingerprint/Thumbprint (Hexadecimal)

Observações

  • As datas de validade inicial e final são retornadas no formado “AAMMDDhhmmssZ” (ano,mes,dia,hora,minuto e segundo, todos com 2 dígitos e o sufixo “Z”, que identifica uma hora universal (GMT).
  • O número serial (índice 5) parece estar sendo retornado com um valor decimal.
  • Os valores do Fingerprint/ThumbPrint são calculados para o certificado — usando SHA1 por default — e retornados em duas formas: Hexadecimal e Base64. Caso seja necessário retornar esta informação usando outro algoritmo de hash, basta especificar o algoritmo desejado no terceiro parâmetro da função.

Usando a OpenSSL

Podemos utilizar o comando abaixo da OpenSSL para ler as informações do certificado:

openssl x509 -in note-juliow-ssd.cer -noout -text -fingerprint

E com ele obtemos o resultado abaixo:

Version: 3 (0x2)
Serial Number:
5e:a9:76:38:db:c0:3c:5e:cf:f6:8b:e0:cf:7c:b8:5c:ad:87:59:d9
Issuer: C = BR, ST = SP, L = Sao Paulo, O = Tudo em AdvPL, CN = note-juliow-ssd
Validity
Not Before: Aug 16 00:06:19 2019 GMT
Not After : Aug 15 00:06:19 2020 GMT
Subject: C = BR, ST = SP, L = Sao Paulo, O = Tudo em AdvPL, CN = note-juliow-ssd
SHA1 Fingerprint=51:54:A6:04:5C:36:9D:7C:E2:1C:E2:76:C2:2B:EC:05:FA:91:8F:08

Função AdvPL WriteRSAPK()

Documentada no TDN no link http://tdn.totvs.com/display/tec/WriteRSAPK, a função WriteRSAPK() foi criada para permitir a conversão de uma chave privada no formato DER para PEM, bastando informar onde está o arquivo com a chave privada de origem, e onde deve ser gerada a chave de destino.

Uma chave privada ou mesmo um certificado digital são salvos em disco normalmente usando dois formatos:

  • Arquivo texto codificado em Base64 — formato PEM
  • Arquivo ASCII binário — Formato DER

Os formatos PEM são usados por exemplo em servidores Apache e similares, enquanto o formato DER é usado em aplicações Java. O conteúdo da chave privada é o mesmo, o que muda é a forma de representação da chave do arquivo. Como os formatos de certificados e chaves no TOTVS Application Server são os do Apache (PEM), todas as demais funções de conversão de certificados e chaves do AdvPL até o momento convertem informações, certificados e chaves de outros formatos ( PKCS, PFX, DER ) para PEM.

Funções de suporte ao formato PFX/PKCS12

O formato PFX (ou PKCS12) — usado pelo Windows / IIS por exemplo — é um container que permite encapsular — dentro do mesmo aquivo — por exemplo um ou mais certificados digitais, certificado(s) da cadeia de autenticação, chaves privadas e outros. Para facilitar a extração e conversão destas informações de um arquivo no formato PFX para PEM, o AdvPL disponibiliza as seguintes funções:

E, adicionalmente, para obter a lista de informações que estão dentro de um determinado arquivo PFX, temos também a função PFXInfo – http://tdn.totvs.com/display/tec/PFXInfo. Esta função retorna apenas informações básicas sobre o certificado principal e o(s) certificados da Autoridade Certificadora — quando presentes — do arquivo, como por exemplo a verão do certificado, emissor, destinatário e datas de validade inicial e final.

Todas as funções acima têm em comum o arquivo PFX a ser avaliado e um parâmetro opcional de senha, que deve ser utilizado quando o arquivo PFX estiver protegido por senha. E, claro, a função somente obterá sucesso se o arquivo PFX utilizado tiver a informação que será extraída 😀

Formatos PKCS7 (P7B) e PKCS8

Além dos formatos já vistos, ainda temos o PKCS e PKCS8, que podem ser usados para guardar uma chave privada. Para extrair a chave privadas destes formatos para o formato PEM em AdvPL, podemos usar as funções abaixo:

Conclusão

Agora que já vimos hash criptográfico, uso de certificados da infra-estrutura do TOTVS Application Server — HTTPS e conexão segura com SmartClient, nos próximos posts serão abordadas funcionalidades de assinatura digital usando o AdvPL 😀

Agradeço novamente a audiência e lhes desejo TERABYTES DE SUCESSO !!!

Referências

 

Criptografia em AdvPL – Parte 04

Introdução

Nos posts anteriores, abrimos a caixa de Pandora da criptografia, e os três primeiros posts vimos uma introdução ao assunto e tudo o que temos em matéria de HASH Criptográfico em AdvPL. Agora, vamos entrar no mundo das conexões seguras, chaves criptográficas e certificados digitais 😀

Posts anteriores

Glossário – Introdução de terminologias

Como são muitas terminologias novas, vou colocar aqui no começo algumas descrições breves sobre algumas palavrinhas novas que vamos ver muito daqui pra frente 😀

Chave criptográfica — Uma chave criptográfica é uma parte da informação que permite codificar (ou cifrar) um texto em algo incompreensível, que somente pode ser decodificado por quem também têm esta chave. A criptografia assimétrica baseia-se em pelo menos um par de chaves — a chave privada, que deve ser de acesso único da pessoa ou entidade proprietária da chave, e a chave pública, gerada a partir da chave privada usando um método que não torna possível descobrir uma através da outra.

Criptografia assimétrica — Mais conhecida por criptografia de chave pública e privada, oferece um método seguro de troca de mensagens entre as partes envolvidas na troca de mensagens e documentos criptografados ou assinados com um certificado digital. É necessário um par de chaves para codificar e decodificar uma informação.

Criptografia simétrica – Método criptográfico que utiliza apenas uma chave criptográfica para codificar e decodificar uma informação.

Certificado Digital — Trata-se de um arquivo eletrônico que serve como identidade virtual para uma pessoa física ou jurídica, e por ele pode se fazer transações online com garantia de autenticidade e com toda proteção das informações trocadas. Ela contém alguns dados de identificação, uma chave criptográfica púbica para identificá-lo em comunicações seguras e assinaturas digitais.

Assinatura Digital — Processo realizado sobre um documento, envolvendo HASH e criptografia assimétrica, para garantir que um documento não foi alterado ou adulterado após assinado, e garantindo em caráter irrevogável que a assinatura digital pertence ao emissor do documento, de forma autêntica e irretratável. De forma resumida, um HASH do documento é gerado e criptografado com a chave privada do emissor, e esse conteúdo passa a fazer parte daquele documento. A autenticidade do documento e validade da assinatura pode ser verificada por outra entidade que possua a chave pública do emissor, descriptografando o HASH que está no documento usando a chave pública do emissor, e recalculando o HASH do documento para ver se os valores são idênticos.

Conexão Segura (SSL e/ou TLS) — Trata-se de uma especificação de protocolo de conexão segura, criada para prover privacidade e integridade das informações trocadas entre as duas pontas da conexão. É uma especificação extensa e bem flexível, que internamente usa chaves criptográficas geradas dinamicamente, envolvendo pelo menos a autenticidade do certificado digital usado pelo servidor, mas também pode envolver a autenticação de ambas as partes através de uma troca de certificados e de chaves públicas entre as partes envolvidas.

OpenSSL — Trata-se de um projeto Open Source criado para prover ferramentas e bibliotecas de funções para criar e manipular certificados digitais, funções criptográficas e implementação de conexões seguras SSL/TLS para Clientes e Servidores. Vamos usá-la em breve, inclusive…

RSA – Nome do principal sistema / especificação de criptografia e assinatura digital, baseado em chave pública. Criado em 1978, é um dos mecanismos de criptografia mais usados no mundo, e considerado um dos mais seguros.

AES – Acrônimo de Advanced Encryption Standard, é o nome da especificação de um algoritmo de criptografia simétrico de dados eletrônicos estabelecida pelo Instituto Nacional de Padrões e Tecnologia (NIST) dos EUA em 2001. Ela substitui o DES — Data Encryption Standard — publicado em 1977

DES e 3DESData Encryption Standard e Triple Data Encryption Algorithm , respectivamente, são algoritmos de criptografia de 56 bits e 168 bits (***) de tamanho de chave, atualmente considerados inseguros devido ao tamanho curto das chaves empregadas, foram substituídos pelo AES.  

(***) Embora o 3DES tenha uma chave de 168 bits, ele é composto por três chaves de 56 bits, onde a primeira é igual a terceira, garantindo uma segurança efetiva de apenas 112 bits.

X.509 — Trata-se da especificação do formato de um certificado digital de chave pública. A especificação possui até o momento três versões, sendo que a versão 3 está em uso e atende a múltiplas necessidades com segurança a mais de 20 anos.

PKCS 12# — Formato de arquivo para armazenamento de múltiplos objetos criptográficos — chaves, certificados, etc — em um único arquivo. O Windows usa esta especificação em arquivos com extensão .PFX — Os certificados digitais X.509 podem ser gravados em arquivos com outros formatos e extensões, como por exemplo .pem , .cer , .per, .der , .p12, … O formato PEM é codificado em BASE64 e pode ser aberto para visualização em um editor de textos comum. Os formatos de arquivos são divididos em dois tipos : Container (pfx,pks12,pkcs7) e Encoding (pem,der,cert).

Só pelo número de termos e conceitos, percebe-se que o assunto é extenso … mas se abordarmos ele por partes — algumas partes, um monte de partes — veremos que todas se encaixam, e que o monstro não é tão feio assim 😀

Onde tudo isso é utilizado ?

Conexões seguras usando SSL/TLS são usadas em diversos protocolos, como HTTPS (Conexões WEB seguras — sites de e-commerce, bancos, etc), protocolos de acesso a e-mails por conexão segura (SMTP, POP3, IMAP). Assinaturas digitais são usadas em documentos como NFE (Nota Fiscal Eletrônica), e-mails com certificado pessoal, eCPF e eCNPJ para acesso e operações com as ferramentas e portais da Secretaria da Fazenda, o leque é muito grande.

Como isso é utilizado ?

Aí sim, a resposta é maior ainda. Existem tipos de certificados digitais para finalidades diferentes, entidades e órgãos certificados para a emissão de certificados, conjuntos de certificados auxiliares para validação e autenticação dos certificados emitidos, formas e padrões para a assinatura digital de vários tipos de documentos, conexões seguras com ou sem autenticação do cliente, com parâmetros e tipos de chaves diferenciados, cada utilização tem a sua receita de bolo que pode usar ou não boa parte desse embrolho 😛

Para não dar nó nem na minha cabeça e nem na de vocês, eu acho interessante começar com os testes práticos em pequenas aplicações, demonstrando os comportamentos apresentados pelos componentes envolvidos para cada caso de uso. Assim, fica mais fácil entender o que cada caso têm em comum, e como as funções do AdvPL e as configurações do TOTVS Application Server permitem usar tudo isso, além de descrever o que exatamente e como ele suporta cada recurso para cada caso .

O material de pesquisa na Internet é muito amplo e completo, ao longo dos posts dessa série eu me baseio em muito material, todos os posts são acompanhados dos links de referência de onde eu obtive as informações, utilize-os e não se limite as fontes que eu usei, pesquise outras fontes para obter mais detalhes 😀

Conclusão

Por hora eu acho que já tem bastante coisa pra digerir e assimilar, os termos usados aqui serão repetidos com frequência nos próximos posts, não se acanhe em ler esta página quantas vezes forem necessárias para fixar estas terminologias 😀

Espero que este conteúdo lhe seja útil, em caso de dúvidas ou sugestões, escreva um comentário, e novamente desejo a todos TERABYTES DE SUCESSO !!!

Referências

 

Criptografia em AdvPL – Parte 03

Introdução

Nos post anterior (Criptografia em AdvPL – Parte 02), vimos com mais propriedades os hashes criptográficos MD5 e SHA1, e as funções AdvPL correspondentes. Agora, vamos ver todas as demais funções de hash disponíveis no AdvPL !!!

Formatos suportados no AdvPL

As funções publicadas no AdvPL para hash criptográfico permitem o cálculo de hash usando os seguintes formatos / algoritmos:

  • MD5
  • RIPEMD160
  • SHA1
  • SHA224
  • SHA256
  • SHA384
  • SHA512

A maioria das gerações de hash possuem funções próprias, enquanto outras usam uma função de encapsulamento para gerar vários tipos de hash criptográfico. Vamos começar pela função que permite gerar todos estes hashes.

Função AdvPL EVPDigest()

Esta função permite gerar todos os hashes acima a partir de uma string em AdvPL, e um segundo parâmetro numérico para indicar qual o algoritmo para a geração do hash deve ser utilizado. A documentação dela na TDN está disponível no link http://tdn.totvs.com/display/tec/EVPDigest

A diferença de comportamento dela para as demais já vistas anteriormente, é que — por hora — seu retorno é uma nova string binária com o hash correspondente, e não uma string com o hash em hexadecimal. Mas isso não é problema, podemos converter o resultado para hexadecimal de forma muito simples. Vamos ao exemplo:

User Function Hash2()
Local cInfo := 'Hello AdvPL'
Local nI

conout('String ......... ['+cInfo+']')

conout('MD5 ............ ['+Str2Hex(EVPDigest(cInfo,1))+']')
conout('RIPEMD160 ...... ['+Str2Hex(EVPDigest(cInfo,2))+']')
conout('SHA1 ........... ['+Str2Hex(EVPDigest(cInfo,3))+']')
conout('SHA224 ......... ['+Str2Hex(EVPDigest(cInfo,4))+']')
conout('SHA256 ......... ['+Str2Hex(EVPDigest(cInfo,5))+']')
conout('SHA384 ......... ['+Str2Hex(EVPDigest(cInfo,6))+']')
conout('SHA512 ......... ['+Str2Hex(EVPDigest(cInfo,7))+']')

Return

// Converte cada byte da string em uma string com 2 caracteres
// em hexadecimal, representando o valor do byte ( de 00 a ff ) 
STATIC Function Str2Hex(cBuffer)
Local cRet := '', nI
For nI := 1 to len(cBuffer)
  cRet += padr(__dectohex(asc(substr(cBuffer,nI,1))),2,'0')
Next
Return cRet

Com esta função, podemos gerar quaisquer um dos hashes da lista acima. Agora vamos ver o resultado no console do Totvs Application Server:

String ......... [Hello AdvPL]
MD5 ............ [cab49bf9a88ee1c3bfa6c34220edb58e]
RIPEMD160 ...... [ae2497e1672cce7642e329da57ba7fd6b4d5334e]
SHA1 ........... [d7e0a366d6f262c9bca0f3fe89c060f9b876f6ed]
SHA224 ......... [929692e8d0e887e887c82fb26af9b799a311c44c793845ddaceb7073]
SHA256 ......... [a06b154db33e70fab8bed58a9854975b97ea10b8e8f3a097b36f2956fd6f4019]
SHA384 ......... [918a60a0a2c080771b6ef194af54ee22af1c6046544dc5f3f21d5d4d3e202ba1d9249766339f4a5dc743cc2ba5969ed1]
SHA512 ......... [8be7f9a5286c3ed334511572ecbc41fcfb3fe4a1bb6070b2b16b30737e9df022e7781c9793873da7647996bd19485b58b84f5d4aa0106f3c7c84566ae1241022]

Agora, vamos rever a lista de funções para ver o que faltou:

As funções SHA256(), SHA384() e SHA512() recebem os mesmos parâmetros da SHA1(), e retornam o hash calculado usando o respectivo algoritmo, sem mais mistérios. MD5 e MD5File() já vimos no post anterior, faltou apenas a HMAC().

Função AdvPL HMAC()

HMAC é o acrônimo de Hash-based Message Authentication Code — trata-se de uma construção específica para calcular o código de autenticação de mensagem (MAC) envolvendo uma função hash criptográfica em combinação com uma chave secreta, definida pela RFC 2104.

No primeiro post sobre criptografia (Criptografia em AdvPL – Parte 01) eu havia comentado sobre uma técnica chamada “salting” — salgar — usada para você adicionar um valor fixo adicional — e secreto — na informação para gerar e validar o hash, certo? Por razões óbvias, você “salga” uma informação que você vai armazenar e validar, do seu jeito. A HMAC permite fazer isso mais “elegantemente” 😀

Internamente a função gera um hash criptográfico da string informada como primeiro argumento, usando um método de hash informado no segundo argumento, junto com uma string AdvPL contendo uma palavra-chave no terceiro argumento, retornando uma string contendo o código hash correspondente representado em hexadecimal. Podemos alterar o formato do retorno para uma string em bytes com o hash gerado através do quarto parâmetro. Vamos pro exemplo que fica mais fácil:

User Function Hash3()
Local cInfo := 'Hello AdvPL'
Local cChave := 'abracadabra'

conout("String ...... ["+cInfo+"]")
conout("Chave ....... ["+cChave+"]")
conout("HMAC MD5 .... ["+HMAC( cInfo, cChave, 1 ) +"]")
conout("HMAC SHA1 ... ["+HMAC( cInfo, cChave, 3 ) +"]")
conout("HMAC SHA256 . ["+HMAC( cInfo, cChave, 5 ) +"]")
conout("HMAC SHA512 . ["+HMAC( cInfo, cChave, 7 ) +"]")

Return


// Resultado no console

String ...... [Hello AdvPL]
Chave ....... [abracadabra]
HMAC MD5 .... [8fe3373f9a96fd5cf8f3ec1c038b26b9]
HMAC SHA1 ... [d4993adf35c2377417e281d77c5134cc6c2cecde]
HMAC SHA256 . [06245e76c80b6e1a6e955eb94fe1577540f6ea8644d0e55c69d590eddaeb44a1]
HMAC SHA512 . [e8a8edcec569d5ea8d31d6a9880eb145232d2ccf29ad624f8f0f575dd71372c530eee0436c9a3aa639a416c989dbe3b06d03f5cee369a4729f5675e8d8f9972f]

Os métodos suportados de hashing usados pela função são MD5, SHA1, SHA256 e SHA512, sendo o seu retorno correspondente ao método internamente utilizado. Da mesma forma que as demais funções de hashing, ela não é reversível, então a verificação de um hashing gerado pela HMAC() somente pode ser feita recalculando o hash do conteúdo original, e tendo a palavra-passe utilizada.

Função HASH não criptográfica

Até agora vimos funções de hash criptográfico, que têm as seguintes premissas — leia-se `hash` onde está escrito `resumo`:

  • deve ser fácil computar o valor de dispersão (hash) para qualquer mensagem
  • deve ser difícil gerar uma mensagem a partir de seu resumo (hash)
  • deve ser difícil modificar a mensagem sem modificar o seu resumo (hash)
  • deve ser difícil encontrar duas mensagens diferentes com o mesmo resumo (hash).

Porém, o conceito de hash é simplesmente gerar um valor de tamanho fixo baseado em um conteúdo de tamanho variável. Um hash não criptográfico é usado por exemplo para acelerar buscas em listas de tamanhos variáveis, que torna a busca por valores muito, muito rápida. Este artifício chama-se HASHMAP.

HASHMAP

Vamos partir por exemplo de uma lista de palavras ordenada com 1000 (mil) palavras. Com a lista está ordenada, a operação de busca pode fazer uma pesquisa binária — compara direto o texto a ser buscado com o elemento no meio da lista, se for menor, ignora tudo pra baixo e refaz a busca, se for maior ignora tudo pra cima e refaz a busca, até acabar a lista ou encontrar o elemento pesquisado, o que vier primeiro.

Na pior das hipóteses, em uma pesquisa binária em uma lista com 1000 elementos, a primeira busca descarta 500, a segunda 250, a terceira 125, a quarta 62, a quinta 31, depois 16, 8, 4, 2, 1 — e com apenas 10 comparações foi possível determinar que um determinado valor não existe na lista, certo ?

Agora, se ao invés disso, criarmos na memoria uma lista sequencial (Array), onde pré-alocamos um determinado número de elementos, por exemplo 2048 elementos, acessados diretamente pelo índice do array (de 1 a 2048), onde cada elemento é um outro array vazio, e criamos uma função de hash não criptográfico, que a partir de strings de tamanho variável, gere um número entre 1 e 2048, ao inserir um dado calculamos o hash da informação e inserimos ela diretamente no node correspondente. Com isso, você criou um HASHMAP (ou Mapa de Hash) de tamanho 2048.

Na hora de fazer a busca, a primeira etapa é calcular o hash para já buscar o array com os elementos de mesmo hash direto, em tempo constante. Depois, você compara o que você quer buscar apenas com os poucos elementos que eventualmente podem apresentar colisão de hash. Você reduz de 10 comparações para um cálculo de hash e uma comparação, fazendo apenas uma comparação adicional para cada colisão 😀

HASHMAP nativo no AdvPL

Em AdvPL existem um conjunto de funções para criar um hashmap para Arrays e até uma classe para isso — veja no link da TDN TDN – Manipulação de matriz (HashMap) — com elas nem precisamos criar o nosso hash, ela cria um mapa internamente, e permite buscas em tempo linear — não tende a crescer mesmo que a lista seja bem grande. Este recurso já foi visto em posts anteriores no blog, veja as referências no final do post.

Nem preciso dizer que, dependendo das diferenças entre os conteúdos dos elementos do array, não é fácil fazer uma função de hash para gerar um mapa de tamanho fixo que evite colisões, mesmo com mais elementos no mapa do que elementos no array. Logo, reinventar a roda é desnecessário para estes casos, use as funções de HASHMAP para Arrays do AdvPL e seja feliz !!! ❤

Conclusão

Agora que já vimos praticamente tudo sobe hash, vamos entrar no mundo dos certificados digitais e chaves criptográficas 😀

Espero que tenham gostado, e desejo a todos TERABYTES DE SUCESSO !!!

Referências

 

 

Criptografia em AdvPL – Parte 02

Introdução

No primeiro post da série sobre criptografia e hash (Criptografia em AdvPL – Parte 01), vimos uma breve introdução ao assunto, a lista de funções disponíveis no AdvPL para trabalhar com isso, e um exemplo de uso dos hashes MD5 e SHA*. Agora, vamos ver o hash com mais detalhes sobre cada um deles, e o que cada função permite fazer.

Vimos até agora superficialmente os hashes MD5 e SHA, mas existem vários outros algoritmos para fins específicos, como o RIPE Message Digest 160 Bits (RIPEMD160), que é considerado um hash mais “fraco”, utilizado em projetos onde o nível de segurança é aceitável. Vamos começar pelo MD5 e logo em seguida pelo SHA1.

MD5 message-digest algorithm

Criado por Ronald Rivest em 1991 para substituir uma função anterior de hash (MD4), cuja especificação foi publicada em 1992 na RFC 1321. Para os mais curiosos, veja nesse link o fonte em C da implementação do algoritmo MD5, e demais links de referência no final do post.

Segurança do HASH MD5

Embora o hash gerado tenha 128 bits — o que pode gerar 2^128 combinações distintas (aproximadamente 3,40e+38 combinações possíveis), a base do seu algoritmo hoje é considerada “fraca”, pois foram descobertas formas de ataque que permitem em tempo computacional ridiculamente pequeno forçar uma colisão de hash alterando ou injetando informações nos dados originais usados para gerar o HASH.

Onde isso impacta ?

Em diversos mecanismos de segurança de arquivos e assinaturas digitais, um HASH pode ser usado para garantir a integridade do conteúdo. Por exemplo, em vários sites de download um programa oficial é fornecido diretamente em um link, e logo abaixo é fornecido um HASH MD5, SHA1 ou outro, referente ao conteúdo disponibilizado. Com isso, após fazer o download, você pode executar uma ferramenta do sistema operacional ou um utilitário que calcule novamente o HASH do arquivo baixado. Se o HASH for diferente, ou o arquivo está corrompido, ou pode ter sido adulterado.

Em assinaturas digitais e chaves de criptografia, é fundamental que a chave não seja alterada por ninguém, pois isso permitiria alterar atributos da chave ou injetar código malicioso em um programa. Usando um algoritmo de busca por colisão, eu posso pegar um arquivo qualquer, acrescentar conteúdo nele, e temperar uma parte desse conteúdo para fazer com que o HASH gerado desse novo conteúdo seja idêntico ao HASH gerado pelo conteúdo inicial.

Com isso um certificado digital pode ser “forjado” a partir de um certificado original, e enganar o mecanismo de autenticação do certificado — veremos mais para a frente como isso funciona — mas na prática, você pode acessar um website com SSL, onde o Browser vai achar que o certificado é válido e que você está em uma conexão segura. Golpes como criar um site falso de um banco e pegar seus dados bancários podem ser criados usando  um certificado digital falso que o Browse acredita ser autêntico e verificado, e fazer você realmente acreditar que está acessando o site verdadeiro do banco, ou o sistema operacional acreditar que está usando um software original e autenticado, fornecido pelo próprio fabricante 😛

Entre 2005 e 2008 foram feitas diversas descobertas sobre as vulnerabilidades de colisão de MD5 e SHA1, e em 2008 uma equipe de pesquisadores usou um cluster de PlayStations 3 para forjar um certificado autenticado pela RapidSSL, e a partir dele poder criar outros certificados. Desde então não foram mais emitidos certificados com validação de autenticidade baseadas em MD5. EM 2012 foi identificado um malware chamado Flame, que forjou um certificado digital da Microsoft, fazendo os usuários acreditarem que estavam executando um programa autêntico da mesma. Em 2013 a Microsoft anunciou o fim do suporte a certificados digitais que usam autenticação com o MD5. Também suscetível a ataques desta natureza, as autenticações usando SHA1 também estão sendo descontinuadas, a Microsoft anunciou em 2013 que não recomenda o uso de SHA1 para esta finalidade, e informou para as autoridades certificadoras que deixará de reconhecer certificados com este algoritmo a partir de 2016.

Mesmo assim, MD5 e SHA1 ainda são bastante utilizados em outras finalidades, que não dependem de um nível de segurança maior, quando não compensa o esforço de tentar quebrá-lo.

SHA1 – Secure Hash Algorithm 1

Desenvolvido pela NSA (Agência de Segurança Nacional Americana), publicado na RFC 3174, o SHA1 retorna um código de 20 bytes (160 bits), correspondente a 40 caracteres em representação hexadecimal. Embora use um mecanismo diferente do MD5, o SHA1 também está sujeito a ataques de colisão, e não é recomendável seu uso em chaves criptográficas e certificados, e seu uso nestas aplicações vem sido descontinuado. Por outro lado, ele ainda é amplamente utilizado em outros fins, como determinar por exemplo se um arquivo está íntegro ou foi alterado, como por exemplo os softwares e plataformas de versionamento de fontes, como o GIT, por exemplo.

Funções AdvPL MD5() e MD5FILE()

Documentada no TDN nos links http://tdn.totvs.com/display/tec/MD5 e http://tdn.totvs.com/display/tec/MD5File, a função recebe uma string de tamanho variável, até o limite estabelecido para Strings em AdvPL, e retorna por default uma nova string de 32 caracteres contendo o código hash gerado, representado em hexadecimal. A string informada como parâmetro inclusive pode ser vazia, e pode conter bytes/caracteres de 0 a 255. Como cada byte informado na string de parâmetros é importante e significativo, uma string com um espaço em branco a mais na direita é diferente de uma sem este espaço, e cada uma vai gerar um hash diferente, por qualquer método utilizado. A função possui um segundo parâmetro, opcional, que quando informado com o número 1, faz a função retornar uma string de 16 bytes contendo a representação do HASH calculado.

Ela pode receber uma string originalmente de até 1 MB — tamanho máximo de uma variável “C” Caractere do AdvPL — mas este limite pode ser estendido usando a configuração MaxStringSize do TOTVS Application Server. Caso seja necessário gerar um HASH de um arquivo, você não precisa carregá-lo na memória para fazer isso, basta usar a função MD5File()

Um exemplo de uso da função já foi publicado anteriormente aqui no Blog, no uso do Banco de Dados para armazenamento de imagens em disco. Junto da imagem eu posso salvar o MD5 de seu conteúdo, e caso eu receba uma nova imagem para atualização, eu posso optar por não atualizá-la caso o MD5 gerado sobre o conteúdo da imagem seja o mesmo, veja links de referência no final do post.

No post sobre O que é CODEPAGE e ENCODING – Parte 04, eu menciono uma função do Framework AdvPL muito útil para determinar conteúdos binários dentro de strings, chama-se HexStrDump(). 

Função AdvPL SHA1()

Documentada na TDN no link http://tdn.totvs.com/display/tec/SHA1, ela recebe os mesmos parâmetros da MD5(), e aceita uma string de parâmetro com bytes de ASCII 0 a ASCII 255, retornando por default uma string com 40 caracteres em hexadecimal, correspondendo aos 20 bytes (126 Bits) do hash calculado.

Outras representações

Por padrão a maioria as funções do AdvPL retorna por default a representação do hash calculado em uma string hexadecimal, que ocupa duas vezes mais espaço do que efetivamente os bytes que compõe o hash. Porém, guardar um resultado de HASH em bytes em um campo de uma tabela em um Banco de Dados somente seria possível se fosse usado um campo do tipo “M” Memo, que aceita conteúdo binário. Porém, existem formas menores de representação que o hexadecimal. Por exemplo, podemos calcular um HASH SHA1 e parametrizar a função para retornar os 20 bytes correspondentes ao HASH, e depois codificar o resultado em BASE64 — assim o hash calculado poderia ser gravado em um campo de texto / caractere em apenas 24 caracteres, e não 40 caso ele fosse representado em hexadecimal, veja exemplo abaixo:

User Function SHaToB64()
Local cInfo := 'Hello AdvPL'
Local cSHA1 , cBase64

conout('String original')
conout(hexstrdump(cInfo))

cSha1 := SHA1(cInfo,1) // retorno de string em bytes 
conout('SHA1 em bytes')
conout(hexstrdump(cSha1))

cBase64 := Encode64(cSha1)
conout('Convertido para BASE64')
conout(hexstrdump(cBase64))

Return

// Resultado no console

String original
HexSTRDump  ( String 11 / Start 1 / Length 11 )
-------------------------------------------------------------------------------
48 65 6C 6C 6F 20 41 64 76 50 4C                 | Hello AdvPL
-------------------------------------------------------------------------------

SHA1 em bytes
HexSTRDump  ( String 20 / Start 1 / Length 20 )
-------------------------------------------------------------------------------
D7 E0 A3 66 D6 F2 62 C9 BC A0 F3 FE 89 0C 06 F9  | ╫αúf╓≥b╔╝á≤■ë__∙
B8 76 F6 ED                                      | ╕v÷φ
-------------------------------------------------------------------------------

Convertido para BASE64
HexSTRDump  ( String 28 / Start 1 / Length 28 )
-------------------------------------------------------------------------------
31 2B 43 6A 5A 74 62 79 59 73 6D 38 6F 50 50 2B  | 1+CjZtbyYsm8oPP+
69 51 77 47 2B 62 68 32 39 75 30 3D              | iQwG+bh29u0=
-------------------------------------------------------------------------------

No final das contas, precisamos utilizar os métodos de hashing e codificações exigidos por quem implementa uma determinada funcionalidade.

Conclusão

Ainda tem muito pela frente, mas eu acredito que a partir daqui cada nova etapa deve ficar mais clara.

Espero que estas informações lhes sejam úteis, e lhes desejo como sempre TERABYTES DE SUCESSO !!!

Referências

Copyrights

 *** MD5 message-digest algorithm
 *** Retrieved from https://people.csail.mit.edu/rivest/Md5.c
 
 **********************************************************************
 ** Copyright (C) 1990, RSA Data Security, Inc. All rights reserved. **
 **                                                                  **
 ** License to copy and use this software is granted provided that   **
 ** it is identified as the "RSA Data Security, Inc. MD5 Message     **
 ** Digest Algorithm" in all material mentioning or referencing this **
 ** software or this function.                                       **
 **                                                                  **
 ** License is also granted to make and use derivative works         **
 ** provided that such works are identified as "derived from the RSA **
 ** Data Security, Inc. MD5 Message Digest Algorithm" in all         **
 ** material mentioning or referencing the derived work.             **
 **                                                                  **
 ** RSA Data Security, Inc. makes no representations concerning      **
 ** either the merchantability of this software or the suitability   **
 ** of this software for any particular purpose.  It is provided "as **
 ** is" without express or implied warranty of any kind.             **
 **                                                                  **
 ** These notices must be retained in any copies of any part of this **
 ** documentation and/or software.                                   **
 **********************************************************************

SHA-1

RFC 3174           US Secure Hash Algorithm 1 (SHA1)      September 2001


Full Copyright Statement

   Copyright (C) The Internet Society (2001).  All Rights Reserved.

   This document and translations of it may be copied and furnished to
   others, and derivative works that comment on or otherwise explain it
   or assist in its implementation may be prepared, copied, published
   and distributed, in whole or in part, without restriction of any
   kind, provided that the above copyright notice and this paragraph are
   included on all such copies and derivative works.  However, this
   document itself may not be modified in any way, such as by removing
   the copyright notice or references to the Internet Society or other
   Internet organizations, except as needed for the purpose of
   developing Internet standards in which case the procedures for
   copyrights defined in the Internet Standards process must be
   followed, or as required to translate it into languages other than
   English.

   The limited permissions granted above are perpetual and will not be
   revoked by the Internet Society or its successors or assigns.

   This document and the information contained herein is provided on an
   "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING
   TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
   BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION
   HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF
   MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.

Criptografia em AdvPL – Parte 01

Introdução

O AdvPL possui várias funções nativas da linguagem para hashing, criptografia e assinatura digital. Nesta sequência de post, vamos entrar um pouco mais fundo para entender o que é cada uma dessas coisas, o que fazem e como utilizá-las.

HASH

Explicando de forma simples, uma função de HASH gera uma informação de tamanho fixo a partir de dados de tamanho variável. Trata-se de uma forma de gerar um identificador diferente de tamanho fixo para informações quaisquer. Uma função de HASH criptográfico também é chama de função de dispersão criptográfica. Um HASH é uma função de criptografia unidirecional, isto é, não reversível — um HASH gerado a partir de uma informação não permite recuperar a informação original.

CRIPTOGRAFIA

Em linhas gerais, trata-se do uso de uma (entre várias) metodologias de codificação e decodificação de um bloco de dados, reversível, onde o método empregado pode tornar de difícil a impossível recuperar o seu conteúdo original, e apenas quem codificou ou quem sabe decodificar a mensagem consegue fazê-lo. Este recurso é usado para armazenamento e tráfego de dados sensíveis, onde mesmo que alguém intercepte ou veja os dados, o uso de um método criptográfico “forte” — veremos depois o que isso significa — torna impossível saber o que aquilo significa realmente. Usada por exemplo em conexões seguras ( HTTPS, SSL/TLS ) e assinaturas digitais.

ASSINATURA DIGITAL

Assinatura digital (não confundir com assinatura eletrônica) é, em poucas palavras, o uso de HASH junto com criptografia para determinar de forma confiável que uma mensagem recebida foi gerada pelo emissor (confirma o emissor da mensagem), íntegra (a mensagem originalmente gerada pelo emissor não foi alterada) e irretratável (o emissor não pode negar que a informação foi por ele gerada), com tal nível de segurança que, a assinatura digital (ou firma digital) substitui legalmente —  para fins jurídicos — a assinatura física de um documento.

O que temos em AdvPL ?!

No TDN — Totvs Developer Network — dentro da árvore das funções AdvPL, existe um ramo dedicado às funções de Segurança (http://tdn.totvs.com/pages/viewpage.action?pageId=6063905), e dentro dele duas seções de funções:

As funções sob a árvore de Criptografia referem-se a Criptografia e Hash, enquanto a árvore Genérica possui funções auxiliares para informações e conversões entre frmatos de certificados digitais. Vamos separar elas por grupo de funcionalidade nas listas abaixo:

Funções de HASH

Funções de Criptografia

Assinatura Digital 

Assinatura digital com HSM

Chaves Criptográficas e Certificados Digitais

Inicialmente, vamos começar vendo em mais detalhes o que é e onde podemos usar um HASH !

HASH, o que é e como funciona ?

Beleza, por que eu preciso de uma função de HASH, por que existe mais de uma, onde eu preciso usar isso ? Bem, vamos por partes. Como vimos na descrição resumida, uma função de HASH em si não é exatamente uma função criptográfica, que podem ser usadas para fins específicos, e usadas junto com funções criptográficas.

Eu acredito ser mais prático e didático partir para um exemplo de uso, e a partir dele abordar os detalhes: Imagine que você vai montar uma determinada aplicação WEB — um site ou portal — onde determinados conteúdos requerem uma autenticação de usuário para acesso — normalmente feitos com e-mail e uma senha.

Para estar em conformidade com as diretrizes de segurança, tanto suas como as dos usuários cadastrados nesta aplicação, a senha usada pelo usuário é uma informação “sensível”, ninguém além do usuário deve ter acesso a senha que ele usou — NEM VOCÊ que criou o site. Porém, você precisa usar um método de autenticação, que confirme se o usuário está cadastrado e têm acesso ao site, e se a senha digitada é daquele usuário.

Ao criar a sua tabela de usuários do site, se você gravar a senha, mesmo que criptografada de alguma forma, você é capaz de recuperá-la. Se esta informação cair na mão de terceiros e a criptografia utilizada for descoberta — você usou um método “fraco” ou a sua chave de criptografia “vazou”, isso pode causar prejuízos incalculáveis, pois muitos usuários não atentos às melhores práticas de definição de senhas, fatalmente usam a mesma senha de autenticação para outros web sites…

A alternativa mais elegante e segura para todos é usar uma função de HASH. Com ela, quando o usuário se cadastrar no seu site, e você receber a senha que ele quer utilizar, você não grava a senha na base de dados, mas sim um HASH da senha. Lembrando que uma função de HASH — bem projetada — não é reversível, porém é determinística — a mesma entrada gera a mesma saída.

Logo, quando o usuário for se autenticar, quando ele informar o e-mail e a senha, você busca as informações do usuário no seu cadastro usando o e-mail, gera o HASH da senha que ele informou, e compara com o HASH da senha que ele forneceu quando se cadastrou. Se eles forem iguais, o usuário digitou a senha correta de acesso.

Existem muitos sites na internet que (ainda) não seguem as melhores práticas de segurança da informação. Podemos constatar isso em alguns deles quando usamos o recurso “Esqueci minha senha”: Se o site envia um e-mail pra você com a sua senha, ele guardou a sua senha, e não um HASH dela 😛

Diferenças de HASH

Cada função de HASH normalmente leva o nome do algoritmo utilizado para o cálculo do HASH, por exemplo:

  • MD5 = MD5 Digest Algorithm
  • SHA1 = Secure Hash Algorithm 1

Uma função de HASH recebe dados de um tamanho variável, e gera um novo dado de tamanho fixo e de forma determinística a partir do dado fornecido. O que muda entre cada função de HASH é a forma que ela gera o HASH e o tamanho da informação resultante. Por exemplo:

Um HASH usando MD5 usa um algoritmo que gera, para cada informação recebida, um código de 128 bits — ou 16 bytes — normalmente representada por 32 caracteres em hexadecimal (base numérica 16, que usa os números de 0 a 9 e os caracteres de ‘a’ a ‘f’).

Já um HASH usando SHA1 gera um código de 160 bits (20 bytes), normalmente representados por 40 caracteres em hexadecimal. Se você procurar as origens de ambos, vai descobrir que ambos são evoluções do algoritmo MD4 — porém o SHA1 foi projetado pela Agência de Segurança Nacional Americana (NSA).

Todos os demais SHA* ( SHA-256, SHA-384, SHA-512) são algoritmos baseados no SHA1, que geram chaves de tamanhos maiores — o próprio nome da chave indica quantos BITS são utilizados, para er o tamanho em bytes da chave divida o número de bits por 8, e para o tamanho da chave em hexadecimal, multiplique o tamanho em bytes por 2.

  • SHA-256 | 256 Bits | 32 Bytes | 64 caracteres em hexadecimal
  • SHA-384 | 384 Bits | 48 Bytes | 96 caracteres em hexadecimal
  • SHA-512 | 512 Bits | 64 Bytes | 128 caracteres em hexadecimal

Exemplo em AdvPL

Vamos partir de um programa que, a partir de uma determinada informação, gere os hashes MD5, SHA1, SHA256 e SHA512 para o texto “Hello AdvPL”

User Function HashTest()
Local cInfo := 'Hello AdvPL'

conout('String Fornecida .... ['+cInfo+']')

cMD5 := MD5(cInfo)
conout('MD5 ............ ['+cMD5+']')
cSha1 := SHA1(cInfo)
conout('SHA1 ........... ['+cSha1+']')
cSha256 := SHA256(cInfo)
conout('SHA256 ......... ['+cSha256+']')
cSha512 := SHA512(cInfo)
conout('SHA512 ......... ['+cSha512+']')

Return

/* 
Resultado para "Hello AdvPL"

String Fornecida .... [Hello AdvPL]
MD5 ............ [cab49bf9a88ee1c3bfa6c34220edb58e]
SHA1 ........... [d7e0a366d6f262c9bca0f3fe890c06f9b876f6ed]
SHA256 ......... [0a6b154db33e07fab8bed58a9854975b97ea10b8e8f3a097b36f2956fd6f0419]
SHA512 ......... [8be7f9a5286c3ed334511572ecbc41fcfb3fe4a1bb0670b2b16b30737e9d0f22e7781c9793873da7647996bd19485b58b84f5d4a0a016f3c7c84566ae1240122]
*/

Agora, vamos ver o que acontece se eu alterar o texto de  “Hello AdvPL” para “Hello AdvPl” — eu troquei apenas a última letra “L” de maiúsculo para minusculo.

String Fornecida .... [Hello AdvPl]
MD5 ............ [203ac52a0d8e698846b9c9e0c199871e]
SHA1 ........... [96481d2963dda4b9b3ffc76fa10f1873fdb6cbbf]
SHA256 ......... [604bae14779f7f07bea48836d19e225df61c550b0680e477c8ffd0c60465caa6]
SHA512 ......... [0a1aaa24f5c98ebcd1c0f7ffa13cc4fbd854e5696bf040e47a7d2fc571345436aedbcdc26c6ace5cc8926d1e566250c9144fcb86d163cffee89e54b427baa79a]

Só de bater o olho entre os hashes gerados, dá pra ver que a alteração de uma letrinha na mensagem gerou um hash totalmente diferente. Vou colocar os dois hashes MD5 um embaixo do outro:

HASH MD5 ............ [cab49bf9a88ee1c3bfa6c34220edb58e]
HASH MD5 ............ [203ac52a0d8e698846b9c9e0c199871e]

Se você pegar o texto utilizado, e usar qualquer ferramenta on-line para cálculo de hash no padrão escolhido, os resulados serão os mesmos. Por exemplo, ao informar o texto “Hello AdvPl” usando o https://emn178.github.io/online-tools/sha512.html , o hash gerado será o mesmo calculado pelo AdvPL.

Vulnerabilidades gerais do HASH

Uma vulnerabilidade básica do hash é justamente ele ser determinístico — um mesmo valor informado sempre gera o mesmo HASH. Logo, isso possibilita ataques de reversão baseados em dicionários — vou explicar isso….

Senhas curtas, fracas, baseadas em palavras normalmente usadas, podem ser usadas para compor um dicionário de senhas. Nomes de cidades, times de futebol, signos, números como “123456” ou “000000”, etc… Basta o cidadão que conseguir colocar as mãos no arquivo de dados com os emails e os hashes, e fazer um programa para gerar os hashes das palavras do dicionário que ele montou para ver se o hash “bate” — assim ele consegue recuperar a informação original “no chute”, e saber a senha que você usa.

Contorno operacional

Normalmente essa vulnerabilidade é contornada de forma bem simples, mas precisa ser “secreta”: No caso do exemplo onde eu sugeri o uso de salvar a senha do usuário usando um HASH, nada impede que você acrescente junto da senha uma informação adicional fixa e “secreta”, usando a mesma junto da senha para gerar o hash de cadastro, e para validar o login. Com isso, o hash gerado não vai apenas ser baseada na senha do usuário, mas sim na senha junto ou mesclara com uma informação adicional qualquer. Este artifício é conhecido como “salgar” a informação (ou Salting).

Conclusão

Por hora já temos bastante informação para avaliar, para mais detalhes das implementações consulte os links de referência no final do post. E até agora o que vimos foi meramente uma “introdução” assunto 😛  No próximo post, vamos ver um pouco mais sobre cada tipo de HASH e algumas vulnerabilidades especificas e intrínsecas de alguns deles.

Agradeço novamente a audiência e desejo a todos TERABYTES DE SUCESSO !!!

Referências

 

Imagens PNG em AdvPL – Parte 05

Introdução

Nos posts anteriores ( Imagens PNG em AdvPL – Parte 03Imagens PNG em AdvPL – Parte 04 ) vimos a implementação da leitura e gravação do formato PNG (por hora monocromático e limitado a um Chunk de dados IDAT). Agora, vamos ver a implementação de transparência simples — cor de fundo com transparência — e alguns detalhes a mais deste formato.

Transparência no PNG

Este formato permite várias implementações de transparência. Uma delas em especial pode ser feita com a paleta de cores (Chunk PLTE), quando usamos o tipo de cor 3 (cores da Paleta). Basta inserir um novo chuck, do tipo “tRNS” (Transparency), composto por uma sequência de bytes (caracteres) de 0 a 255, um para cada cor da Paleta de Cores. Em cada byte podemos definir o grau de opacidade da cor, de 0 (transparente) a 255 (opaco).

A implementação de transparência na classe zBitmap foi implementada por um método chamado SetTransparency(), que quando chamado com o parâmetro .T. (verdadeiro) indica que, caso o aquivo seja salvo no formato PNG, a cor de fundo usada na imagem — e armazenada na propriedade nBgColor — será definida com 100% de transparência — Byte 0 — sendo acrescentado o Chunk tRNS na imagem. Por hora esta propriedade ainda não está sendo considerada na leitura da imagem PNG do disco. Segue a título de exemplo apenas a parte do código que implementa a transparência da cor de fundo na gravação do PNG no disco:

// Salva a paleta de cores utilizada
PNGSaveChunk(nH,"PLTE",cData)

if ::lTransparent
  // SE tiver transparencia, por hora seta apenas para cor de fundo da imagem 
  // Cria o Chunk tRNS para isso ( baseado no tipo de cor 3 ( indexed colors ) 
  // para as cores da palete 

  cData := ''
  For nI := 0 to len(::aColors)-1
    If nI == ::nBgColor // Cor atual da paleta é a cor de fundo 
      cData += chr(0)   // Seta a cor como Transparente
    Else
      cData += chr(255) // Demais cores, opacidade 100%
    Endif
  NExt
  
  // Salva o Chunk de transparência 
  PNGSaveChunk(nH,"tRNS",cData)

Endif

A paleta de cores é informada no PNG quando utilizado o tipo de cor 3 (Pallete Indexed). Uma imagem True Color 24 bits normalmente usa o tipo de cor 2 ou 6 — onde cada pixel da imagem usam 3 ou 4 bytes, que compõe a cor do Pixel em RGB e/ou RGB+Alpha Channel, respectivamente.

Outros Chunks

O formato PNG permite outros Chunks adicionais, para gravar informações mais avançadas sobre as cores e sua utilização na imagem, informações de renderização adicionais para dispositivos específicos. As resoluções de cores permitem modos True Color, inclusive com canal Alpha — que permite colocar o grau de opacidade (transparência) direto na própria cor, sem precisar de um chunk auxiliar tRNS.

E, podem existir dentro do arquivo informações em texto ( Codificados por exemplo em ISO-8859 e UTF-8). Uma imagem PNG gerada por um iPhone por exemplo possui informações codificadas em Chunks tEXt que contém o modelo do telefone utilizado (iPhone 6 por exemplo), a versão do Sistema Operacional e a data e hora da criação da imagem, vide exemplo abaixo:

WARNING - LoadFromPNG - Ignored Chunk [tEXt]
HexSTRDump  ( String 15 / Start 1 / Length 15 )
-------------------------------------------------------------------------------
53 6F 75 72 63 65 00 69 50 68 6F 6E 65 20 36     | Source_iPhone 6
-------------------------------------------------------------------------------

WARNING - LoadFromPNG - Ignored Chunk [tEXt]
HexSTRDump  ( String 15 / Start 1 / Length 15 )
-------------------------------------------------------------------------------
53 6F 66 74 77 61 72 65 00 31 32 2E 33 2E 31     | Software_12.3.1
-------------------------------------------------------------------------------

WARNING - LoadFromPNG - Ignored Chunk [tIME]
HexSTRDump  ( String 7 / Start 1 / Length 7 )
-------------------------------------------------------------------------------
07 E3 07 1C 0B 1E 25                             | _π____%
-------------------------------------------------------------------------------

WARNING - LoadFromPNG - Ignored Chunk [tEXt]
HexSTRDump  ( String 32 / Start 1 / Length 32 )
-------------------------------------------------------------------------------
43 72 65 61 74 69 6F 6E 54 69 6D 65 00 32 30 31  | CreationTime_201
39 3A 30 37 3A 32 38 20 31 31 3A 33 30 3A 33 37  | 9:07:28 11:30:37
                                                 |
-------------------------------------------------------------------------------

WARNING - LoadFromPNG - Ignored Chunk [tEXt]
HexSTRDump  ( String 33 / Start 1 / Length 33 )
-------------------------------------------------------------------------------
43 72 65 61 74 69 6F 6E 20 54 69 6D 65 00 32 30  | Creation Time_20
31 39 3A 30 37 3A 32 38 20 31 31 3A 33 30 3A 33  | 19:07:28 11:30:3
37                                               | 7
-------------------------------------------------------------------------------

Um detalhe interessante que eu reparei, por exemplo o Chunk de texto — que possui o formato chave/valor — foi incluída duas vezes, uma com espaço e outra sem espaço — para ser possível softwares que trabalham com uma ou com a outra forma identificarem a data da fotografia.

Próximas etapas da zBitmap

Da mesma forma que o suporte a múltiplas cores foi implementado aos poucos para o formato BMP, este será também implementado para a classe lidar com PNGs coloridos, tanto para ler um arquivo do disco como alterá-lo e gravá-lo novamente. Por hora o suporte a JPEG está em estudos, e uma primeira versão em testes apenas usa uma função de conversão de imagem disponível no AdvPL — BMPTOJPG().

O tratamento dos chunks ainda precisa contemplar as propriedades adicionais dos tipos do Chunk, onde o nome do tipo do Chunk pode ter letras maiúsculas e minusculas, e isso adiciona um significado diferenciado para o Chunk. Por hora a leitura dos Chunks é avaliada com UPPER() mesmo, desconsiderando estas propriedades, e a geração do arquivo grava apenas os Chunks críticos, com a capitulação correta. E, também será implementado o tratamento para múltiplos Chunks 😀

Conclusão

Se têm uma especificação, dá pra implementar 😀

Agradeço a todos novamente pela audiência e lhes desejo como sempre TERABYTES DE SUCESSO !!!

Referências

 

Números Primos em AdvPL

Introdução

No post anterior (Números Primos e a Informática) vimos o que são e qual a importância dos números primos. Agora, vamos ver algumas formas de brincar com eles em AdvPL !

Gerando números primos em sequência

Uma das formas mais simples de geração de números primos em sequência, é partir dos dois primeiros números primos — números 2 e 3 — e, em incrementos de duas unidades, verificar se o número e questão é múltiplo dos números primos anteriores, isto é, se o resultado da divisão desse número por um número primo anterior é igual a zero.  O primeiro resultado de resto 0 indica que ele é múltiplo de um número anterior, logo não pode ser primo.

Este processo pode ser otimizado, pois eu não preciso testar todos os números primos anteriores, mas apenas os primos menores que a raiz quadrada do número a ser testado. Por exemplo, antes do número 1227, existem 200 números primos. Para testar se o número 1227 é primo, eu preciso realizar o teste de resto zero de divisão com todos os números primos menores que 35 — apenas 11 números.

Primeiro POC em AdvPL

O primeiro programa em AdvPL usando esta abordagem ficou bem rápido, e armazena os números gerados em um Array na memoria, vamos ver como ficou — gerando a sequencia de números primos de 2 a 8 milhões:

USER Function Primos()
Local aPrimos := {2,3}
Local nTest := 5
Local nLimite := 8000000 
Local nI, nMax
Local nTimer
Local lPrime := .T.

nTimer := seconds()

While aTail(aPrimos) < nLimite
	lPrime := .T.
	nMax := INT(SQRT(nTest))+1
	nI := 1
	While aPrimos[nI] < nMax
		IF ( nTest % aPrimos[nI] ) == 0
			lPrime := .F.
			EXIT
		Endif
		nI++
	Enddo
	If lPrime
		aadd(aPrimos,nTest)
	Endif
	nTest += 2
Enddo

nTimer := seconds() - nTimer

For nI := 1 to len(aPrimos)
	conout('P('+cValToChar(nI)+') = '+cValToChar(aPrimos[nI]))
Next

Conout('Primos calculados .... '+cValToChar(len(aPrimos)))
Conout('Tempo de Calculo ..... '+str(nTimer,8,3)+' s.')

Return

O Array já nasce com os dois primeiros números primos (2 e 3), e o próximo número a ser testado é o 5. O looping de processamento é limitado pelo maior número primo descoberto — só finaliza quando encontrar o primeiro número primo maior que 8 milhões. Antes de fazer os testes de resto de divisão com os números primos anteriores, eu determino qual é o maior número primo necessário para o teste ser válido, usando a expressão:

 nMax := INT(SQRT(nTest))+1

Com isso o looping de testes realiza as operações de modulo apenas com a quantidade necessária de números primos para um resultado confiável, usando apenas uma parte da lista já calculada.

Custo x Desempenho

O teste consumiu com uma Thread (processo) de 34% a 50% de CPU de um Intel core i5 — Dual-Core HT — de 2.4 GHZ, obtendo em 72 segundos 539778 números primos, o último primo é o número 8000009.Com a rotina inicial, e aproximadamente 540 mil primos gerados em 72 segundos, isso dá uma média de 7946 números primos gerados por segundo.

Porém, o custo de uso de memória passa a ser um fator limitante para determinar números extensos, pois embora apenas uma parte da lista de primos gerada precisa ser usada para testar se um novo número da sequência é primo, todos os números gerados estão nesse array.

Existem várias alternativas interessantes para tirar um pouco o peso de consumo de memória do programa, por exemplo transferindo para disco ou banco de dados os números gerados, e mantendo na memória a lista dos primos menores que a raiz quadrada mas um do número a ser testado, com mais uns 10% de margem, para permitir fazer o cálculo de vários próximos números, e quando mais números primos forem necessários, a lista da memória pode ser complementada com a lista já calculada no banco de dados, bastando carregar mais alguns números 😀

Teste de primalidade

Para descobrir se um determinado número qualquer é primo, na faixa de 2 a 4294967296 ( aproximadamente 4,3 bilhões, máximo valor inteiro positivo de 32 bits) usando essa abordagem, eu preciso apenas da lista dos números primos até 65537 (Raiz quadrada de 4294967296 + 1 ), que na prática são apenas os primeiros 6543 números primos.  Isso é factível de ser gerado em apenas 1/10 segundo ou menos, e pode ser cacheado.

Agora, se você precisa descobrir se um determinado número é primo, numa faixa como 64 bits ( 18446744073709551616 ), aí lascou …. você precisa da lista dos primeiros 4,3 bilhões de números primos, que pode levar mais de 170 horas para ser calculada, e ocupar no mínimo 16 GB de RAM, e quanto maior o número — usando esta abordagem primária — bilhões de operações de divisões serão necessárias para confirmar ou descartar se ele é um número primo — que podem demorar de minutos a horas para serem executados. Normalmente se o número não é primo, algum descarte pode ocorrer ao varrer a lista, porém a confirmação se ele é primo exige todas as operações.

Outros métodos de confirmar a primalidade

Existem outros testes que podem ser feitos, na verdade existem outros tipos de teste de primalidade — determinísticos, probabilísticos e heurísticos. Maiores detalhes na página sobre testes de primalidade na Wikipedia (em Inglês), vide referências no final do post.

Os testes probabilísticos partem do sorteio de alguns números dentro de uma faixa para tentar determinar se um número é primo, porém este tipo de teste pode reportar que um número composto é primo. Já os testes determinísticos não tem erro, mas são bem mais difíceis de implementar, mas conseguem trabalhar com números muito grandes em segundos.

Encontrei por exemplo um teste de primalidade determinístico chamado “Adleman–Pomerance–Rumely” (ou APR), onde um algoritmo escrito em C usando a lib GMP ( GNU Multiprecision Library) , que consegue determinar em aproximadamente 50 segundos se um determinado número de 500 (quinhentos) dígitos é primo !!

Voltando ao AdvPL

Bem, se não vamos lidar com números astronômicos, um teste “ingênuo” como o da divisão por primos conhecidos pode ser seguramente implementado e rodar em um tempo satisfatório, usando alguns truques. Vamos escrever uma função que determina se um número entre 2 e 4294967296 é primo 😀

Primeiro, vamos montar um cache com os números primos de 2 a 65537, e guardá-lo em uma variável STATIC, assim ele será gerado na carga do código, e pode ser usado quantas vezes for necessário. Vamos ao fonte:

STATIC _Primos := PCache()

STATIC FUNCTION PCache()
Local nTest := 5
_Primos := {2,3}
While nTest <= 65537
	If isPrime(nTest)
		aadd(_Primos,nTest)
	Endif
	nTest += 2
Enddo
Return _Primos

STATIC Function ISPrime(nTest)
Local nMax := INT(SQRT(nTest))+1
Local nI := 1
While _Primos[nI] < nMax
	IF ( nTest % _Primos[nI] ) = 0
		Return .F.
	Endif
	nI++
Enddo
Return .T.

Um ponto bem interessante desse código é que ele usa internamente a própria função de teste de primalidade ( isPrime ) para montar o cache necessário para os demais testes. Eu preciso de todos os números primos do 2 até o 65537 ( 6543 números ) para testar um número até 4.3 bilhões, e isso a função de carga do cache — PCache() — faz em 1/10 de segundo. A lista já nasce com os primos 2 e 3, e os demais são calculados.

Usando posteriormente a função isPrime() para testar 4 milhões de números ímpares, na faixa de 3 a 8000001, todos os testes de primalidade rodaram em 74 segundos, encontrando nesta faixa 539776  números primos, um desempenho médio de aproximadamente 53 mil testes de primalidade por segundo !!!

USER Function primos()
Local nI         
Local nPrimos := 0
Local nTestes := 0
Local nTimer := seconds()
For nI := 3 to 8000001 STEP 2
	nTestes++
	if isprime(nI) 
		nPrimos++
	Endif
Next     
nTimer := seconds() - nTimer
Conout('Testes ............... '+cValToChar(nTestes))
Conout('Primos ............... '+cValToChar(nPrimos))
Conout('Tempo de Calculo ..... '+str(nTimer,8,3)+' s.')
Return

/* 
Resultado obtido:
Testes ............... 4000000
Primos ............... 539776
Tempo de Calculo .....   74.316 s.
*/

É um tanto quanto óbvio que, quanto maior o número a ser testado, maiores são as operações de verificação, e maior será o tempo. No exemplo acima, eu verifiquei 4 milhões de números, de uma faixa “baixa”, apenas os números impares até 8000000. Agora, se eu verificar por exemplo a faixa de números ímpares entre 1073741823 e 1074741823 — apenas meio milhão de números — o tempo total foi de 59 segundos … foram 8 vezes menos números, e o desempenho do teste de primalidade caiu para 8400 verificações por segundo — que mesmo assim, é bem rapidinho 😀

Conclusão

Eis que no final das contas trabalhar com números primos de até 4 bilhões não foi tão doloroso … o caldo engrossa quando os números são astronômicos — por exemplo uma chave criptográfica de 128 bits pode gerar … 3,41e+38 combinações , ou seja, um número bem grandinho (3,402,823,669,209,384,634,633,746,074 para ser exato — não sei nem falar isso). Para isso, os outros testes de primalidade são bem mais eficientes, mas até agora nenhum chegou a resolver o problema em tempo polinomial 😛

Espero que vocês tenham gostado do tema e das informações, e lhes desejo novamente TERABYTES DE SUCESSO !!!

Referências