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, version 1.5. This page shows how to do it in VB6/VBA code.
We must point out that we are completely independent of the ITSG Trustcenter and anything on this site has not 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". We can, though, make a pretty good stab at understanding the technical documents involved here.
The basic procedures to be carried out break down into the following tasks.
Of these, the first three only need be done once.
The full source code in VB6 is in GermanHealthCode.zip (8 kB)
and the test files we used are in GermanHealthTestFiles.zip (28 kB).
You will need to have installed the CryptoSys PKI Toolkit on your system and include the
basCrPKI VB6 module in your project
(you should find it in the folder C:\Program Files\CryptoSysPKI\VB6).
A fully-functional trial version of CryptoSys PKI can be
downloaded from here.
The source code provided uses dummy test files that we made up ourselves. The files should be in the correct format as 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. The current code is in VB6/VBA. We will get around to producing a version in C# at some stage in the future. Drop us a line to remind us if you are interested.
GermanHealthCode.zip basGermanHealthSetup.bas - VB6 procedures to get set up. basGermanHealthExamples.bas - VB6 procedures to create signed and encrypted messages, etc.
GermanHealthCode.zip 999009991_pri.p8 - our encrypted private key file 999009991_pub.p1 - our public key file 999009991.p10 - the CRS file we sent to our CA 999009991.p7c - the .p7c certificate chain file we received back annahme-test-certs.txt - the dummy list of other certificates we received TheCert1.cer - a certificate as extracted from the .p7c file TheCert2.cer - ditto TheCert3.cer - ditto 999009991.cer - our own certificate extracted from the .p7c file (=TheCert1.cer) Int_Cert.cer - Our dummy CA's self-signed root certificate (=TheCert2.cer) CA_Cert.cer - Our dummy intermediate CA's certificate (=TheCert3.cer) 999009051.cer - the certificate for user with dummy id IK999009051 To_999009051.p7m - a message we sent to user IK999009051 999009051_pri.p8 - user IK999009051's private key file (!)
RSA_MakeKeys.
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)
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.
These file extensions (.p1 and .p8) are our own convention.
As far as we know, there is no accepted conventions to name these 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.
X509_CertRequest.
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_SHA1RSA)
To submit to ITSG, you need to generate a binary file. 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_MD5 option.
strPublicKeyFile = TESTPATH & YOUR_PKID & "_pub.p1"
strDigest = String(PKI_MD5_CHARS, " ")
nRet = HASH_HexFromFile(strDigest, Len(strDigest), strPublicKeyFile, PKI_HASH_MD5)
Debug.Print "MD5(PublicKey)=" & strDigest
In our example, we got
MD5(PublicKey)=be854ca565a0951633f6d101d964d8e5
annahme-pkcs.key. It is a text file and can be viewed with any text file editor, including NotePad.
Here are the test files we have created: 999009991.p7c.zip and annahme-test-certs.txt. These should be similar to what you get back in practice. These files are included in the source code above.
This is the output from using the Windows CERTMGR.EXE program on the test certificates we created.
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
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.
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 01 issued 2005-11-14, expiring 2013-01-04, is
SHA-1 thumbprint=8ce6e391d1dba9afd0f809dc7697fa768ea6b709
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)
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
nRet = CMS_MakeEnvData(strOutputFile, strSigFile, strRecipCertFile, "", 0, 0)
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
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
In the example code, we send a message to a dummy end user called Ingrid Kruger from 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,
download GermanHealthTestUserKeys.zip (52 kB) and the private keys for the
root CA and intermediate CA in GermanHealthCAIntKeys.zip (6 kB). The password for all
of the private key files is "password". Needless to say, in practice you will not have this information.
For more information, please contact us.
This page last updated: 30 July 2008