Our CryptoSys PKI Toolkit provides all the functions you should need to carry out the basic cryptography operations for the security interface for data exchange in the German health service (Security Schnittstelle für den Datenaustausch im Gesundheits- und Sozialwesen). This page shows how to do it with example source code in VB6/VBA and C#.
GermanHealthExamples2018.cs
- Examples in C# using CryptoSys PKI to create and read signed-and-enveloped PKCS7 (CMS) objects.
GermanHealthExamples2018.bas
- Same again in VBA/VB6.GermanHealth2018Files.zip
.GermanHealthSetup2019.cs
- Creating your own public/private key pair and getting a certificate from your Certification Authority.
“
CryptoSys PKI Toolkit erfüllt alle Voraussetzungen, die notwendig sind, gemäß der Security Schnittstelle
für den Datenaustausch im Gesundheitswesen Version 1.5,
um mit den Datenannahmestellen der gesetzlichen Krankenkassen und dem ITSG-Trustcenter zu kommunizieren.
Anders als bei DAKOTA stehen hier alle notwendigen Prozeduren in einer einzigen DLL zur Verfügung.
Für das Erstellen der Zertifizierungsanfrage, dem Einlesen der Zertifizierungsantwort,
dem Signieren / Verschlüsseln der Nachricht und der Speicherung der Daten
(Zertifikate, privater Schlüssel, Annahme-pkcs.key) sind Beispiele in VB vorhanden.
Sowohl der Zertifikatsantrag (PKCS#10 Format) beim ITSG-Trustcenter als auch die
Datenübermittlung (PKCS#7 Format) an AOK, IKK, BKK, LKK, Knappschaft wurden erfolgreich durchgeführt.
CryptoSys PKI Toolkit wird im Leistungserbringerverfahren und im Arbeitgeberverfahren erfolgreich eingesetzt.
”
“ Just to update you, after last changes, now everything is going on like a music. ”
“ We have used CryptoSys PKI for all data exchange with health insurance ever since the transition from PEM to PKCS/CMS some fifteen years ago, and it has always performed flawlessly.
That is why we are pleased that we can now use this tried and trusted toolkit for electronic prescriptions as well, instead of having to deal with the unreliable mess of cryptographic support in .NET and Windows. ”
—Rechenzentrum für Apotheken Hildegard Schröter GmbH, Lutherstadt Wittenberg, Germany.
string outputFile = "To_999009051a.p7m"; string inputFile = "To_999009051a.p7m.int.tmp"; string recipCertFile = "999009051a.cer"; int r = Cms.MakeEnvData(outputFile, inputFile, recipCertFile, CipherAlgorithm.Aes256, Cms.EnvDataOptions.None); // Debugging: Console.WriteLine("Cms.MakeEnvData returns {0}", iRet); // 1 string s = Cms.QueryEnvData(outputFile, "contentEncryptionAlgorithm"); Console.WriteLine("contentEncryptionAlgorithm = {0}", s); //aes256-CBCIn VBA/C:
nRet = CMS_MakeEnvData(strOutputFile, strSigFile, strRecipCertFile, "", 0, PKI_BC_AES256)
The basic procedures to be carried out break down into the following tasks.
Of these, the first three only need be done once.
This page is an update for the latest 2014 version of the ITSG specification version 3.0, published 2014-03-27 [ITSG-2014]. This page was originally written in 2008 for version 1.5. The major changes made since then in the latest version 3.0 are as follows.
Make_Key_CheckSum()
change the hash algorithm used to compute the checksum of the public key
from MD5 to SHA-1.
nRet = HASH_HexFromFile(strDigest, Len(strDigest), strPublicKeyFile, PKI_HASH_SHA1)
Make_Certificate_Request()
change the signature algorithm for the
certificate request from
sha1WithRSAEncryption to sha256WithRSAEncryption
nRet = X509_CertRequest(strCrsFile, strPrivateKeyFile, strDN, "", strPassword, PKI_X509_FORMAT_BIN + PKI_SIG_SHA256RSA)
Make_Signed_And_EnvelopedData()
change the digest algorithm used for the signature from SHA-1 to SHA-256.
nRet = CMS_MakeSigDataFromString(strSigFile, strMsg, strSignersCertList, strPrivateKey, PKI_CMS_INCLUDE_ATTRS + PKI_CMS_ADD_SIGNTIME + PKI_HASH_SHA256)
Make_Signed_And_EnvelopedData()
to use AES-256.
nRet = CMS_MakeEnvData(strOutputFile, strSigFile, strRecipCertFile, "", 0, PKI_BC_AES256)
Thanks to Tanmay Mukherjee for his help on the latest version.
We are told it is important to send the Auftrag (AUF) file along with the encrypted data.
Without this file you cannot do the exchange.
This must include the correct encryption type 8347I8000303
. The "303" is important.
5000000100000348000TPFL0003 500825234 500825234 108018007 108018007 000000000000PL114004SAO201411071448000000000000000000000000000000000000000000000000000000000004350000000008347I8000000 0000000000000 0000000000000 00
5000000100000348000TPFL0003 500825234 500825234 108018007 108018007
000000000000PL114004SAO201411071448000000000000000000000000000000000000000000000000000000000004350000000008347I8000303
0000000000000 0000000000000 00
The above example is for a message to an insurance company. There are other variants. For more information, see this document: Richtlinien für den Datenaustausch im Gesundheits- und Sozialwesen. (Thanks again, Tanmay).
We must point out that we are completely independent of the ITSG Trustcenter and nothing on this site has been approved in any way by that organisation. The contents of this page are based on discussions we've had with users of our CryptoSys PKI Toolkit who have successfuly used it to create applications they claimed worked.
Our examples use test certificates and private keys that we made up ourselves. In no way do we intend to represent ourselves as trying to pass off as ITSG or any associated firm. We hope the names we use in our examples appear obviously false, as they were intended to be. The individual's names used in the test X.509 certificates were generated from random combinations of typical German first names and surnames. And just so there is no doubt,
The characters and organisations depicted in these examples are fictitious. Any similarity to actual persons, living or dead, or to actual organisations is purely coincidental.
Apologies, too, that this is in English. We are English speakers and can't write German to an acceptable standard. Well, actually, not to any standard. We got stuck at "Zwei Biere, bitte" (and even then we're told this may not mean what we thought). We can, though, make a pretty good stab at understanding the technical documents involved here.
The source code provided uses dummy test files that we made up ourselves. The files should be in a similar format to the real ones. You will need to tailor the code to your own circumstances and identity and substitute the file names and folders to match the ones on your system. If you find a problem, please let us know.
RSA_MakeKeys
function
or the
Rsa.MakeKeys
method.
You currently need a key of length 2048 bits and should use an exponent e=65537
.
You will need to supply a password at this stage. This is used to protect your private key, so make it something decently long and secure,
and keep it secret.
' Set filenames to be created strPublicKeyFile = TESTPATH & YOUR_PKID & "_pub.p1" strPrivateKeyFile = TESTPATH & YOUR_PKID & "_pri.p8" ' Set your password - use something decent! strPassword = "password" ' Create a new pair of 2048-bit RSA keys saved as binary BER-encoded files nRet = RSA_MakeKeys(strPublicKeyFile, strPrivateKeyFile, 2048, _ PKI_RSAEXP_EQ_65537, 50, 1000, strPassword, "", 0, PKI_KEYGEN_INDICATE)
string pubKeyFile = YOUR_PKID + "_pub.p1";
string priKeyFile = YOUR_PKID + "_pri.p8";
string password = "password";
int n = Rsa.MakeKeys(pubKeyFile, priKeyFile, 2048, Rsa.PublicExponent.Exp_EQ_65537, 2000, password, Rsa.PbeOptions.Default, true);
This function creates two files: your public key in what we call here a .p1
file and an encrypted
private key in a .p8
file.
(The .p8 extension is the convention for PKCS#8 key files.)
This process takes a minute or so, depending on the speed of your machine. Every time you do this you will create a new RSA public/private key pair. Each one will be different. You will never be able to reproduce the same values again. If you accidentally run this process again and overwrite your key files, you will invalidate all the procedures that follow. Be careful.
X509_CertRequest
function or the
X509.CertRequest
method.
Use the sha256RSA signature algorithm (formerly sha1RSA).
strDN = "C=DE;O=ISTG Beispiel Vertrauen Mitte;OU=Unsere Firma;OU=IK999009991;CN=Erika Mustermann"
strPrivateKeyFile = TESTPATH & YOUR_PKID & "_pri.p8"
strPassword = "password"
strCrsFile = TESTPATH & YOUR_PKID & "1.p10"
nRet = X509_CertRequest(strCrsFile, strPrivateKeyFile, strDN, "", _
strPassword, PKI_X509_FORMAT_BIN + PKI_SIG_SHA256RSA)
string reqFile = YOUR_PKID + ".p10";
string priKeyFile = YOUR_PKID + "_pri.p8";
string password = "password";
string distName = "C=DE;O=ISTG Beispiel Vertrauen Mitte;OU=Unsere Firma;OU=IK999009991;CN=Erika Mustermann";
int n = X509.CertRequest(reqFile, priKeyFile, distName, password, X509.Options.SigAlg_Sha256WithRSAEncryption);
To submit to ITSG, you need to generate a binary file and you need to specify a valid Distinguished Name. Here is what we have used in our dummy test examples and what we have been told that ITSG actually require. Please make your own checks before submitting your data to them. You may be charged a fee if you are wrong. Don't blame us!
Field Name | What we have used in our dummy test example | What ITSG probably expects |
---|---|---|
countryName | C=DE | C=DE |
organizationName | O=ISTG Beispiel Vertrauen Mitte | O=ITSG TrustCenter fuer sonstige Leistungserbringer |
organizationalUnit | OU=Unsere Firma | OU=(your company) |
organizationalUnit | OU=IK999009991 | OU=IK(ID number) |
commonName | CN=Erika Mustermann | CN=(name of the person responsible) |
These keys need to be put into a string in the correct order, separated by a semi-colon character (;), with no extra spaces and no umlaute or essetz characters (use the "ae", "oe", "ue" and "ss" forms). The order of field names is important and, yes, there are two "OU" fields, one for your company name and one for your IK id number, in that order.
The distinguished name string for our dummy example above would be
"C=DE;O=ISTG Beispiel Vertrauen Mitte;OU=Unsere Firma;OU=IK999009991;CN=Erika Mustermann"
The misspelling of "ISTG" in our example is deliberate on our part. Fill in your own correct details as required.
HASH_HexFromFile
function
with the PKI_HASH_SHA1
option. (The hash algorithm used to be MD5 before 2014).
strPublicKeyFile = TESTPATH & YOUR_PKID & "_pub.p1" strDigest = String(PKI_SHA1_CHARS, " ") nRet = HASH_HexFromFile(strDigest, Len(strDigest), strPublicKeyFile, PKI_HASH_SHA1) Debug.Print "SHA1(PublicKey)=" & strDigest
In C# use Hash.HexFromFile
.
string pubKeyFile = YOUR_PKID + "_pub.p1";
string digestHex = Hash.HexFromFile(pubKeyFile, HashAlgorithm.Sha1);
Console.WriteLine("SHA1(PublicKey)=" + digestHex);
In our dummy example file 999009991a_pub.p1
, we obtain the hash value in hex
SHA1(PublicKey)=c362f3bea237c09837167571d42eaf64ee3f4ae5
You can check this independently by computing the SHA-1 digest of the file itself.
Note: The contents of the .p1 public key file is exactly the same as the contents of the entire BITSTRING element extracted from the subjectPublicKeyInfo structure in the X.509 certificate (cheers, Robin).
annahme-sha256.key
. It is a text file and can be viewed with any text file editor, including NotePad.
We show a procedure in the source code to extract all these certificates and examine their details.
To use with CryptoSys PKI you need to extract your X.509 certificate from the .p7c file you receieved.
Use the
X509_GetCertFromP7Chain
function.
Public Sub Split_p7c_file() ' Extracts all X.509 certificates from a PKCS#7 certificate list file ' INPUT: Name of pkcs7 certificate list file containing sender's X.509 certificate and certificates of all issuers. ' OUTPUT: Set of X.509 certificate files: TheCert1, TheCert2, TheCert3, etc. Dim nRet As Long Dim strListFile As String Dim strCertFile As String Dim iCert As Long Dim nCerts As Long strListFile = TESTPATH & YOUR_PKID & ".p7c" ' How many certificates? nCerts = X509_GetCertFromP7Chain("", strListFile, 0, 0) Debug.Print "X509_GetCertFromP7Chain(0) returns " & nCerts & " for " & strListFile ' Enumerate through them all If nCerts > 0 Then For iCert = 1 To nCerts strCertFile = TESTPATH & "TheCert" & iCert & ".cer" nRet = X509_GetCertFromP7Chain(strCertFile, strListFile, iCert, 0) Debug.Print "X509_GetCertFromP7Chain(" & iCert & ") returns " _ & nRet & "->" & strCertFile Next End If ' But we don't know which one is ours...so see the next procedure ' (it's most likely that the first one is yours, but we'll check anyway) End Sub
string listFile = YOUR_PKID + ".p7c"; string certFile; // How many certificates? int nCerts = X509.GetCertFromP7Chain("", listFile, 0); Console.WriteLine("X509.GetCertFromP7Chain(0) returns " + nCerts); // Enumerate through them all if (nCerts > 0) { for (int iCert = 1; iCert <= nCerts; iCert++) { // NB 1..n not 0..n-1 certFile = "TheCert" + iCert + ".cer"; int n = X509.GetCertFromP7Chain(certFile, listFile, iCert); if (n < 0) display_error(n); Console.WriteLine("Cert(" + iCert + ")->" + certFile); } }
There is a more detailed procedure Check_CertList_With_PrivateKey()
in the full source code that will actually find and extract your own X.509 certificate
if you provide your private key. The procedure TestTheCerts()
, also in the full source code,
that will verify that your certificate is valid and was indeed issued by the owners of the certificates claimed.
You can check that all the certificates in the p7c certificate chain file are valid and correctly signed by each other. You can even check that the root certificate is as expected by checking its SHA-1 thumbprint against a known value.
string p7cFile = YOUR_PKID + ".p7c"; // The trusted self-signed CA cert and its known SHA-1 thumbprint // -- YOU WILL NEED TO CHANGE THESE string trustedCert = "CA_Cert.cer"; string trustedThumb = "3867c2c9072362fa2565365b1e82b639a42f8220"; string thumb; int n; // 1. Does the trusted cert match its known thumbprint? thumb = X509.CertThumb(trustedCert, HashAlgorithm.Sha1); if (String.Compare(thumb, trustedThumb, true) == 0) { Console.WriteLine("OK, trusted certifcate has expected thumbprint."); } else { Console.WriteLine("ERROR: thumbprint of trusted cert does not match"); return false; } // 2. Use ValidatePath to check that the certificate path is valid // with the trusted cert as its ultimate parent n = X509.ValidatePath(p7cFile, trustedCert, false); if (n == 0) Console.WriteLine("OK, certification path is valid"); else if (n == 1) Console.WriteLine("ERROR: certification path is invalid"); else display_error(n);
If you use the real root certificate, you will need to change the hard-coded SHA-1 thumbprint in the code. The correct value
we have for the genuine root certificate issued by "Datenaustausch im Gesundheits- und Sozialwesen"
with serial number 0x28 issued 2011-11-23T13:21:52Z
, expiring 2019-01-04T13:21:52Z
, is
SHA-1 thumbprint=65478043e79b9071b3bf61ab50baf5354122622d
The text file contains the currently valid X.509 certificates of all the registered participants in the German Health system. There is no secret information here. It is all deliberately public. You can find the public key details of your recipient from this file.
The code reads the base64 string from the text file using standard file reading techniques,
and saves to a new X.509 certificate file using the
X509_SaveFileFromString
function.
We use the new X.509 certificate file to send an encrypted message to the owner.
Private Function ExtractCertsFromB64File(strFileIn As String, ByVal strOutPath As String) As Integer ' Given a text file consisting of base64-encoded X.509 certificates separated by a blank line ' extract all the certificates [cert01.cer, cert02.cer, ..., certN.cer] and return N on success ' or 0 if no certs found, or -1 if file error. Dim hFile As Integer Dim strLine As String Dim strData As String Dim iCert As Integer Dim strCertName As String Dim nRet As Long ' Make sure strOutpath has a trailing backslash, unless blank strOutPath = Trim(strOutPath) If Len(strOutPath) > 0 And Right$(strOutPath, 1) <> "\" Then strOutPath = strOutPath & "\" End If ' Make sure file exists If Len(Dir(strFileIn)) = 0 Then MsgBox "Cannot find file '" & strFileIn & "'", vbCritical ExtractCertsFromB64File = -1 Exit Function End If hFile = FreeFile Open strFileIn For Input As #hFile If LOF(hFile) = 0 Then MsgBox "File is empty", vbExclamation Close #hFile Exit Function End If ' Read in the data file line-by-line until find a blank line or EOF iCert = 0 strData = "" Do Until EOF(hFile) Line Input #hFile, strLine If Len(Trim(strLine)) = 0 Then ' We have blank line, so save what we have so far as an X.509 cert iCert = iCert + 1 strCertName = strOutPath & "cert" & Format(iCert, "00") & ".cer" ''Debug.Print strData ' Save from base64 string to a DER-encoded X.509 certificate file nRet = X509_SaveFileFromString(strCertName, strData, 0) Debug.Print "X509_SaveFileFromString returns " & nRet & " (expecting 0)" If nRet = 0 Then Debug.Print "Saved X.509 certificate file '" & strCertName & "'" Else Debug.Print "ERROR (" & nRet & ") saving cert file: " & pkiErrorLookup(nRet) End If ' NOTE: we can actually use the X509_CertSerialNumber, X509_QueryCert, etc. functions ' to query directly the base64 string itself instead of the file. Call ShowCertDetailsFromString(strData) strData = "" Else ' Just append the line to existing base64 data strData = strData & strLine End If Loop ' Catch final cert, if any If Len(strData) > 0 Then iCert = iCert + 1 strCertName = strOutPath & "cert" & Format(iCert, "00") Debug.Print strData strData = "" End If Close #hFile ' Return number of X.509 cert files created ExtractCertsFromB64File = iCert End Function Public Function ShowCertDetailsFromString(strData As String) ' NOTE: We can replace the strCertFile parameter with a string containing the cert as a base64 string Dim strOutput As String Dim strQuery As String Dim nRet As Long ' Make a large buffer to receive output strOutput = String(1024, " ") strQuery = "serialNumber" nRet = X509_QueryCert(strOutput, Len(strOutput), strData, strQuery, 0) If nRet <= 0 Then Exit Function ' catch error Debug.Print strQuery & "=" & Left(strOutput, nRet) strQuery = "subjectName" nRet = X509_QueryCert(strOutput, Len(strOutput), strData, strQuery, 0) If nRet <= 0 Then Exit Function ' catch error Debug.Print strQuery & ": " & Left(strOutput, nRet) End Function
Finally, after all the one-off preparation above, we can actually send some information to another user.
The required format is to sign then encrypt the information in CMS/PKCS#7 format.
We read our private key with the pre-written
rsaReadPrivateKey
function (which calls
RSA_ReadEncPrivateKey
),
and use the
CMS_MakeSigDataFromString
function
and then the
CMS_MakeEnvData
function.
The general algorithm is
1. (sigfile) <-- MakeSigData(message, certlist, privatekey, password) 2. (envfile) <-- MakeEnvData(sigfile, recipcert)
where
message
is the text data to be sent;
certlist
is a list of filenames of X.509 certificates, the first of which must be yours;
privatekey
is your own RSA private key;
password
is the password that protects your private key;
sigdata
is the binary signed-data value output by the signing procedure;
recipcert
is the filename of the recipient's X.509 certificate (a .cer file); and
envfile
is the binary enveloped-data value to be sent to the recipient.
Public Function Make_Signed_And_EnvelopedData( _ strOutputFile As String, _ strMsg As String, _ strPriKeyFile As String, _ strPassword As String, _ strSignersCertList As String, _ strRecipCertFile As String _ ) As Long Dim nRet As Long Dim strPrivateKey As String Dim strSigFile As String ' Intermediate signed-data file we will create strSigFile = strOutputFile & ".int.tmp" ' Read in the private key string Debug.Print "Reading private key from PRI file..." strPrivateKey = rsaReadPrivateKey(strPriKeyFile, strPassword) ' Check for success If Len(strPrivateKey) = 0 Then MsgBox "Cannot read private key", vbCritical Exit Function Else Debug.Print "...OK, read private key: key length=" & RSA_KeyBits(strPrivateKey) & " bits" End If ' Create a signed-data object with signed attributes and signing-time and all certificates. nRet = CMS_MakeSigDataFromString(strSigFile, strMsg, strSignersCertList, _ strPrivateKey, PKI_CMS_INCLUDE_ATTRS + PKI_CMS_ADD_SIGNTIME _ + PKI_HASH_SHA256) ' NEW IN 2014 Debug.Print "CMS_MakeSigDataFromString returns " & nRet & " (expecting 0)" ' Clean up as we go Call WIPE_String(strPrivateKey, Len(strPrivateKey)) ' Check for success If nRet <> 0 Then Debug.Print "ERROR (" & nRet & "): " & pkiErrorLookup(nRet) & ": " & pkiGetLastError() MsgBox "Cannot create signed-data file", vbCritical Exit Function Else Debug.Print "OK, created signed-data file '" & strSigFile & "'" End If ' Now we encrypt the signed-data object directly using the recipient's certificate ' this produces a binary enveloped-data file ' -- changed from default TDEA to AES256 in 2016 nRet = CMS_MakeEnvData(strOutputFile, strSigFile, strRecipCertFile, "", 0, PKI_BC_AES256) Debug.Print "CMS_MakeEnvData returns " & nRet & " (expecting 1)" If nRet <= 0 Then Debug.Print "ERROR (" & nRet & "): " & pkiErrorLookup(nRet) & ": " & pkiGetLastError() MsgBox "Cannot create enveloped-data file", vbCritical Exit Function Else Debug.Print "OK, created enveloped-data file '" & strOutputFile & "'" End If ' Clean up intermediate file Call WIPE_File(strSigFile, 0) ' Now send the output file to the recipient... End Function
static bool Make_Signed_And_EnvelopedData(string outputFile, string msg, string priKeyFile, string password, string signersCertList, string recipCertFile, bool keepInterFile) { int n; // Intermediate signed-data file we will create string sigFile = outputFile + ".int.tmp"; // 1. Read in the secret private key to a StringBuilder Console.WriteLine("Reading private key from PRI file..."); StringBuilder sbPriKey = Rsa.ReadEncPrivateKey(priKeyFile, password); // Check for success if (sbPriKey.Length == 0) { Console.WriteLine("ERROR: Cannot read private key"); return false; } else { Console.WriteLine("...OK, read in private key: key length=" + Rsa.KeyBits(sbPriKey) + " bits"); } // 2. Create a signed-data object with signed attributes and signing-time and all certificates. // using SHA-256 for the signature n = Cms.MakeSigDataFromString(sigFile, msg, signersCertList, sbPriKey.ToString(), HashAlgorithm.Sha256, Cms.SigDataOptions.IncludeAttributes | Cms.SigDataOptions.AddSignTime); Console.WriteLine("CMS_MakeSigDataFromString returns " + n + " (expecting 0)"); // Clean up as we go Wipe.String(sbPriKey); // Check for success if (n != 0) { display_error(n); return false; } else { Console.WriteLine("OK, created signed-data file '" + sigFile + "'"); } // 3. Encrypt the signed-data object directly using the recipient's certificate // -- this produces a binary enveloped-data file // -- Changed from Tdea to Aes256 in 2016 n = Cms.MakeEnvData(outputFile, sigFile, recipCertFile, CipherAlgorithm.Aes256, 0); // Clean up sensitive data if (!keepInterFile) { Wipe.File(sigFile); } // Check for success (NB expecting # of recipients, not zero) if (n <= 0) { display_error(n); return false; } else { Console.WriteLine("OK, created enveloped-data file '" + outputFile + "'"); } // Now send the output file to the recipient... return true; }
To decrypt and verify a file sent to you by another user, do the following. You will need your own private key and its password.
But how do I create such a file to test it? Answer: send yourself a test message signed by yourself.
Public Function Read_Signed_and_Enveloped_Data( _ strInputFile As String, _ strPriKeyFile As String, _ strPassword As String, _ Optional strCertFile As String _ ) As String ' Returns string containing output message or an empty string on error Dim nRet As Long Dim strPrivateKey As String Dim strSigFile As String Dim strOutput As String Dim strQuery As String ' Read in the recipient's private key strPrivateKey = rsaReadPrivateKey(strPriKeyFile, strPassword) If Len(strPrivateKey) = 0 Then Debug.Print "ERROR: " & pkiGetLastError() MsgBox "Cannot read private key", vbCritical Exit Function End If ' Intermediate file we will create strSigFile = strInputFile & ".i2.tmp" ' Read the encrypted data from the enveloped-data file nRet = CMS_ReadEnvData(strSigFile, strInputFile, "", strPrivateKey, 0) Debug.Print "CMS_ReadEnvData returns " & nRet & " (expected 0)" If nRet <> 0 Then Debug.Print "ERROR (" & nRet & "): " & pkiErrorLookup(nRet) & ": " & pkiGetLastError() Else Debug.Print "Extracted signed-data file '" & strSigFile & "'" End If ' Pre-dimension output string for query result strOutput = String(64, " ") Debug.Print "For SigData file '" & strSigFile & "'..." nRet = CMS_QuerySigData(strOutput, Len(strOutput), strSigFile, "version", 0) Debug.Print "Version=" & nRet nRet = CMS_QuerySigData(strOutput, Len(strOutput), strSigFile, "signingTime", 0) If nRet > 0 Then Debug.Print "signingTime=" & Left$(strOutput, nRet) Else Debug.Print "ERROR=" & nRet End If strQuery = "messageDigest" nRet = CMS_QuerySigData(strOutput, Len(strOutput), strSigFile, strQuery, 0) If nRet > 0 Then Debug.Print strQuery & "=" & Left$(strOutput, nRet) Else Debug.Print "ERROR=" & nRet End If strQuery = "CountOfSignerInfos" nRet = CMS_QuerySigData(strOutput, Len(strOutput), strSigFile, strQuery, 0) If nRet > 0 Then Debug.Print strQuery & "=" & nRet Else Debug.Print "ERROR=" & nRet End If strQuery = "CountOfCertificates" nRet = CMS_QuerySigData(strOutput, Len(strOutput), strSigFile, strQuery, 0) If nRet > 0 Then Debug.Print strQuery & "=" & nRet Else Debug.Print "ERROR=" & nRet End If nRet = CMS_VerifySigData(strSigFile, "", "", 0) Debug.Print "CMS_VerifySigData('') returns " & nRet & " (expecting 0)" nRet = CMS_VerifySigData(strSigFile, strCertFile, "", 0) Debug.Print "CMS_VerifySigData('" & strCertFile & "') returns " & nRet & " (expecting 0)" Dim nDataLen As Long Dim strData As String ' How long is the content to be read? nDataLen = CMS_ReadSigDataToString("", 0, strSigFile, 0) If nDataLen <= 0 Then Debug.Print "ERROR (" & nRet & "): " & pkiErrorLookup(nRet) & ": " & pkiGetLastError() MsgBox "Cannot read signed-data file", vbCritical Exit Function End If ' Pre-dimension string to receive data strData = String(nDataLen, " ") nRet = CMS_ReadSigDataToString(strData, nDataLen, strSigFile, 0) Debug.Print "CMS_ReadSigDataToString returns " & nRet Debug.Print "Data is [" & strData & "]" ' Return message string Read_Signed_and_Enveloped_Data = strData ' Clean up intermediate file Call WIPE_File(strSigFile, 0) Call WIPE_String(strData, Len(strData)) End Function
static string Read_Signed_and_Enveloped_Data(string inputFile, string priKeyFile, string password, bool keepInterFile) { string msg = ""; int n; string s, query; // 1. Read in the recipient's secret private key to a StringBuilder Console.WriteLine("Reading private key from PRI file..."); StringBuilder sbPriKey = Rsa.ReadEncPrivateKey(priKeyFile, password); // Check for success if (sbPriKey.Length == 0) { Console.WriteLine("ERROR: Cannot read private key"); return ""; } else { Console.WriteLine("...OK, read in private key: key length=" + Rsa.KeyBits(sbPriKey) + " bits"); } // Intermediate file we will create string sigFile = inputFile + ".i2.tmp"; // 2. Read the encrypted data from the enveloped-data file n = Cms.ReadEnvDataToFile(sigFile, inputFile, "", sbPriKey.ToString(), 0); Console.WriteLine("Cms.ReadEnvDataToFile returns " + n + " (expected 0)"); // Check for success if (n != 0) { display_error(n); return ""; } else { Console.WriteLine("Extracted signed-data file '" + sigFile + "'"); } // 3. Extract the content msg = Cms.ReadSigDataToString(sigFile); // Clean up sensitive data if (!keepInterFile) { Wipe.File(sigFile); } // Check for success if (msg.Length == 0) { display_error(General.ErrorCode()); return ""; } return msg; }
The VB6/VBA source code is in GermanHealthCode-VB6.zip, the C# code is in GermanHealthCode-CS.zip, and the test files we used in all languages are in GermanHealthTestFiles.zip. Note that if you re-create the files by running this code you will get different results from these examples. To check: use Windows CERTMGR on our test certificates and you should get these results.
You will need to have installed the CryptoSys PKI Toolkit on your system and set up your programming environment as described in Using with Classic Basic VB6 and VBA or Using with .NET. A fully-functional trial version of CryptoSys PKI can be downloaded from here.
In the example code, we send a message to a dummy end user called Ingrid Kruger in the company Weiss GmbH with id IK999009051. To read the message we use the private key for that user, which we have because we made it, but in practice, you won't be able to read the messages you create because they can only be decrypted by the owner of the private key, which you won't have.
If you want the actual private and public keys and binary X.509 test certificates for the dummy test users
and root CA and intermediate CA,
download GermanHealthTestUserKeys.zip (23 kB).
The password for all of the private key files is "password
".
Obviously, in practice you will not have this information, but it can help in your testing and debugging.
For more information, to comment on this page, or to tell us we have made a mistake, please send us a message.
This page last updated: 6 May 2021