This page looks at some of the new features in CryptoSys PKI Pro 1.0 and gives some sample code.
Read any key | Direct signature values | ASN.1 utilities | S/MIME wrapping | CMS compressed-data objects | Processing large CMS data files
For example code of the new elliptic curve cryptography features in CryptoSys PKI Pro 1.1 see More details on Elliptic Curve Cryptography (ECC).
RSA_ReadAnyPrivateKey
and RSA_ReadAnyPublicKey
can automatically read private and public keys from almost "any" supported format into an internal key string
... more →
<SignatureValue>
element for an XML-DSIG document in one step using the simplified
SIG_SignData
and SIG_SignFile
functions
... more →
ASN1_Type
function and
Asn1.Type Method
will attempt to identify the type of ASN.1 data
... more →
CMS_MakeComprData
and CMS_ReadComprData
will create and read CMS compressed-data (.p7z) files
... more →
Use these test files to reproduce the tests on this page: pki_pro_files.zip. This includes variants of the public and private keys for "Alice" from RFC 4134 along with some other text files. Alice's RSA key pair in PEM text form is alice.cer and alice.key (password: "password").
Most functions involving RSA keys require the key to be first read into an internal key string.
In earlier versions you needed to know what sort of key file you had and use the appropriate function, e.g.
RSA_ReadEncPrivateKey()
, RSA_ReadPrivateKeyFromPFX()
, and so forth.
The new functions
RSA_ReadAnyPrivateKey
and RSA_ReadAnyPublicKey
can automatically read private and public keys from almost "any" supported format into an internal key string.
Furthermore, the functions will accept input in the following forms:
For XML data it will accept both an XML file or a string containing the contents. It supports the same formats as
described in the remarks for
RSA_FromXMLString
In .NET we added the
Rsa.ReadPrivateKey Method
and changed the
Rsa.ReadPublicKey Method
to cope with any supported key input.
string prikey1 = Rsa.ReadPrivateKey("alice.key", "password"); string prikey2 = Rsa.ReadPrivateKey("alice.pfx", "password"); string prikey3 = Rsa.ReadPrivateKey("alice.p8", ""); // Unencrypted string pubkey1 = Rsa.ReadPublicKey("alice.cer"); string pubkey2 = Rsa.ReadPublicKey("alice.pub");
In VB6/VBA we have added the rsaReadPrivateKey
function, which will read "any" private key with an optional password,
and changed the rsaReadPublicKey
function to read both public key data files and X.509 certificates.
Public Function rsaReadPrivateKey(strKeyFileOrString As String, strPassword As String) As String ' Reads the private key from any supported private key file or PEM string ' Returns the key as an ephemeral base64 string or an empty string on error Dim nLen As Long ' How long is PrivateKey string? nLen = RSA_ReadAnyPrivateKey("", 0, strKeyFileOrString, strPassword, 0) If nLen <= 0 Then Exit Function End If ' Pre-dimension the string to receive data rsaReadPrivateKey = String(nLen, " ") ' Read in the Private Key nLen = RSA_ReadAnyPrivateKey(rsaReadPrivateKey, nLen, strKeyFileOrString, strPassword, 0) End Function
Public Function rsaReadPublicKey(strKeyFile As String) As String ' Reads the public key from any supported public key file or PEM string ' Returns the key as an ephemeral base64 string or an empty string on error Dim nLen As Long ' How long is key string? nLen = RSA_ReadAnyPublicKey("", 0, strKeyFile, 0) If nLen <= 0 Then Exit Function End If ' Pre-dimension the string to receive data rsaReadPublicKey = String(nLen, " ") ' Read in the Public Key nLen = RSA_ReadAnyPublicKey(rsaReadPublicKey, nLen, strKeyFile, 0) End Function
For a fast way to create a <SignatureValue>
element for an XML-DSIG document, use the simplified
SIG_SignData
and SIG_SignFile
functions, which create the signature value in one step
using the input data-to-be-signed, key data and password (if required).
In .NET, use the corresponding methods
Sig.SignData
,
Sig.SignFile
and
Sig.SignDigest
.
// Compute rsa-sha1 signature value of c14n'd signed-info element using Alice's private key string sigval = Sig.SignFile("xmldsig-signedinfo-c14.txt", "alice.key", "password", SigAlgorithm.Rsa_Sha1); Console.WriteLine("SignatureValue: " + sigval);
SignatureValue: nihUFQg4mDhLgecvhIcKb9Gz8VRTOlw+adiZOBBXgK4JodEe5aFfCqm8WcRIT8GLL XSk8PsUP4//SsKqUBQkpotcAqQAhtz2v9kCWdoUDnAOtFZkd/CnsZ1sge0ndha40wWDV+nOWyJxkYgic vB8POYtSmldLLepPGMz+J7/Uws=
Most data files used in security applications are formatted in ASN.1 BER or DER encoding. DER is a stricter subset of BER encoding that guarantees a unique encoding for given data and is typically used for small objects that require a consistent form, like X.509 certificates. BER encoding is generally used for larger files like CMS objects.
The ASN1_Type
function and
Asn1.Type
method
will attempt to identify the type of ASN.1 data. This example uses the test files above.
// Get type name of ASN.1 object s = Asn1.Type("alice.cer"); Console.WriteLine(s); s = Asn1.Type("alice.crt"); Console.WriteLine(s); s = Asn1.Type("alice.key"); Console.WriteLine(s); s = Asn1.Type("alice.p7c"); Console.WriteLine(s); s = Asn1.Type("alice.p8"); Console.WriteLine(s); s = Asn1.Type("alice.pfx"); Console.WriteLine(s); s = Asn1.Type("alice.pub"); Console.WriteLine(s); s = Asn1.Type("alice_pri_ssl.txt"); Console.WriteLine(s); s = Asn1.Type("alice_pub_ssl.txt"); Console.WriteLine(s);
should output
X509 CERTIFICATE X509 CERTIFICATE PKCS8 ENCRYPTED PRIVATE KEY PKCS7 CERTIFICATE CHAIN PKCS8 PRIVATE KEY INFO PKCS12 PFX PKCS1 RSA PUBLIC KEY PKCS1 RSA PRIVATE KEY PUBLIC KEY INFO
The ASN1_TextDump
function and
Asn1.TextDump Method
will dump the contents of ASN.1 formatted data to a text file.
The output shows the nested structure of the data together with comments to describe the type and length of each element.
Each byte of the ASN.1 input file is shown in hex format. The length is given in both decimal and hex form.
The output for a small test X.509 certificate is as follows
30 81 e0 --SEQUENCE/224=0xE0 30 81 9a --SEQUENCE/154=0x9A 02 01 --INTEGER/1=0x1 01 30 0d --SEQUENCE/13=0xD 06 09 --OBJECTIDENTIFIER/9=0x9 2a 86 48 86 f7 0d 01 01 05 --sha1WithRSAEncryption (1.2.840.113549.1.1.5) 05 00 --NULL/0=0x0 30 0c --SEQUENCE/12=0xC 31 0a --SET/10=0xA 30 08 --SEQUENCE/8=0x8 06 03 --OBJECTIDENTIFIER/3=0x3 55 04 03 --commonName (2.5.4.3) 13 01 --PRINTABLESTRING/1=0x1 41 --'A' ---CUT--- 30 0d --SEQUENCE/13=0xD 06 09 --OBJECTIDENTIFIER/9=0x9 2a 86 48 86 f7 0d 01 01 05 --sha1WithRSAEncryption (1.2.840.113549.1.1.5) 05 00 --NULL/0=0x0 03 32 --BITSTRING/50=0x32 00 --0 unused bits 02 eb 77 39 df af 4f 87 f9 57 51 f6 68 96 29 f5 bd 50 39 cf fe 35 ad 76 ee 31 bb 01 10 26 96 95 93 10 1c a1 e1 91 af 36 5d 2b 5f 63 f1 e1 2d bc 7c --(227 bytes)
S/MIME entities are text wrappings around CMS objects, with the inside data usually encoded in base64, but sometimes left
in raw binary form.
The new functions to wrap, extract and query S/MIME entities are
SMIME_Wrap
,
SMIME_Extract
and
SMIME_Query
and their corresponding .NET methods,
Smime.Wrap
,
Smime.Extract
and
Smime.Query
.
See the examples below.
The new functions CMS_MakeComprData
and CMS_ReadComprData
will create and read CMS compressed-data (.p7z) files.
The corresponding .NET methods are
Cms.MakeComprData
and
Cms.ReadComprData
.
The example below shows how to create a chain of S/MIME-wrapped enveloped-signed-compressed-data objects.
We compress a text file, sign it, and then envelope it (i.e. encrypt it with the recipient's public RSA key).
Each time we wrap the CMS object inside an S/MIME entity before processing again.
The full code is in the distributed source code file
TestPKIcsharp.cs
- search for in test_CMS_SMIME_Chain()
.
int r; string s; string inpFile; string outFile; string origFile = "sonnets.txt"; string alicekeyfile = "AlicePrivRSASign.epk"; string alicecerfile = "AliceRSASignByCarl.cer"; string password = "password"; string bobkeyfile = "BobPrivRSAEncrypt.epk"; string bobcerfile = "BobRSASignByCarl.cer"; StringBuilder sbPrikey; string query; string digest1, digest2; Console.WriteLine("\nCREATE A CHAIN OF CMS OBJECTS WRAPPED IN S/MIME:"); // Start with original input file inpFile = origFile; Console.WriteLine("File '{0}' is {1} bytes", inpFile, FileLength(inpFile)); // Create a CMS compressed-data object outFile = "sonnet-compr.p7z"; r = Cms.MakeComprData(outFile, inpFile); Console.WriteLine("Cms.MakeComprData returns {0} (expected 0)", r); Debug.Assert(0 == r); inpFile = outFile; Console.WriteLine("File '{0}' is {1} bytes", inpFile, FileLength(inpFile)); // Wrap in S/MIME outFile = "sonnet-compr-smime.txt"; r = Smime.Wrap(outFile, inpFile, 0); Console.WriteLine("Smime.Wrap returns {0} (expected +ve)", r); Debug.Assert(r > 0); inpFile = outFile; Console.WriteLine("File '{0}' is {1} bytes", inpFile, FileLength(inpFile)); // Create a CMS signed-data object signed by Alice outFile = "sonnet-sig-compr.p7m"; sbPrikey = Rsa.ReadPrivateKey(alicekeyfile, password); Debug.Assert(sbPrikey.Length > 0); r = Cms.MakeSigData(outFile, inpFile, alicecerfile, sbPrikey.ToString(), 0); Console.WriteLine("Cms.MakeSigData returns {0} (expected 0)", r); Debug.Assert(0 == r); inpFile = outFile; Console.WriteLine("File '{0}' is {1} bytes", inpFile, FileLength(inpFile)); // Wrap in S/MIME outFile = "sonnet-sig-compr-smime.txt"; r = Smime.Wrap(outFile, inpFile, 0); Console.WriteLine("Smime.Wrap returns {0} (expected +ve)", r); Debug.Assert(r > 0); inpFile = outFile; Console.WriteLine("File '{0}' is {1} bytes", inpFile, FileLength(inpFile)); // Create a CMS enveloped-data object encrypted for Bob outFile = "sonnet-env-sig-compr.p7m"; r = Cms.MakeEnvData(outFile, inpFile, bobcerfile, 0); Console.WriteLine("Cms.MakeSigData returns {0} (expected 1)", r); Debug.Assert(1 == r); inpFile = outFile; Console.WriteLine("File '{0}' is {1} bytes", inpFile, FileLength(inpFile)); // Wrap in S/MIME outFile = "sonnet-env-sig-compr-smime.txt"; r = Smime.Wrap(outFile, inpFile, 0); Console.WriteLine("Smime.Wrap returns {0} (expected +ve)", r); Debug.Assert(r > 0); inpFile = outFile; Console.WriteLine("File '{0}' is {1} bytes", inpFile, FileLength(inpFile));
which should give output as follows:
CREATE A CHAIN OF CMS OBJECTS WRAPPED IN S/MIME: File 'sonnets.txt' is 106081 bytes Cms.MakeComprData returns 0 (expected 0) File 'sonnet-compr.p7z' is 40862 bytes Smime.Wrap returns 56366 (expected +ve) File 'sonnet-compr-smime.txt' is 56366 bytes Cms.MakeSigData returns 0 (expected 0) File 'sonnet-sig-compr.p7m' is 57198 bytes Smime.Wrap returns 78822 (expected +ve) File 'sonnet-sig-compr-smime.txt' is 78822 bytes Cms.MakeSigData returns 1 (expected 1) File 'sonnet-env-sig-compr.p7m' is 79091 bytes Smime.Wrap returns 108929 (expected +ve) File 'sonnet-env-sig-compr-smime.txt' is 108929 bytes
Now we reverse the procedure: examine the input file before processing using
Smime.Query()
and
Asn1.Type()
, then extract and process.
// NOW REVERSE THE PROCEDURE... Console.WriteLine("\nReverse the procedure (and check file types as we go)..."); // What S/MIME type are we starting with? query = "smime-type"; s = Smime.Query(inpFile, query); Console.WriteLine("{0} is '{1}'", query, s); // Extract from S/MIME outFile = "sonnet-out-env-sig-compr.p7m"; r = Smime.Extract(outFile, inpFile, 0); Console.WriteLine("Smime.Extract returns {0} (expected +ve)", r); Debug.Assert(r > 0); inpFile = outFile; Console.WriteLine("File '{0}' is {1} bytes", inpFile, FileLength(inpFile)); // What ASN.1 type did we get? s = Asn1.Type(inpFile); Console.WriteLine("Found a '{0}' file", s); // Bob decrypts enveloped-file using his private key outFile = "sonnet-out-sig-compr-smime.txt"; sbPrikey = Rsa.ReadPrivateKey(bobkeyfile, password); Debug.Assert(sbPrikey.Length > 0); r = Cms.ReadEnvDataToFile(outFile, inpFile, "", sbPrikey.ToString(), 0); Console.WriteLine("Cms.ReadEnvDataToFile returns {0} (expected 0)", r); Debug.Assert(r == 0); inpFile = outFile; Console.WriteLine("File '{0}' is {1} bytes", inpFile, FileLength(inpFile)); // What S/MIME type was it? query = "smime-type"; s = Smime.Query(inpFile, query); Console.WriteLine("{0} is '{1}'", query, s); // Extract from S/MIME outFile = "sonnet-out-sig-compr.p7m"; r = Smime.Extract(outFile, inpFile, 0); Console.WriteLine("Smime.Extract returns {0} (expected +ve)", r); Debug.Assert(r > 0); inpFile = outFile; Console.WriteLine("File '{0}' is {1} bytes", inpFile, FileLength(inpFile)); // What ASN.1 type did we get? s = Asn1.Type(inpFile); Console.WriteLine("Found a '{0}' file", s); Debug.Assert(s.Length > 0); // Verify the signature in the signed-data object using its embedded certificate r = Cms.VerifySigData(inpFile); Console.WriteLine("Cms.VerifySigData returns {0} (0=>signature OK)", r); Debug.Assert(r == 0); // Extract contents of signed-data outFile = "sonnet-out-compr-smime.txt"; r = Cms.ReadSigDataToFile(outFile, inpFile); Console.WriteLine("Cms.ReadSigDataToFile returns {0} (expected +ve)", r); Debug.Assert(r > 0); inpFile = outFile; Console.WriteLine("File '{0}' is {1} bytes", inpFile, FileLength(inpFile)); // What S/MIME type was it? query = "smime-type"; s = Smime.Query(inpFile, query); Console.WriteLine("{0} is '{1}'", query, s); // Extract from S/MIME outFile = "sonnet-out-compr.p7z"; r = Smime.Extract(outFile, inpFile, 0); Console.WriteLine("Smime.Extract returns {0} (expected +ve)", r); Debug.Assert(r > 0); inpFile = outFile; Console.WriteLine("File '{0}' is {1} bytes", inpFile, FileLength(inpFile)); // What ASN.1 type did we get? s = Asn1.Type(inpFile); Console.WriteLine("Found a '{0}' file", s); Debug.Assert(s.Length > 0); // Finally, read contents of CMS compressed-data object outFile = "sonnet-out.txt"; r = Cms.ReadComprData(outFile, inpFile, 0); Console.WriteLine("Cms.ReadComprData returns {0} (expected +ve)", r); Debug.Assert(r > 0); inpFile = outFile; Console.WriteLine("File '{0}' is {1} bytes", inpFile, FileLength(inpFile)); // Check we have no more S/MIME wrapping query = "smime-type"; s = Smime.Query(inpFile, query); Console.WriteLine("{0} is '{1}'", query, s); // Did we get the same as we started? Console.WriteLine("Compute SHA-1 digests and compare..."); digest1 = Hash.HexFromFile(outFile, HashAlgorithm.Sha1); Console.WriteLine("SHA1('{0}')=\t{1}", outFile, digest1); digest2 = Hash.HexFromFile(origFile, HashAlgorithm.Sha1); Console.WriteLine("SHA1('{0}')=\t{1}", origFile, digest2); Debug.Assert(digest1 == digest2, "Digests do not match");
Reverse the procedure (and check file types as we go)... smime-type is 'enveloped-data' Smime.Extract returns 79091 (expected +ve) File 'sonnet-out-env-sig-compr.p7m' is 79091 bytes Found a 'PKCS7/CMS ENVELOPED DATA' file Cms.ReadEnvDataToFile returns 0 (expected 0) File 'sonnet-out-sig-compr-smime.txt' is 78822 bytes smime-type is 'signed-data' Smime.Extract returns 57198 (expected +ve) File 'sonnet-out-sig-compr.p7m' is 57198 bytes Found a 'PKCS7/CMS SIGNED DATA' file Cms.VerifySigData returns 0 (0=>signature OK) Cms.ReadSigDataToFile returns 56366 (expected +ve) File 'sonnet-out-compr-smime.txt' is 56366 bytes smime-type is 'compressed-data' Smime.Extract returns 40862 (expected +ve) File 'sonnet-out-compr.p7z' is 40862 bytes Found a 'PKCS7/CMS COMPRESSED DATA' file Cms.ReadComprData returns 106081 (expected +ve) File 'sonnet-out.txt' is 106081 bytes smime-type is '' Compute SHA-1 digests and compare... SHA1('sonnet-out.txt')= 13d93bb1baa7ebc360a35c05bbfd23570ce42400 SHA1('sonnets.txt')= 13d93bb1baa7ebc360a35c05bbfd23570ce42400
The PKI_CMS_BIGFILE option will significantly speed up the processing of signed-data and enveloped-data objects when dealing with large files. It is used automatically for compressed-data objects.
// Alice encrypts a file for Bob using the BigFile option n = Cms.MakeEnvData(fnameEnvel, fnameInput, "bob.cer", Cms.Options.BigFile); Console.WriteLine("Cms.MakeEnvData returns {0} (expecting 1)", n); // # recipients // Alice then signs the result string fnameSigned = "cmsalice2bobsigned.p7m"; StringBuilder sbPriKey = Rsa.ReadPrivateKey("alice.key", "password"); n = Cms.MakeSigData(fnameSigned, fnameEnvel, "alice.cer", sbPriKey.ToString(), Cms.Options.BigFile); Console.WriteLine("Cms.MakeSigData returns {0} (expecting 0)", n);
For more information, please send us a message.
This page last updated 15 August 2025