Blog Archives
Encryption (part 2, basics, advanced), RSA
The last post dealt with symmetric encryption. Today we are going to deal with asymmetric encryption. This involves two keys; a public and a private key.
The .NET Framework provides the RSACryptoServiceProvider and DSACryptoServiceProvider classes for asymmetric encryption.
The public key must be known to encrypt data. The private key can be used to decrypt data. You cannot use a public key to decrypt data. A good YouTube introduction about private and public keys can be found here.
The two keys are mathematically related with each other. But you cannot determine the private key by knowing the public key and vice versa.
RSA (RSACryptoServiceProvider class) is a public key algorithm with key sizes ranging from 1024 to 4096. The key sizes are obviously much larger than for the AES algorithm. They did not contribute to increased security though.
We start with key generations, which are returned in the XML format.
public static void Test4() { string lPublicKey, lPrivateKey; GetKeys(out lPublicKey, out lPrivateKey); Console.WriteLine(Environment.NewLine + " P U B L I C K E Y : " + Environment.NewLine); Console.WriteLine(lPublicKey); Console.WriteLine(Environment.NewLine + " P R I V A T E K E Y : " + Environment.NewLine); Console.WriteLine(lPrivateKey); Console.ReadLine(); } // private static void GetKeys(out string xPublicKey, out string xPrivateKey) { using (RSACryptoServiceProvider lRSA = new RSACryptoServiceProvider()) { xPublicKey = lRSA.ToXmlString(false); xPrivateKey = lRSA.ToXmlString(true); } } //
edited/formatted example output:
<!--P U B L I C K E Y :--> <RSAKeyValue> <Modulus> qfwkAyqbHnI+a2f/xh9+3zufUE6drTrT7i83t5lFwvVQp1QGxSp0wrkNl3 alkuUw48N4vfn3vbjpViB3fb0ndmpyRKx1Tffa2gYmYe2DNZw79kAR6mcJLIsedOluon93etw3cHtTef zXtj9aefTtAS6R/VNkYBKyPiwz4JbR7nM= </Modulus> <Exponent>AQAB</Exponent> </RSAKeyValue> <!--P R I V A T E K E Y :--> <RSAKeyValue> <Modulus> qfwkAyqbHnI+a2f/xh9+3zufUE6drTrT7i83t5lFwvVQp1QGxSp0wrkNl3 alkuUw48N4vfn3vbjpViB3fb0ndmpyRKx1Tffa2gYmYe2DNZw79kAR6mcJLIsedOluon93etw3cHtTef zXtj9aefTtAS6R/VNkYBKyPiwz4JbR7nM= </Modulus> <Exponent>AQAB</Exponent> <P> 0K3ebgo/zcAZ5JHM2s7O78SdiLnXthO0IGOKYHfkZeegXMb8AzKpj38DwkNigkQ+rptFmwQuMTl9Eura+BtTGQ== </P> <Q> 0IgDlR6wS5TqnJmCpqb70fqMRhrMLEwFAvJlS3SFxLd+TiZSX+up68s+mg8BhhcsgkSnaSUxkzm/JYfZhxkraw== </Q> <DP> ibEf4kXbAbzumNXncL0i6Cw4sh3GCrsHkJN9m9egGely86TMZqPIJAnwBf+GgWPcZEPJ8tYYUJyZPaE/qJQHCQ== </DP> <DQ> MsHonVNq9fq5YIS9GHNsuB+UJTxAlkeqsJzvqv4h0VAYnk0Vn+Ns6Mf/5N/iLxFU9CBh32X+OyfDLw9yE0A9IQ== </DQ> <InverseQ> CgD4wQfd6bfcJgHKmmkXmoWTkz3VT722jiQ5mwSIjbGo6sZ0zBBF8qUJNzsybpg+ilqzStcOQqwO2lqwHqnw8g== </InverseQ> <D> TiN3snTtZWuCwgDGlJ55xcg0jcf1t2Hpdf4CkMVGSj5WWvTHP+8qSTCjzNJffk0Y0jpS0JGNjor nyA2YoBZJgufWeEv2rNfTkblVaLx+1nWlFJ2hWz80XbaBeK4zpA1sf8SwnUefdxnqmjDs0Jc0vUwnhFP HfXZ6hD02uTwJxSE= </D> </RSAKeyValue>
You do not have to work with XML. You can also access binary data directly by using the methods ExportParameters or ExportCspBlob. A definition for blob can be found here.
private static void GetKeys() { using (RSACryptoServiceProvider lRSA = new RSACryptoServiceProvider()) { RSAParameters xPublicKey = lRSA.ExportParameters(false); byte[] lP = xPublicKey.P; byte[] lQ = xPublicKey.Q; byte[] lModulus = xPublicKey.Modulus; byte[] lExponent = xPublicKey.Exponent; ... byte[] xPrivateKey = lRSA.ExportCspBlob(true); } } //
We have two keys now. Let’s put that into practice.
private static void GetKeys(out string xPublicKey, out string xPrivateKey) { using (RSACryptoServiceProvider lRSA = new RSACryptoServiceProvider()) { xPublicKey = lRSA.ToXmlString(false); xPrivateKey = lRSA.ToXmlString(true); } } // public static void HowToUseAsymmetricEncryption() { string lText = "Let's encrypt and decrypt this text :)"; byte[] lData = UnicodeEncoding.Unicode.GetBytes(lText); string lPublicKey, lPrivateKey; GetKeys(out lPublicKey, out lPrivateKey); byte[] lEncrypted; using (RSACryptoServiceProvider lRSA = new RSACryptoServiceProvider()) { lRSA.FromXmlString(lPublicKey); lEncrypted = lRSA.Encrypt(lData, false); } byte[] lDecrypted; using (RSACryptoServiceProvider lRSA = new RSACryptoServiceProvider()) { lRSA.FromXmlString(lPrivateKey); lDecrypted = lRSA.Decrypt(lEncrypted, false); } string lResult = UnicodeEncoding.Unicode.GetString(lDecrypted); Console.WriteLine(lResult); Console.ReadLine(); } //
There are many ways to store keys. One is to store them on the computer, so they can be accessed from anywhere. This can be achieved by setting “RSACryptoServiceProvider.UseMachineKeyStore” to true. Or you can instantiate CspParameters and set the UseMachineKeyStore in Flags (see following example).
The UseMachineKeyStore property applies to all code in the current application domain, whereas a CspParameters object applies only to classes that explicitly reference it. By instanciating RSACryptoServiceProvider with CspParameters as the constructor parameter, the keys get stored automatically. There is no explicit Store() method.
To delete the keys simply use the RSACryptoServiceProvider instance, set PersistKeyInCsp to false and then call the method Clear().
Uncomment these two lines in the code and you will see the exception “Key not valid for use in specified state”. The keys of the new instance are not the same anymore.
private static void GetKeys(out string xPublicKey, out string xPrivateKey) { using (RSACryptoServiceProvider lRSA = new RSACryptoServiceProvider()) { xPublicKey = lRSA.ToXmlString(false); xPrivateKey = lRSA.ToXmlString(true); } } // private static CspParameters GetCspParameters() { CspParameters lCspParams = new CspParameters(); lCspParams.Flags |= CspProviderFlags.UseMachineKeyStore; // all users of this computer have access //lCspParams.Flags |= CspProviderFlags.UseUserProtectedKey; // a popup window will ask for confirmation //lCspParams.Flags |= CspProviderFlags.UseNonExportableKey; // you can use the key, but not read or export it lCspParams.KeyContainerName = "MySecretKeyContainer"; return lCspParams; } // public static void HowToUseAsymmetricEncryption2() { CspParameters lCspParams; string lText = "Let's encrypt and decrypt this text :)"; byte[] lData = UnicodeEncoding.Unicode.GetBytes(lText); string lPublicKey, lPrivateKey; GetKeys(out lPublicKey, out lPrivateKey); try { lCspParams = GetCspParameters(); // new instance byte[] lEncrypted; using (RSACryptoServiceProvider lRSA = new RSACryptoServiceProvider(lCspParams)) { lRSA.FromXmlString(lPublicKey); lRSA.FromXmlString(lPrivateKey); // assigned here to show that we assigned it to a different RSACryptoServiceProvider instance lEncrypted = lRSA.Encrypt(lData, false); byte[] x = lRSA.ExportCspBlob(true); // how to delete a KeyContainer // lRSA.PersistKeyInCsp = false; // lRSA.Clear(); } lCspParams = GetCspParameters(); // new instance to demonstrate the independence using (RSACryptoServiceProvider lRSA = new RSACryptoServiceProvider(lCspParams)) { byte[] lDecrypted = lRSA.Decrypt(lEncrypted, false); string lResult = UnicodeEncoding.Unicode.GetString(lDecrypted); Console.WriteLine(lResult); } } catch (Exception ex) { Console.WriteLine(ex.Message + Environment.NewLine); Console.WriteLine(ex.StackTrace); } Console.ReadLine(); } //
I would recommend playing with the “CspProviderFlags.UseUserProtectedKey” flag. A pop-up should show up when you uncomment the line in method GetCspParameters().
Also test the “spProviderFlags.UseNonExportableKey” flag. I added the nondescript line byte[] x = lRSA.ExportCspBlob(true) to demonstrate the effect .
The nicest exception message wins the contest 😉