This is a re-write of a page looking at the Billing Software Certification (Certificação de Software Facturação) scheme introduced by the Portugal General Directorate of Taxes (Direcção Geral dos Impostos) (DGCI) in June 2010. You can read the relevant parts in our first version written in August 2010 which pointed out various errors and potential problems with the original document [ESPECIF-2010]. This page incorporates information from the 1st Addendum [ADIT-2010] and related test vectors released by the DCGI. The code provided shows how these can be reproduced exactly.
Introduction | The general idea | Examples from the sample XML file | The Code | Functions to create and verify signatures | Functions to create keys and make X.509 certificate | What we like about the specification | My-key-is-invalid-because-it-has-278-bytes-instead-of-272 | Delphi code | References | Contact us
2010-11-25: We have completely re-written our original code using the CryptoSys PKI Toolkit. The code now shows how to do things properly. It can successfully reproduce the new test vectors published by the DGCI on their web site. The code also shows how to create RSA public and private keys in the correct format and how to create a self-signed X.509 certificate containing the public key.
We know of at least one user who has successfully used the CryptoSys PKI Toolkit to make an application certified by the DGCI. Well done António of AMMSoftware.
We hope our revised code on this page will be of help to other developers.
We emphasise that we have no connection whatsoever with the Portuguese DGCI, and this site is in no way endorsed by them.
And we do not speak Portuguese at all. Sorry. Nós não falam Português. Pedimos desculpas. We understand enough to figure out technical documents (with lots of help from Google Translate).
The general idea is to create a signature of the invoice details in a database and
store this value as a base64-encoded string in a <Hash>
field in the same record
(the word "hash" is usually used in cryptography to refer to a message digest value, but here it means "signature value").
A string is created by concatenating the fields (InvoiceDate, SystemEntryDate, InvoiceNo, GrossTotal) for the invoice record separated by semicolons ";". Then the Hash value from the previous record is appended. If this is the first record in the series or for the year, then the Hash field is left empty.
This string is signed by the RSA algorithm using PKCS#1v1.5 EMSA with SHA-1 as the digest algorithm (rsa-sha1).
The <Hash>
value is the base64-encoded value of the 1024-bit "raw" signature value created by the RSA algorithm.
A base64 string consists of the characters [a-zA-Z0-9/+]
terminated by none, one or two "=" padding characters.
The length should always be divisible by four.
For a 1024-bit key, the signature will always result in a 172-byte string of base64 characters ending with exactly one "=" character.
The DGCI insists that there should not be any spaces in this string, so make sure there aren't any.
Take the first record in the sample XML file (this extract starts at line 9452)
<Invoice> <InvoiceNo>FT 1/1</InvoiceNo> <InvoiceStatus>N</InvoiceStatus> <Hash>F8952fjEClltx2tF9m6/QTFynFjSuiboMslNZ1ag9oR5iIivgYYa0cNa0wJeWXlsf8QQVHUol303hp7XmIy5/kFOiV0Cv8QH6SF0Q5zNsDtpeFh2ZJ256y0DkJMSQqCq3oSka+9zIXXRkXgEsSv6VScCYv8VTlIcGjsablpR6A4=</Hash> <HashControl>1</HashControl> <Period>3</Period> <InvoiceDate>2008-03-10</InvoiceDate> <InvoiceType>FT</InvoiceType> <SelfBillingIndicator>0</SelfBillingIndicator> <SystemEntryDate>2008-03-10T15:58:00</SystemEntryDate> <TransactionID>2008-03-10 VND 1</TransactionID> <CustomerID>123456789</CustomerID> <ShipFrom> <DeliveryDate>2008-03-10</DeliveryDate> <Address> <AddressDetail>Av. Duque D´ Avila, 71</AddressDetail> <City>Lisboa</City> <PostalCode>1000-013</PostalCode> <Region>Porto</Region> <Country>PT</Country> </Address> </ShipFrom> <Line> <LineNumber>1</LineNumber> <ProductCode>FAT01</ProductCode> <ProductDescription>Fato de algodão de mulher</ProductDescription> <Quantity>5</Quantity> <UnitOfMeasure>UN</UnitOfMeasure> <UnitPrice>4.6788</UnitPrice> <TaxPointDate>2008-03-10</TaxPointDate> <Description>Fato de algodão de mulher</Description> <CreditAmount>23.3938</CreditAmount> <Tax> <TaxType>IVA</TaxType> <TaxCountryRegion>PT</TaxCountryRegion> <TaxCode>NOR</TaxCode> <TaxPercentage>20</TaxPercentage> </Tax> <SettlementAmount>1.6062</SettlementAmount> </Line> <DocumentTotals> <TaxPayable>4.68</TaxPayable> <NetTotal>23.39</NetTotal> <GrossTotal>28.07</GrossTotal> <Currency> <CurrencyCode>EUR</CurrencyCode> <CurrencyDebitAmount>28.07</CurrencyDebitAmount> </Currency> <Settlement> <SettlementAmount>0</SettlementAmount> <SettlementDate>2008-03-15</SettlementDate> </Settlement> </DocumentTotals> </Invoice>
The input string for this entry consists of the fields (InvoiceDate, SystemEntryDate, InvoiceNo, GrossTotal, Hash) separated by a semicolon ";" with no extra spaces, no quotation marks, and no extra CR or LF characters:
2008-03-10;2008-03-10T15:58:00;FT 1/1;28.07;
Since this is the first record of the series, the Hash field is left empty in the input string,
so the last character in the string is a semicolon ";" .
Remember the Hash in this string is the value of the previous record in the database.
We will use this string to compute the value of the <Hash>
field for the XML file.
This string is signed using the RSA algorithm and the sample DGCI private key Chave_Privada.txt. The output is a 172-character string of base64 characters:
F8952fjEClltx2tF9m6/QTFynFjSuiboMslNZ1ag9oR5iIivgYYa0cNa0wJeWXlsf8QQVHUol303hp7XmIy5/kFOiV0Cv8QH6SF0Q5zNsDtpeFh2ZJ256y0DkJMSQqCq3oSka+9zIXXRkXgEsSv6VScCYv8VTlIcGjsablpR6A4=
and this is the value to be stored in the <Hash>
field for this record.
<Invoice> <InvoiceNo>FT 1/2</InvoiceNo> <InvoiceStatus>N</InvoiceStatus> <Hash>wh0uUgI/fLTt9Kpb/hFwN6VIkjWZWI8R2TxtHUMyRL0a7hyQLIvoxuqGzKfzUfvAV3E1gxpKZtai5qli6Nx7unqzC4vIoc6rtb3ObuxifXiBAUD95BMh31T73O6cgcwhGR0YhiV/E6jfCbihJL2B/2s+/qsaL7OY/bU651c3va0=</Hash> <HashControl>1</HashControl> <Period>9</Period> <InvoiceDate>2008-09-16</InvoiceDate> <InvoiceType>FT</InvoiceType> <SelfBillingIndicator>0</SelfBillingIndicator> <SystemEntryDate>2008-09-16T15:58:00</SystemEntryDate> <TransactionID>2008-09-16 VND 2</TransactionID> <CustomerID>123456789</CustomerID> <ShipTo> <DeliveryDate>2006-03-17</DeliveryDate> </ShipTo> <!-- // CUT // --> <DocumentTotals> <TaxPayable>39.19</TaxPayable> <NetTotal>195.96</NetTotal> <GrossTotal>235.15</GrossTotal> <Currency> <CurrencyCode>EUR</CurrencyCode> <CurrencyDebitAmount>235.15</CurrencyDebitAmount> </Currency> <Settlement> <SettlementAmount>0</SettlementAmount> <SettlementDate>2008-10-16</SettlementDate> </Settlement> </DocumentTotals> </Invoice>
The input string for this entry is:
2008-09-16;2008-09-16T15:58:00;FT 1/2;235.15;F8952fjEClltx2tF9m6/QTFynFjSuiboMslNZ1ag9oR5iIivgYYa0cNa0wJeWXlsf8QQVHUol303hp7XmIy5/kFOiV0Cv8QH6SF0Q5zNsDtpeFh2ZJ256y0DkJMSQqCq3oSka+9zIXXRkXgEsSv6VScCYv8VTlIcGjsablpR6A4=
Note we have carried forward the hash/signature value from the previous record, so the signature from the previous record is incorporated in the signature for the current record.
The rsa-sha1 signature from this input is:
wh0uUgI/fLTt9Kpb/hFwN6VIkjWZWI8R2TxtHUMyRL0a7hyQLIvoxuqGzKfzUfvAV3E1gxpKZtai5qli6Nx7unqzC4vIoc6rtb3ObuxifXiBAUD95BMh31T73O6cgcwhGR0YhiV/E6jfCbihJL2B/2s+/qsaL7OY/bU651c3va0=
which is the value of the <Hash>
field for this record.
Here is The Code (zipped, 56 kB) with the functions described below (last updated 2010-11-26). It includes example code in VB6 (PortugalTax2.bas), VB.NET (PortugalTax2.vb) and C-sharp (PortugalTax2.cs). The zipped file includes the DGCI sample XML and keys available from the DGCI web site.
The code uses our CryptoSys PKI Toolkit. A free, fully-functional trial version can be downloaded from the CryptoSys PKI Toolkit web site.
These programs all output various messages to the Immediate Debug window, so they need to be run in IDE mode. You should be able to adapt it to your own projects quite easily.
OPEN_SSL
public key file in what should
be the "correct" Unix format.
You should remove the calls to the FixFileDosToUnix
function in the above example code.
These functions create and examine signatures for the <Hash>
field.
They do it "properly".
Compute the correct signature values for the examples given in [ESPECIF-2010]. See also Correct Values in version 1 of this page.
1: OpE9IFpK5cJO8SwC5BUy3XTCkjVK5JsjHo3TvWjM9D09aw9wabH+sGNOs7hx4iEoOP9UY6DGsR6PgIkAZSTYInhbgs2x9sxWkr417aCKoSGY4awDIVB9aUlQ91SseH3Hk5S24PfjXFDn44acWhQL4INp9Re+dC51YNC7MrpAmP4= 2: hsR2TYJtl0mad+zVAhGxNLxs6matD+T8Y8IpEo12I3szSohdwwWVOfPclnu6D23pZ0w8g/Eh0TOMzYNsdkkUJpM68/nKH2d8ehI8HT85NUyLgrGhC8msXHK+ASCCOU0RN4mr04249IG+MuOAlnW8EcMJNZA+lTf94MbpJNqRYUw=
Reproduce the signatures (<Hash>
) values in SAFT_IDEMO599999999.XML
using the sample DGCI private key Chave_Privada.txt.
FT 1/1: F8952fjEClltx2tF9m6/QTFynFjSuiboMslNZ1ag9oR5iIivgYYa0cNa0wJeWXlsf8QQVHUol303hp7XmIy5/kFOiV0Cv8QH6SF0Q5zNsDtpeFh2ZJ256y0DkJMSQqCq3oSka+9zIXXRkXgEsSv6VScCYv8VTlIcGjsablpR6A4= FT 1/2: wh0uUgI/fLTt9Kpb/hFwN6VIkjWZWI8R2TxtHUMyRL0a7hyQLIvoxuqGzKfzUfvAV3E1gxpKZtai5qli6Nx7unqzC4vIoc6rtb3ObuxifXiBAUD95BMh31T73O6cgcwhGR0YhiV/E6jfCbihJL2B/2s+/qsaL7OY/bU651c3va0= FT 1/3: iVYbEDuefMedP5DHBfl+pao72LsahJFyNCvnoS4ybJ4+gfnnLhGYDQLT5+cSrIIXwpH2ZtJHzW9aFOzZeyZUIUqRNeuvJ9xbKWgCc52Hz04D4BN3R+XCw8gSCLeuD2EQW+Eesnd03cfElYSRHg2v4LSypN2QoA3yZ4+0oX3qdxY= ... FT 1/6: V5HNew6rKFxmSeNTSmp5hrleL4Li0LwK/x/+5bGHfleGLGC1U0Vr7QkEQnKREIinP2KUAnaB41XQcmHufcS+8lxJskDGlJD6c6ydP6pYFpNGALYP/AHWCUMSgpNfw0nhXeUW+i9Lx9PfrIjksygCTx7oFB8serLlAqTsAdmi9WU= NC 1/1: jTCuqNUzz+QDJiHeOGwkJzBoJwqNOLRMs0ISI7TXddv5RrH8KmKtaMgzaZxWY9QO4U5aoasqHRieqof+7oXq0fALKcROyVxU/PQRsh7eKani46ENkrkQNXREjAdz1nvoCSAKphd21nfMJupWlYTAJV2H0A7I+MGcDpQ3kO770ko= NC 1/2: YIt8KKn+0m9HpK2BpsnYtHQXXnD2FMm2UkpRNcODiWOfu5p/JRxK4hSkbIf5HMpO6p7+Q0E6wtshdbfRDtq1xtgU6mZtTEqfS/pY1pPaaoWIyc1I925SzMCc66QMbPLXLadHMbAgQr87PP2BXXWmDcoppaxR+bvuvfxhM7re2SU=
Use the corresponding public key Chave_Publica.txt to verify signatures
Public key size is 1024 bits. Signature block length = 128 bytes 17CF79D9F8C40A596DC76B45F66EBF4131729C58D2BA26E832C94D6756A0F684798888AF81861AD1C35AD3025E59796C7FC410547528977D37869ED7988CB9FE414E895D02BFC407E92174439CCDB03B69785876649DB9EB2D0390931242A0AADE84A46BEF732175D1917804B12BFA55270262FF154E521C1A3B1A6E5A51E80E RSA_RawPublic returns 0 (0 => success) 0001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF003021300906052B0E03021A05000414BB5C0F8FF294016FA4F0A3265410249D275B0986 Message digest is 20 bytes long EXTRACTED DIGEST=BB5C0F8FF294016FA4F0A3265410249D275B0986 COMPUTED DIGEST =bb5c0f8ff294016fa4f0a3265410249d275b0986 Result=OK Public key size is 1024 bits. Signature block length = 128 bytes 86C4764D826D97499A77ECD50211B134BC6CEA66AD0FE4FC63C229128D76237B334A885DC3059539F3DC967BBA0F6DE9674C3C83F121D1338CCD836C76491426933AF3F9CA1F677C7A123C1D3F39354C8B82B1A10BC9AC5C72BE012082394D113789ABD38DB8F481BE32E3809675BC11C30935903E9537FDE0C6E924DA91614C RSA_RawPublic returns 0 (0 => success) 0001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF003021300906052B0E03021A050004140196B4E57465F02408099FD5897AC769B052083F Message digest is 20 bytes long EXTRACTED DIGEST=0196B4E57465F02408099FD5897AC769B052083F COMPUTED DIGEST =0196b4e57465f02408099fd5897ac769b052083f Result=OK
Extract the message digest from a given signature. (Use this in your debugging)
DIGEST FOUND=BB5C0F8FF294016FA4F0A3265410249D275B0986 EXPECTED =BB5C0F8FF294016FA4F0A3265410249D275B0986
Example to read in key files directly as a string. Use this as an alternative to passing filenames. The CryptoSys PKI RSA_Read* functions will also accept a string containing the file contents. You must still use the RSA_Read* functions to obtain the ephemeral "internal" key strings to use with the RSA_Raw* functions.
<Hash>=F8952fjEClltx2tF9m6/QTFynFjSuiboMslNZ1ag9oR5iIivgYYa0cNa0wJeWXlsf8QQVHUol303hp7XmIy5/kFOiV0Cv8QH6SF0Q5zNsDtpeFh2ZJ256y0DkJMSQqCq3oSka+9zIXXRkXgEsSv6VScCYv8VTlIcGjsablpR6A4= DIGEST EXTRACTED=BB5C0F8FF294016FA4F0A3265410249D275B0986 Result=OK
These functions show how you can generate an RSA key pair compatible with the OpenSSL PEM format, and how to use the keys to create an X.509 certficate.
In CryptoSys PKI the private key is generated in encrypted form secured by a password. We show how this can be converted to the unencrypted OpenSSL PEM format and vice versa.
CAUTION: saving or keeping a production private key in unencrypted form is a huge security risk!
Create an RSA key pair and do some checks on the resulting files. Note that this will give different values for the keys each time you run it.
Creating keys. This may take a few seconds... RSA_MakeKeys returns 0 (expected 0) Input is 278 bytes long Output is 272 bytes long RSA_SavePrivateKeyInfo returns 0 (expected 0) Private key is 1024 bits Public key is 1024 bits Hashcodes are BD978004 and BD978004 RSA_KeyMatch returns 0 (0 => keys match)
Returns TRUE if the public and private keys in the given files match or FALSE if they do not.
Save an unencrypted OpenSSL private key in PKCS-8 encrypted form.
Use the RSA key file to make a self-signed X.509 certificate containing the public key.
Creating self-signed X.509 certificate serial number 0x101 for subject 'C=PT;O=Exemplo Organização;CN=Certificado auto-assinado' X509_MakeCertSelf returns 0 (expected 0)
Query an X.509 certificate for selected information.
For certificate file Pt_SelfSigned.cer serialNumber=0101 subjectName=C=PT;O=Exemplo Organização;CN=Certificado auto-assinado notAfter=2020-11-25T01:45:01Z
Get the public key in <RSAKeyValue> XML form.
Read the public key from both the original public key file and from the X.509 certificate we created. Display some info about the key.
Public key is 1024 bits HashCode=0x19DB1130 <RSAKeyValue><Modulus>wWtxdohSwp6p660m7AyzR0DD3V55CB4UZmONZVzi1jtodIkaAMzVNNGJGTmhsp8L3u0xy8q+SE3ebVEt3DS5RJoKvoQ+Wlqd3XZO5DXkcrTMWqpW54WjqaqS73+BS9tjnRaEWUFqZKqaufxEFqtmBcECPQt9RsmEq/z7U6XYWDk=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue> Public key is 1024 bits HashCode=0x19DB1130 <RSAKeyValue><Modulus>wWtxdohSwp6p660m7AyzR0DD3V55CB4UZmONZVzi1jtodIkaAMzVNNGJGTmhsp8L3u0xy8q+SE3ebVEt3DS5RJoKvoQ+Wlqd3XZO5DXkcrTMWqpW54WjqaqS73+BS9tjnRaEWUFqZKqaufxEFqtmBcECPQt9RsmEq/z7U6XYWDk=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>
2010-10-26: To fix this problem, use a text editor to remove the newline characters so that the 278-byte file like this is converted to the 272-byte file like that which has the base64 characters all on one line. This apparently fixes the problem. You can also fix by converting each of the six CR-LF pairs to a single LF (0x0A) and so converting a "DOS" file into a "UNIX" one.
Note: This shows a complete lack of understanding of the PEM format by the people who designed the code to do the "check" on this. The white space in a PEM file should not matter! (Thanks to António Mendes for his help on this.)
OPEN_SSL
public key file in what should
be the "correct" Unix format.
You should remove the calls to the FixFileDosToUnix
function in the above example code.
Here is some code in Delphi (zipped, 8 kB) that computes the signature value using the Delphi interface to CryptoSys PKI. This code has been kindly provided by Pedro Miguel Martins <www.mch-infor.pt>. Update 2011-04-17: Pedro informs us
“ Just to confirm that the delphi code that I sent to you is working and the software that used that is certificated by Portuguese DGCI. ”
Thank you to Pedro for making this Delphi code available.
Questions? Errors? Comments? Suggestions? Legal notices? Please send us a message.
This page re-written by DI Management 25 November 2010. Last updated 13 June 2020.