CryptoSys Home > PKI > Data Exchange in the German Health Service

Data Exchange in the German Health Service with CryptoSys PKI


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#.

READ THIS FIRST

New 2019Changes in 2018-19: There are changes to the specifications required as of 2019. These may not yet be reflected in all the code on this page.
The new specifications require These new** RSA-PSS and RSA-OAEP variants are now fully supported as of version 12.0 of CryptoSys PKI 1.2, released 20 June 2018.
2018-19 changes 2019-06-26: Code samples out of date. CAUTION! Many of the code samples on this page are now out of date. These will be fixed in due course. In the meantime here are some updated source code files that demonstrate the methods and options required for the latest specifications. The above source code files supersede all the code examples below.
Latest references: Best Practice zur Security Schnittstelle (pdf 487 kB) and Security Schnittstelle (SECON) Anlage 16 (pdf 1.2 MB, dated 26.10.2017).

** Well, "new" in the sense that they've only just become popular. They were introduced and have been recommended best practice since, er, 2002. Just sayin'.
History:
Recommendations:
“ 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. ”
New 2022May 2022: Electronic prescriptions in the German Health system
“ 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.
July 2016 July 2016: How to change the contentEncryption algorithm to AES-256 instead of Triple DES in the final enveloped-data object.
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-CBC
In VBA/C:
nRet = CMS_MakeEnvData(strOutputFile, strSigFile, strRecipCertFile, "", 0, PKI_BC_AES256)

Overview

The basic procedures to be carried out break down into the following tasks.

  1. Creating your own public/private key pair and getting a certificate from your Certification Authority (CA).
  2. Extracting your own X.509 certificate from the .p7c file sent by the CA.
  3. Extracting the X.509 certificates of other users from the text file sent by the CA.
  4. Creating a signed and encrypted file in the required CMS (PKCS#7) format to send to a recipient.
  5. Decrypting and verifying such a file when someone sends one to you.

Of these, the first three only need be done once.

Changes in 2014

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.

  1. Certificates issued by ITSG will be signed using sha256WithRSAEncryption (sha256RSA).
  2. In 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)

  3. In 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)

  4. In 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)

  5. In the year 2016 the encryption algorithm in the CMS enveloped-data object is to be changed from Triple DES (TDEA) to AES-256. At that time (but not yet) As of July 2016, make the change in 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.

Auftrag (AUF) file

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.

Incorrect old format:
5000000100000348000TPFL0003     500825234      500825234      108018007      108018007
      000000000000PL114004SAO201411071448000000000000000000000000000000000000000000000000000000000004350000000008347I8000000
   0000000000000 0000000000000                                                                              00

Correct new format:
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).

Disclaimer

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

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.

Creating your own public/private key pair and getting a certificate from your Certification Authority (CA)

Step 1.
On your machine, create a new RSA public/private key pair using the 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.

Step 2.
Generate a Certificate Signing Request file (CSR, PKCS#10, .p10) file using the 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
countryNameC=DEC=DE
organizationNameO=ISTG Beispiel Vertrauen MitteO=ITSG TrustCenter fuer sonstige Leistungserbringer
organizationalUnitOU=Unsere FirmaOU=(your company)
organizationalUnitOU=IK999009991OU=IK(ID number)
commonNameCN=Erika MustermannCN=(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.

Step 3.
Create an SHA-1 message digest hash of your public key file using the 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).

Step 4.
Send your .p10 file and hash value plus any fees and any other identification information required to your CA; in your case, ITSG.
Step 5.
Receive back a .p7c file containing your X.509 certificate and the CA's X.509 certificates used to sign your certificate, together with a text file containing the certificates of valid recipients in base64 format. The latter file is currently sent with the filename 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.

Extracting your own X.509 certificate from the .p7c file sent by the CA

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.

Checking the p7c certificate list

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);

The Genuine Root Certificate

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

Extracting the X.509 certificates of other users from the text file sent by the CA

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

Creating a signed and encrypted file in the required CMS (PKCS#7) format to send to a recipient.

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.

Remarks

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;
}

Decrypting and verifying such a file when someone sends one to you.

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;
}

Downloads

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.

Source code listings

Test user keys and certificates

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.

References

Contact

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