4 June 2016:
Here is a change we forgot to mention when we released CryptoSys PKI Pro v11.0 in March 2016
that improves the
SIG_SignData
and SIG_SignFile
functions
and the corresponding .NET methods
Sig.SignData
,
Sig.SignFile
and
Sig.SignDigest
(the "SIG functions").
There's also some C# code that compares the old and new ways together with performance measuring code.
The SIG_SignData()
function and its companions (the "SIG functions") were designed for a simplified interface to create a signature value in base64
format directly from the message, private key file and the keyfile's password. In pseudo-code:
sig = Sign(message, keyfile, password)
This is fine for a one-off operation, but can be improved if you want to call the function repeatedly with the same private key.
As of CryptoSys PKI Pro v11.0 and above you can pass an internal key string to the "SIG functions" instead of the filename and password, so removing the need to repeatedly access and decrypt the key file.
keystr = ReadPrivateKey(keyfile, password) foreach msg in Messages: sig = Sign(message, keystr)
This improves security as well as giving speed improvements (we typically get a 20% improvement, YMMV). You can create the internal key string once and then wipe the password. The internal key is encrypted using an internal key and is only valid for the duration of the session. You can wipe this internal key when you are are finished with it.
keystr = ReadPrivateKey(keyfile, password) Wipe(password) foreach msg in Messages: sig = Sign(message, keystr) Wipe(keystr)
The following C# code shows an example to demonstrate the old way and the new way to sign an arbitrary string, and a way to measure any improvement when done repeatedly. With thanks to Pavel Vladov for his technique to Measure C# Code Performance
The private key used in the example is here and the corresponding certificate is here. Note that the arbitrary strings are just random bytes encoded in base58.
namespace RsaTestSig { class Program { // Original way to sign a string using Sig.SignData() with a key file and password public static string SignStringUsingRSAKeyFile(string Msg, string PrivateKeyFilePath, string PrivateKeyPW) { string sig; byte[] arrData; arrData = System.Text.UTF8Encoding.UTF8.GetBytes(Msg); sig = Sig.SignData(arrData, PrivateKeyFilePath, PrivateKeyPW, SigAlgorithm.Default); return sig; } // New way - pass the internal key string // -- works for Sig.SignData() in CryptoSys PKI v11.0 and above public static string SignStringUsingRSAIntKeyString(string Msg, string PrivateKeyString) { string sig; byte[] arrData; arrData = System.Text.UTF8Encoding.UTF8.GetBytes(Msg); sig = Sig.SignData(arrData, PrivateKeyString, null, SigAlgorithm.Default); return sig; } static void quick_demo() { string keyFile = @"AlicePrivRSASign.epk"; string msg; string sig; // 1. Original method using key file // -- key file is read each time, convenient for a one-off. msg = "abc"; Console.WriteLine("MSG={0}", msg); sig = SignStringUsingRSAKeyFile(msg, keyFile, "password"); Console.WriteLine("SIG={0}", sig); Debug.Assert(sig.Length > 0); // 2. Read in private key to an encrypted internal string // -- valid until process ends. // Read in the private key once StringBuilder sbKeyStr; sbKeyStr = Rsa.ReadPrivateKey(keyFile, "password"); Console.WriteLine("Key size={0}", Rsa.KeyBits(sbKeyStr.ToString())); for (int i = 0; i < 3; i++) { msg = Cnv.ToBase58(Rng.Bytes(48)); Console.WriteLine("MSG{0}={1}", i+1, msg); sig = SignStringUsingRSAIntKeyString(msg, sbKeyStr.ToString()); Console.WriteLine("SIG{0}={1}", i+1, sig); Debug.Assert(sig.Length > 0); } // Clean up Wipe.String(sbKeyStr); } // Code performance technique from Pavel Vladov // http://www.pvladov.com/2012/06/measure-code-performance.html static double computeAverageTime(int repetitions, long[] times) { // Sort the elapsed times for all test runs Array.Sort(times); // Calculate the total times discarding // the 5% min and 5% max test times long totalTime = 0; int discardCount = (int)Math.Round(repetitions * 0.05); int count = repetitions - discardCount; for (int i = discardCount; i < count; i++) { totalTime += times[i]; } double averageTime = ((double)totalTime) / (count - discardCount); return averageTime; } static void test_performance() { string keyFile = @"AlicePrivRSASign.epk"; string msg; string sig; string keyStr; int nbits; const int repetitions = 100; long[] times = new long[repetitions]; double avgTime1, avgTime2; msg = Cnv.ToBase58(Rng.Bytes(48)); Console.WriteLine("MSG={0}", msg); Console.WriteLine("Doing {0} tests using SignStringUsingRSAKeyFile()...", repetitions); for (int i = 0; i < repetitions; i++) { Stopwatch stopwatch = Stopwatch.StartNew(); sig = SignStringUsingRSAKeyFile(msg, keyFile, "password"); Debug.Assert(sig.Length > 0); stopwatch.Stop(); times[i] = stopwatch.ElapsedMilliseconds; } avgTime1 = computeAverageTime(repetitions, times); Console.WriteLine("Average time: {0:N2} ms", avgTime1); keyStr = Rsa.ReadPrivateKey(keyFile, "password").ToString(); nbits = Rsa.KeyBits(keyStr); Console.WriteLine("Key size={0}", nbits); Debug.Assert(nbits > 0); Console.WriteLine("Doing {0} tests using SignStringUsingRSAIntKeyString()...", repetitions); for (int i = 0; i < repetitions; i++) { Stopwatch stopwatch = Stopwatch.StartNew(); sig = SignStringUsingRSAIntKeyString(msg, keyStr); Debug.Assert(sig.Length > 0); stopwatch.Stop(); times[i] = stopwatch.ElapsedMilliseconds; } avgTime2 = computeAverageTime(repetitions, times); Console.WriteLine("Average time: {0:N2} ms", avgTime2); Console.WriteLine("Improvement = {0:N2}%", (avgTime1 - avgTime2) * 100 / avgTime1); } static void Main(string[] args) { quick_demo(); //test_performance(); } } }
The output for quick_demo()
should look like this:
MSG=abc SIG=YK1aePtKQDDsVCyJdM0V9VOE6DZVTO3ZoyLV9BNcYmep0glwxU5mUQcLAUTUOETImTIN2Pp4Gffr xqdxUoczLshnXBNhg7P4ofge+WlBgmcTCnVv27LHHZpmdEbjTg6tnPMb+2b4FvMZ0LfkMKXyiRVTmG4A NyAmHH6QIsDZ8R8= Key size=1024 MSG1=48Q8BvG4GjwxCTZPEJseWYpxGHbEHtY9RbmJdFFDp91u5KELoZWdNLZabW57JaN9fP SIG1=s1Pv47e/G/tvK74jBLFEqDAWTFCRAvbwvSfWF6W5fPOK0JheotkFrn2bJTwzc2Hlg1PVUdhKEmv wZ9Z5KL2Wz7BuhVpbxnZ3LidyiYSKEWw88hSv0EZ72/Xjm9mBzA50mhYlVozYkjlM/28a6JXbbXSgNQT 6irbEJAXsowGmr3Y= MSG2=4QsRTC8GwEsc6WedRJQagrXtd341cAEQm4bo9Y2XnGmAtPAJWtuj1fE2fcDrXpxUFK SIG2=kj4tqg7cjLjSG+40Lxn+wDO2CKa7yegzdGr0NhhmyiuiwzWgA5KTtaiT+En8XmTl20H1EYSsTFL Z22TBiOF/wM8i7feoBgrT+wnaUrDqUyMcZ4kzi91GiyL0N0rSojMP5+PLVhPghpLJlI1sr4x/DIxa27x WAQb0C+wQq7HG6PU= MSG3=26b4zsubrdosZscLEG4ZGXntB1GKZTFYq9g34Qj2cQ7AAL4TKYLTWrmxzrQxnMBR3N SIG3=vMBAsq45AnaK/dYGWkBqGm++0LbqBjvmo9DTI+NSQyiY9hDn9YtDAtpB1Vm19Aa63SwD8JFwTrI ACxB4zoJ1QzZq/lsX5jB3frp+1Q3uQrddaeVoY5P/NBcAqLJWdJlmO9CBDhlmDQP/uX9IYWVi8/6/P7s wIhhsbTgfDudO4As=
For more information, or to comment on this page, please send us a message.
This page last updated 15 August 2025