CryptoSys Home > PKI > Using the new features in CryptoSys PKI Pro

Using the new features in CryptoSys PKI Pro


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

Summary

Read "any" key
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 ... more →
Direct signature values
Create a <SignatureValue> element for an XML-DSIG document in one step using the simplified SIG_SignData and SIG_SignFile functions ... more →
ASN.1 utilities
The ASN1_Type function and Asn1.Type Method will attempt to identify the type of ASN.1 data ... more →
S/MIME wrapping
The new functions to wrap, extract and query S/MIME entities are ... more →
CMS compressed-data objects
The new functions CMS_MakeComprData and CMS_ReadComprData will create and read CMS compressed-data (.p7z) files ... more →
Processing large CMS data files
The PKI_CMS_BIGFILE option will significantly speed up the processing of signed-data and enveloped-data objects when dealing with large files ... more →

Test files

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").

Read "any" key

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

Direct Signature Values

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=

ASN.1 utilities

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 wrapping, extraction and queries

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.

CMS compressed-data objects

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

Processing large CMS files faster

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

Contact

For more information, please send us a message.

This page last updated 15 August 2025

[Go to top]