Processo de certificação de software de gestão e código-exemplo em VB.NET (full .NET Fx & .NET CF)

As empresas que produzem software de gestão estarão neste momento a par da necessidade de a partir de 1 de Janeiro de 2011 disponibilizarem aos seus clientes novas versões das suas aplicações que implementem os requisitos necessários à chamada ‘certificação de software’. O mecanismo a implementar passa por no momento da gravação de um documento criar um hash a partir das suas características, baseado num certificado asimétrico, que garantirá que nenhum dos elementos importantes do documento é editado depois de emitido.

A moving2u, apesar de não criar software de gestão na perspectiva ‘stand alone’, dado que a nossa solução de mobilidade para PDAs integra sempre com um sistema de gestão de terceiros (actualmente contempla Primavera, PHC, Eticadata, SAP Business One, Gexor, entre outros), também é visada por este processo de certificação. Assim sendo, no sentido de garantir a legalidade da sua utilização por parte dos nossos clientes visados por essa regulamentação, encetámos os passos necessários ao seu cumprimento, no sentido de disponibilizar até ao final do ano uma nova versão que assine cada um dos documentos legais com o referido hash, que imprima a informação requerida pela portaria e que permita gerar o ficheiro SAFT-PT, que até agora era sempre emitido pelo sistema onde os documentos eram integrados.

Para o processo da assinatura em si, passo a descrever os passos necessários para a geração do hash em aplicações .NET:

- descarregar o OpenSSL para Windows para criar as chaves privada e pública e gerar um hash para teste

http://www.shininglightpro.com/products/Win32OpenSSL.html

- descarregar o OpenSSLKey para poder converter a chave privada no formato PEM no formato XML

http://www.jensign.com/opensslkey/ (link no final da página)

- criar a chave privada

openssl genrsa -out MinhaChavePrivada.pem 1024

- criar a chave pública a aprtir da chave privada

openssl rsa -in MinhaChavePrivada.pem -out MinhaChavePublica.pem -outform PEM -pubout

- exportar a chave privada de ‘pem’ para ‘xml’

Correr o opensslkey, indicar o caminho do ficheiro PEM, na consola obtem-se entre outras coisas a chave privada em formato XML, copiar essa parte para um ficheiro .XML

- gerar um hash utilizando o openSSL para testar. Ter em conta que o Windows acrescenta um CR+LF ao echo, pelo que o valor obtido será sempre diferente do que as finanças obteriam para a mesma string

echo 2010-05-18;2010-05-18T11:22:19;FAC 001/14;3.12; | openssl dgst -sha1 -sign MinhaChavePrivada.pem | openssl enc -base64 –A

- gerar um hash utilizando o openSSL para testar. Criar um ficheiro string.txt com o conteúdo da string a encriptar, sem linefeed no final!

copy con string.txt<ENTER>
2010-05-18;2010-05-18T11:22:19;FAC 001/14;3.12; <CTRL-Z><ENTER>

…e utilizar o seguinte comando

openssl dgst -sha1 -sign MinhaChavePrivada.pem string.txt| openssl enc -base64 –A

- código em VB.net (adicionar referência ao System.Security.Cryptography):

Imports System.Security.Cryptography
Imports System.Text

Module Module1

    Sub Main()
        ' descomentar o vbCrLf caso se pretenda obter um hash comparável com o obtido com o openssl em Windows 


        Dim stringToHash As String = "2010-05-18;2010-05-18T11:22:19;FAC 001/14;3.12; " ' + vbCrLf 


        Using privatekey As RSACryptoServiceProvider = New RSACryptoServiceProvider

            Dim privateKeyXML As String = System.IO.File.ReadAllText("C:\Trabalho\OpenSSL-Win64\bin\MinhaChavePrivada.xml")

            privatekey.FromXmlString(privateKeyXML)

            Dim buffer As Byte() = Encoding.GetEncoding("Windows-1252").GetBytes(stringToHash)
            Dim signature As Byte() = privatekey.SignData(buffer, "SHA1")

            Console.WriteLine(Convert.ToBase64String(signature))
        End Using
    End Sub
End Module


- A .NET CompactFramework 3.5 não suporta o método FromXmlString pelo que é preciso instanciar um objecto do tipo RSAParameters e preenchê-lo ‘manualmente’ (adicionar referências ao System.Xml e System.Xml.Linq):



Imports System.Security.Cryptography
Imports System.Text

Module Module1

    Sub Main()
        ' descomentar o vbCrLf caso se pretenda obter um hash comparável com o obtido com o openssl em Windows 


        Dim stringToHash As String = "2010-05-18;2010-05-18T11:22:19;FAC 001/14;3.12; " ' + vbCrLf 


        Using privatekey As RSACryptoServiceProvider = New RSACryptoServiceProvider
            Dim privateKeyXML As String = "..." ' carregar o xml da chave privada para esta string
            Dim xe As XElement
            xe = XElement.Load(privateKeyXML)

            Dim rsaP As RSAParameters = New RSAParameters() With {.Modulus = Convert.FromBase64String(xe.Element("Modulus").Value), _
                                                      .Exponent = Convert.FromBase64String(xe.Element("Exponent").Value), _
                                                      .P = Convert.FromBase64String(xe.Element("P").Value), _
                                                      .Q = Convert.FromBase64String(xe.Element("Q").Value), _
                                                      .DP = Convert.FromBase64String(xe.Element("DP").Value), _
                                                      .DQ = Convert.FromBase64String(xe.Element("DQ").Value), _
                                                      .InverseQ = Convert.FromBase64String(xe.Element("InverseQ").Value), _
                                                      .D = Convert.FromBase64String(xe.Element("D").Value)}
            privatekey.ImportParameters(rsaP)

            Dim buffer As Byte() = Encoding.GetEncoding("Windows-1252").GetBytes(stringToHash)
            Dim signature As Byte() = privatekey.SignData(buffer, "SHA1")

            Debug.WriteLine(Convert.ToBase64String(signature))
        End Using
    End Sub
End Module


Actualizado em 2010/09/08 para usar um ficheiro com a string da qual queremos gerar o hash sem o problema do Echo em Windows. Outra alternativa é utilizar um comando Echo portado do equivalente em Unix e que com o switch –n não gera o linefeed.



Todos os processos e o código apresentados servem apenas para exemplificar a forma de obtenção do hash do documento, sem ter em conta regras de boas práticas de desenvolvimento, performance ou segurança, não devendo ser considerado para produção. Obviamente serão declinadas quaisquer reclamações sobre a utilização do mesmo.



Ideias para o seu desenvolvimento foi obtido a partir do fórum ‘Portugal a Programar’ http://www.portugal-a-programar.org/forum/index.php/topic,48555.15.html onde sugiro que sejam discutidas questões relacionadas com este tópico



Este post não tem como objectivo defender ou criticar o processo da certificação de software, pelo que todos os comentários nesse sentido não serão publicados.

44 thoughts on “Processo de certificação de software de gestão e código-exemplo em VB.NET (full .NET Fx & .NET CF)”

  1. Este código ajudou bastante.
    Será possível colocar uma versão para verificar se a assinatura está bem gerada?
    Desde já obrigado

  2. A questão da verificação é simples: a encriptação é tão fiável que de certeza que se mandarem encriptar a mesma string 10000x, vão receber sempre o mesmo resultado. Ou seja, se houver erros, é na forma como geraram a string que concatena os diferentes valores, o que não é detectável pelo algortimo de geração do hash.

  3. Olá Alberto

    Não sei se já tens o teu software certificado, mas na string de exemplo que tens no final tens um espaço, tens de o tirar.

    Abraço

    Joaquim

  4. Olá Joaquim,
    Sim, já tinha lido sobre essa questão do espaço quando o documento é o primeiro da série, mas obrigado à mesma pelo alerta :)
    Ainda não fomos chamados a ir demonstrar o processo, mas não deve tardar muito!

  5. Agradeço desde já o pedaço de código que é bastante esclarecedor. Acontece que existe tem um campo no ficheiro de saft que é o HashControl com o numero 4.1.4.4 , e quanto a isto ha mto pouca informação. Como se gera o campo “HashControl” chave de controlo? obrigado

  6. Olá Nuno,
    Esse hashcontrol é para ser preenchido com a tal assinatura/hash gerado pelo código e exibido em output, o Convert.ToBase64String(signature).

    A portaria estabelece que passa a ter um comprimento de 200 caracteres.

  7. Correcção à minha mensagem anterior.

    De acordo com a redacção da Portaria 363/2010, o hash (4.1.4.3) é preenchido com a assinatura e o hashcontrol (4.1.4.4) com a versão do certificado utilizado:

    “Artigo 8.º
    Alteração à Portaria n.º 1192/2009

    1 – A nota técnica do campo 4.1.4.3 da estrutura de dados constante do anexo à Portaria n.º 1192/2009,
    de 8 de Outubro, passa a ter a seguinte redacção: «Assinatura nos termos da portaria que regulamenta a
    certificação dos programas informáticos de facturação. O campo deve ser preenchido com ‘0’ (zero),
    caso não haja obrigatoriedade de certificação.».

    2 – O formato do campo referido no número anterior passa a ser: «Texto 200».

    3 – A nota técnica do campo 4.1.4.4 da referida estrutura de dados passa a ter a seguinte redacção:
    «Versão da chave privada utilizada na criação da assinatura do campo 4.1.4.3». “

  8. Fátima,
    Sim, cada aplicação é responsável pela emissão do ficheiro SAF-T dos documentos por si produzidos.

    A nossa aplicação, moving2u m2uMobileSalesV3 e PrimaveraMobileBusinessV3, já está em curso de certificação e aguarda conclusão do processo, sendo agora capaz de emitir o seu proprio ficheiro SAF-T.

    Uma coisa que sinceramente não sei, é nos casos em que a empresa que produz o software para PDA é a mesma que produz o sistema de gestão utilizado pela empresa, se a aplicação PDA tem de ser explicitamente certificada com um certificado próprio, ou se pode partilhar o certificado com o sistema de gestão e neste caso ser emitido um único ficheiro SAF-T.

  9. Viva,

    Estamos neste momento no processo de certificação do nosso software mas estamos com um problema.
    Ao validar o saft gerado na aplicação da DGCI é indicado que a penas o 1º documento de cada série é válido. Pensamos estar a seguir todas as regras:
    2010-10-25;2010-10-25T22:39:24;173291 992010/1;192.00;
    2010-10-25;2010-10-25T22:55:31;173292 992010/2;13.72;QL0zPtTOL5LgxM5h+hOIl1g8GHxR6LBftPQ2K/EDJqIu1ZHXApy7db6Dlyc1rCIaw7uXhUV9UnRa9w1285jhrvHdiJpmAGfiJiL4qeyeh9tziEudkLo9FVTG7du6C9qJh8EIq+9w/TOL91/XqeYVZB1E5WI76Gn9K48dtuvyzMg=

    Este é um exemplo dos 2 primeiros registos.
    Mais alguem ja teve este problema?

    Cumprimentos

  10. Boas com este modo de gerar hash quando vou validar com o Validador das finanças ele dis que
    O ficheiro SAF-T tem 1 documentos comerciais para validar, dos quais 1 foram considerados inválidos. Os seguintes documentos não foram considerados validos de acordo com a chave publica fornecida:

    Eu segui-me pelo teu tutorial ao gerar a hash, já tentas-te correr uma exportação com o validador das finanças?

  11. Boas,
    Eu utilizei o código que publiquei para a assinatura dos documentos na nossa aplicação .NET Compact Framework, e os ficheiros gerados passaram no validador de SAF-T e no de validação da geração do hash, tendo-os submetido à DGCI há duas semanas, e estando a aguardar a marcação da ‘visita’.
    Obviamente que os vários cenários por nós testados não passaram todos logo à primeira, mas não tive de mexer no método da assintatura.
    Sei que não ter as definições regionais do PC onde se corre o validador definidas para o padrão de Portugal podem gerar erros desse tipo.

    Pedro, verifica se na string do 1º documento tens algum espaço depois do último ; se tiver,remove-a e testa de novo.

    Hélder, dá-te erro mesmo que esse documento seja o nº 1 da série?

  12. Viva,
    No caso do exemplo indicado, o problema parece-me que estará na colocação do número (é o nº do documento ?) antes da indicação da série/nº dentro da série. Penso que deveria ser FT/FAC/NCre/etc consoante seja Factura ou outro tipo de documento. Poderá ter de ser algo do tipo:

    2010-10-25;2010-10-25T22:39:24;FT 173291992010/1;192.00;

    2010-10-25;2010-10-25T22:55:31;FT 173292992010/2;13.72;QL0zPtTOL5LgxM5h+hOIl1g8GHxR6LBftPQ2K/EDJqIu1ZHXApy7db6Dlyc1rCIaw7uXhUV9UnRa9w1285jhrvHdiJpmAGfiJiL4qeyeh9tziEudkLo9FVTG7du6C9qJh8EIq+9w/TOL91/XqeYVZB1E5WI76Gn9K48dtuvyzMg= (hash já não seria esta aqui indicada; limitei-me a a fazer copy/paste)

    Cps

  13. Sim da . olha tens algum exemplo que possa por aki no site para confirmar se e o meu programa que esta a gerar mal?
    Tipo se poderes põe um exemplo de um saft com umas chaves testes assim da para verificar que e o meu algoritmo que esta a gerar mal , ou so as chaves e o texto e respectivo hash .

    cumprimento

  14. Pessoal, estou mesmo revirado com isto da certificação. Tenho a aplicação pronta a gerar o SAFT, mas ao validar com o programa da DGCI diz-me sempre que não consegue validar com a chave publica fornecida (já confirmei várias vezes que é a certa).
    No ficheiro xml está a mesma chave que na BD. Se gerar a chave manualmente, dá-me uma igual à que está no xml. Se verificar manualmente, diz-me Verification OK.
    Testes que efectuei:
    -a aplicação cria um ficheiro txt com o que vai usar para gerar a chave e outro ficheiro com a chave criada (e esta chave que está na BD e no xml. confirmei)
    -gerei um ficheiro bin a partir da chave gerada
    -comparei com o -verify e com a minha chave publica e dá Verification OK
    Meu ambiente: Win 7 Pro PT, VB 2005 Pro, openssl v1… Já mudei as opções regionais para usar o . como decimal mas não resulta.
    Help precisa-se urgentemente.
    Se alguém puder dar uma dica, fico eternamente grato. O meu obrigado antecipado.
    Cumprimentos a todos e bom trabalho!

  15. Viva.

    Palma, eu também estou exactamente com o mesmo problema. Numa tentativa de despistar a origem do mesmo, tentei o seguinte: para além de gerar a chave no software, gerei manualmente (via linha de comandos) e o hash bate certo e é verificado correctamente. No entanto, se eu gerar o hash em Linux (debian), o hash gerado é diferente, apesar de, se eu fizer verificação, esta é validada com sucesso (obviamente que estou a usar as mesmas chaves num lado e noutro). Já experimentei validar inclusive o ficheiro SAFT com os hashes gerados numa máquina e na outra, mas sempre sem sucesso.
    Por tudo isto, suspeito que eu esteja a ter algum problema com os encodings, mas não consigo descortinar onde.
    Alguma sugestão?

  16. Palma,

    Já descobri onde estava o meu erro. O software de validação do DGCI não valida o SAFT se o sistema estiver com ‘.’ no separador decimal e ‘,’ no agrupamento de milhares, mas sim o contrário. Portanto, para a validação ser “correcta”, isso deverá ser invertido (‘,’ para separação decimal e ‘.’ para agrupamento de milhares).
    Descobri isto após ter tentado validar o XML que está em http://info.portaldasfinancas.gov.pt/pt/apoio_contribuinte/CertificacaoSoftware.htm e o validador ter validado apenas 2 dos 10 documentos.
    Espero que isto ajude.

    Cumprimentos

  17. Quanto aos valores nao se utiliza a virgula nos milhares apenas o . no separador decimal, atenção.
    ex: 25 mil euros e 50 centimos seria 25000.50 e nao 25,000.00.

  18. Bom Dia, eu tenho o código e sempre me mostra erro na chave pública do SAFT-PT Validator, mas se eu enviar os arquivos hash com a chave privada demo de tal programa é perfeitamente válido. Alguém tem isso aconteceu? Eu criei as chaves privadas e públicas usando OpenSSL

    Obridago

  19. Olá Francisco,
    Tal como foi referido aqui e no fórum do Portugal a Programar, as definições regionais do PC onde corres o validador influenciam o resultado.

  20. Viva,

    Estou a implementar a certificação e estou com um problema na verificação com o Validador.

    O hash que o Openssl gera é exactamente igual ao que está no SAFT, no entanto diz sempre “Os seguintes documentos não foram considerados válidos de acordo com a chave pública fornecida”.

    No entanto por cmd dá Verified OK.

    Já verifiquei as definições da Região, mas continua igual, alguma sugestão como resolver o problema?

  21. Porque é que há difrenças na geração de HASH na documentação fornecida pela DGCI:

    1º Se utilizar os comandos fornecidos no PDF especificação das regras técnics ( echo “2010-05-18;2010-05-18T11:22:19;FAC 001/14;3.12; ” | openssl dgst -sha1 -sign ChavePrivada.pem | openssl enc -base64 ) devolve o hash ( Am1K5+CP4LDNVDZYvcLYGpnu8/1b+WWkzgoe8sbZhvk6QFzFvNN77Zsq+cHNm52jCVSEDgWLGHgPS1wcT8ZG7w6KgVq+2/VgOU+xKNt0lcC3gouyarZvcZpZclIReDgLh6m3nv8DYYHKAOQc+eCi/BQ4LqUnuJrca+7emgb/kpU= )

    2º Se utilizarmos os dados contidos no exemplo do SAFT-T fornecido pela DGCI ( echo “2008-03-10;2008-03-10T15:58:00;FT 1/1;28.07; ” | openssl dgst -sha1 -sign ChavePrivada.pem | openssl enc -base64 ) devolve o hash ( ifumkypK+Q/7JEtZxRZHNqmVkMm3sYIU5K9VcXdqR1DETnefdmQ/1q0ufl9qk5TQ
    5IO0LVjdNXFCV2kx3nb2vmsBdWfLT5h4HjKFPHp0T4pxedaWdC2arA4fa0wZpd/B
    zfXfkvpH/sGMYpWEpDhPtCu2DYvGrMb0c2Tu6SqInIs= ) em vez do hash que se encobtra no saft que é ( F8952fjEClltx2tF9m6/QTFynFjSuiboMslNZ1ag9oR5iIivgYYa0cNa0wJeWXlsf8QQVHUol303hp7XmIy5/kFOiV0Cv8QH6SF0Q5zNsDtpeFh2ZJ256y0DkJMSQqCq3oSka+9zIXXRkXgEsSv6VScCYv8VTlIcGjsablpR6A4= )

    Agradeço que me ajudem a esclarecer esta diferença de dados fornecida pela DGCI.

    Desde já cumprimentos a todos eobrigado pela atenção dispensada.

  22. ‘EU’,
    Do que vi, há várias coisas que influenciam o hash gerado. No entanto, eu não perderia tempo com o teste pelo openssl… porque não confrontar directamente o output do algoritmo da aplicação com o do validador de SAF-T?

  23. Usei os exemplos para gerar as chaves mas mesmo assim continuo com o problema na validação do hash no validador da DGCI. Testei o hash da minha aplicação com o do openssl e é igual. Estou a usar vb.net e uso o exemplo do código que tens no teu blog para gerar a assinatura do documento. A estrutura do saft dá-me como valida mas diz-me que alguns documentos não são validos de acordo com a chave publica fornecida. Se for possivel dar uma ajuda ou dica para resolver a situação agradeço.
    Obrigado

  24. @MC,
    As definições regionais do PC onde se corre o validador influenciam o resultado da validação, é a única situação que me lembro que possa influenciar o resultado dado que estamos a usar ‘precisamente’ este código na nossa aplicação.

  25. Olá,

    Já procedi à certificação do software e funciona bem, agora precisei por motivos que não interessam de receber dados via web-service e certificar um documento, tenho o seguinte, tal como no windows forms
    RSACryptoServiceProvider rsaPrivate = new RSACryptoServiceProvider(new CspParameters());

    rsaPrivate.FromXmlString(“string XML”);
    SHA1 sha1 = SHA1.Create();
    byte[] rawSecret = Encoding.UTF8.GetBytes(Text);
    byte[] signedSecretData = rsaPrivate.SignData(rawSecret, SHA1.Create());
    string final= Convert.ToBase64String(signedSecretData);

    NO windows forms certifica bem, aqui no web service dá excepção na linha do FromXmlString(“”) ((System.Security.Cryptography.CryptographicException)(ex1))

    {“O sistema não conseguiu localizar o ficheiro especificado.\r\n”}
    Que ficheiro é que possa faltar aqui, será bug da framework 2.0

    Obrigado
    João

  26. Ola, era so para agradecer este blog e todos os q ajudaram a simplificar o dito cujo certificado o “PAP” tambem.

    Novamente Obrigado. :)

    FuriousAngelPT

  27. Bom dia.

    A minha dúvida é algo mais básico do que tudo isto, mas é algo que não estou a perceber…

    Foi-me pedido para desenvolver um software de facturação com a exportação para SAFT-PT mas… As chaves, são me dadas pelas finanças? Sou eu que “invento” uma para gerar a outra?

    Obrigado,
    José Martins

  28. Boas novamente, espero ainda haver pessoas para ajudar. Pelos vistos nao esta bem o q fiz. utilizado o exemplo em cima e a linha de comando da resultados dão diferentes mas esta tudo direito.

    Alguem ainda me pode ajudar?

    Obrigado,

    FuriousAngel

  29. …..

    Ja vi onde esta o gato. em vez de enviar a HASH toda estava a enviar so os 4 caracteres q aparece no modelo de impressão. Devo ter alterado isso para testar algo e depois esqueci de alterar. :S

    Já funciona e ja enviei os dados para as finanças. Wish me luck.

    e obrigado na mesma por toda esta ajuda q meteram aqui.

    Furious Angel (a little less furious)

  30. Obrigado pela partilha!

    Sabe como é que se consegue desencriptar em VB ou c# para termos a certeza que a hash foi bem gerada?

    Mais uma vez obrigado.

    Pedro

  31. Pedro,
    Um hash é como o resultado da “prova dos 9″. Sempre que o calcular a partir dos mesmos dados, obtem o mesmo resultado, mas a partir do resultado não consegue obter os dados originais. Teoricamente é possível obter o mesmo hash a partir de dados diferentes.

  32. Viva Alberto Silva,

    Hoje tentei pela primeira vez converter a minha app de vb.net 2 para 4. tentei ler a tua informação que o FromXmlString não era suportado em 3.5, mas achei estranho (ou é de mim) porque encontrei. será que pode ser do sp1 do .net3.5 ? Isto era só um comentário. No .net 2 para 4 não tive problema nenhum.

    Se me permites gostaria de fazer o acesso via web services dos documento ( não tive fazer das guias de transporte ) e não percebo ainda nada do assunto. Podes-me indicar se não te importares um sitio / exemplo para começar ? Muito Obrigado

  33. A limitação que referi é na .NET Compact Framework, a versão destinada aos equipamentos Windows Mobile e Windows CE, e não na “full” .NET Framework :)

  34. Hola, por favor ¿alguien sabe cómo hacer el hashing y la asignatura del hashing en PowerBuilder 12.5 classic?, estoy intentando utilizar la Cryto API de Windows, pero no sé muy bien cómo utilizar mi chave privada en las funciones. Agradezco por favor algún comentario, me corre mucha prisa, tengo que hacer la certificación del software. Muchas gracias.

  35. Hola Olga,
    He buscado pero no encuentro ejemplos con PowerBuilder. No sé si sería posible utilizar un de los ejemplos en C#/VB6 o otro lenguaje para compilar un componente (DLL manejada, DLL COM, …) y llamarlo de PowerBuilder.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>