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 😉
Encryption (part 1, basics), AES
After serializing, storing and sending data across the network, we are still working on the same subject. Cryptography does belong into that area as well.
The simplified process is:
data => password => encryption => cipher => password => decryption => data
I am going to concentrate on the well-known AES algorithm. It has a good and reliable history. You can hardly hack it with eg. three allowable attempts per five minutes. As usual I have added links to this post for further explanations. AES (Advanced Encryption Standard) is reportedly used by the U.S. government and was recommended by the U.S. National Institute of Standards and Technology (NIST).
The .Net Framework supports cryptography in the System.Security.Cryptography namespace. The AES algorithm can be found in the AesManaged class. It uses symmetric encryption; there is only one password.
Besides the password there is a so-called Initialization Vector (IV). It adds more randomness. The IV ensures that data does not result in the same cipher data. You could pretty much say that each result is unique. Repetitive patterns are much harder to find.
Users enter passwords, but the AES algorithm does not use these directly. It requires keys instead. Therefore we must translate passwords into keys.
Key sizes of 128, 160, 192, 224, and 256 bits are supported by the algorithm, but only the 128, 192, and 256-bit key sizes are specified in the AES standard.
Block sizes of 128, 160, 192, 224, and 256 bits are supported by the algorithm, but only the 128-bit block size is specified in the AES standard.
using System; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text;
Some extension methods can be found in System.Linq. We use string.Join().
private static AesManaged GetAesAlgo() { AesManaged lAlgo = new AesManaged(); lAlgo.BlockSize = 128; lAlgo.KeySize = 128; lAlgo.Key = ASCIIEncoding.ASCII.GetBytes("Bastian/M/K/Ohta"); // 128 bits = 16 bytes = 16 ASCII characters lAlgo.IV = ASCIIEncoding.ASCII.GetBytes("1234567890123456"); // 128 bits = 16 bytes = 16 ASCII characters Console.WriteLine("Aes block size is " + lAlgo.BlockSize + " bits"); return lAlgo; } // static byte[] Encrypt(SymmetricAlgorithm xAlgo, byte[] xData) { ICryptoTransform lEncryptor = xAlgo.CreateEncryptor(xAlgo.Key, xAlgo.IV); return lEncryptor.TransformFinalBlock(xData, 0, xData.Length); } // static byte[] Decrypt(SymmetricAlgorithm xAlgo, byte[] xCipher) { ICryptoTransform lDecryptor = xAlgo.CreateDecryptor(xAlgo.Key, xAlgo.IV); return lDecryptor.TransformFinalBlock(xCipher, 0, xCipher.Length); } // public static void Test1() { string lText = "Let's encrypt and decrypt this text :)"; byte[] lTextAsBytes = UnicodeEncoding.Unicode.GetBytes(lText); Console.WriteLine("text length in characters " + lText.Length); Console.WriteLine("text length in bytes " + lTextAsBytes.Length); using (SymmetricAlgorithm lAlgo = GetAesAlgo()) { byte[] lEncrypted = Encrypt(lAlgo, lTextAsBytes); Console.WriteLine("encrypted data size in bytes " + lTextAsBytes.Length); byte[] lDecrypted = Decrypt(lAlgo, lEncrypted); Console.WriteLine("decrypted text length in bytes " + lDecrypted.Length); string lResult = UnicodeEncoding.Unicode.GetString(lDecrypted); Console.WriteLine("text length in characters now " + lResult.Length); Console.WriteLine(); Console.WriteLine("the text was: \"" + lResult + "\""); } Console.ReadLine(); } //
example output:
text length in characters 38
text length in bytes 76
Aes block size is 128 bits
encrypted data size in bytes 76
decrypted text length in bytes 76
text length in characters now 38the text was: “Let’s encrypt and decrypt this text :)”
The above code was for warming up. It encrypts/decrypts all data in memory without the use of any stream. The next example uses the FileStream class to store a file to the desktop. Some people prefer to use the memory stream in their examples. I personally do not get the point of this as it is not a requirement. I did show above that you can convert something in memory without using the MemoryStream class.
private static AesManaged GetAesAlgo() { AesManaged lAlgo = new AesManaged(); lAlgo.BlockSize = 128; lAlgo.KeySize = 128; lAlgo.Key = ASCIIEncoding.ASCII.GetBytes("Bastian/M/K/Ohta"); // 128 bits = 16 bytes = 16 ASCII characters lAlgo.IV = ASCIIEncoding.ASCII.GetBytes("1234567890123456"); // 128 bits = 16 bytes = 16 ASCII characters Console.WriteLine("Aes block size is " + lAlgo.BlockSize + " bits"); return lAlgo; } // public static void Test2() { string lDesktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + @"\"; string lFileName = lDesktopPath + "Encrypted.bin"; string lText = "Let's encrypt and decrypt this text :)"; byte[] lTextAsBytes = UnicodeEncoding.Unicode.GetBytes(lText); Console.WriteLine("text length in characters " + lText.Length); Console.WriteLine("text length in bytes " + lTextAsBytes.Length); try { using (SymmetricAlgorithm lAlgo = GetAesAlgo()) { EncryptToStream(lAlgo, lFileName, lTextAsBytes); FileInfo lFileInfo = new FileInfo(lFileName); Console.WriteLine("encrypted file size in bytes " + lFileInfo.Length); int lLength; byte[] lDecrypted = DecryptFromStream(lAlgo, lFileName, out lLength); Console.WriteLine("decrypted text length in bytes " + lDecrypted.Length); //Console.WriteLine("decrypted text length in bytes " + lLength); string lResult = UnicodeEncoding.Unicode.GetString(lDecrypted); //string lResult = UnicodeEncoding.Unicode.GetString(lDecrypted, 0, lLength); Console.WriteLine("text length in characters now " + lResult.Length); Console.WriteLine(); Console.WriteLine("text before encryption: \"" + lText + "\""); Console.WriteLine("text after decryption: \"" + lResult + "\""); } } catch (Exception ex) { Console.WriteLine(ex.Message); } Console.ReadLine(); } // static void EncryptToStream(SymmetricAlgorithm xAlgo, string xFileName, byte[] xData) { ICryptoTransform lEncryptor = xAlgo.CreateEncryptor(xAlgo.Key, xAlgo.IV); System.IO.File.Delete(xFileName); using (FileStream lFileStream = new FileStream(xFileName, FileMode.CreateNew)) { using (CryptoStream lCryptoStream = new CryptoStream(lFileStream, lEncryptor, CryptoStreamMode.Write)) { lCryptoStream.Write(xData, 0, xData.Length); } } } // static byte[] DecryptFromStream(SymmetricAlgorithm xAlgo, string xFileName, out int xLength) { ICryptoTransform lDecryptor = xAlgo.CreateDecryptor(xAlgo.Key, xAlgo.IV); using (FileStream lFileStream = new FileStream(xFileName, FileMode.Open)) { using (CryptoStream lCryptoStream = new CryptoStream(lFileStream, lDecryptor, CryptoStreamMode.Read)) { int lLength = (int)lFileStream.Length; byte[] lResult = new byte[lLength]; xLength = lCryptoStream.Read(lResult, 0, lLength); return lResult; } } } //
example output:
text length in characters 38
text length in bytes 76
Aes block size is 128 bits
encrypted file size in bytes 80
decrypted text length in bytes 80
text length in characters now 40text before encryption: “Let’s encrypt and decrypt this text :)”
text after decryption: “Let’s encrypt and decrypt this text 🙂 ”
I added a typical beginner’s mistake. And to point it out I printed a lot of text messages about the data sizes. Comment and uncomment the following lines to correct the mistake:
Console.WriteLine("decrypted text length in bytes " + lDecrypted.Length); //Console.WriteLine("decrypted text length in bytes " + lLength); string lResult = UnicodeEncoding.Unicode.GetString(lDecrypted); //string lResult = UnicodeEncoding.Unicode.GetString(lDecrypted, 0, lLength); CHANGE THE ABOVE LINES TO: //Console.WriteLine("decrypted text length in bytes " + lDecrypted.Length); Console.WriteLine("decrypted text length in bytes " + lLength); //string lResult = UnicodeEncoding.Unicode.GetString(lDecrypted); string lResult = UnicodeEncoding.Unicode.GetString(lDecrypted, 0, lLength);
example output:
text length in characters 38
text length in bytes 76
Aes block size is 128 bits
encrypted file size in bytes 80
decrypted text length in bytes 76
text length in characters now 38text before encryption: “Let’s encrypt and decrypt this text :)”
text after decryption: “Let’s encrypt and decrypt this text :)”
The block size is 128 bits (=16 bytes). 76 bytes are not a multiple of 16, but 80 bytes are. This results in a larger file, which uses padding at the end. Taking the wrong size did result in a wrong decrypted text.
Usually you do not store passwords. Passwords are used as keys to decrypt data. If the password is wrong, then the decoded data is wrong. Some programmers hardcode passwords to lazily compare them against password entries. This is really bad practice!
Anyhow, some applications do store passwords to make logins easier. Eg. your internet browser has such functionality. In this case you should salt the result. Salt makes cracking passwords more time-consuming, it doesn’t stop criminals though. Nevertheless, every little helps. John Doe will not be able to use your salted password.
The good old hardcore hackers did not care much about passwords. There was no need to crack them. Hackers had freezer modules that could freeze the computer and gave access to all memory including the running assembler code. You only needed to freeze the PC when it was asking for a password. You checked where your program counter was and then did the same right after you had entered the password.
All you had to do was to add an unconditional branch command to bypass this password request. Hackers were then looking for the same code sequence on the disk and changed it accordingly. These days work a bit different.
But why am I telling this? I want to emphasize that you have to encrypt executable code as well to protect yourself efficiently. It is not enough to only encrypt data. The best code decrypts itself bit by bit at runtime.
Back to salt.
Sometimes users use the same password. When you store these passwords with individual salt values in your database, then they do look entirely different. Again, this makes John Doe’s criminal life or his simple temptation harder. A static salt value is not a good idea. You’d better store the salt value right next to your password. You can even encrypt the salt value with hardcoded algorithm parameters. This makes it a bit safer again.
public static void Test3() { // variable salt Console.WriteLine("random salt"); string lPassword = "MySooperDooperSekr1tPa$$wörd"; byte[] lKey, lSalt; GenerateSaltedKey(lPassword, out lKey, out lSalt, 128); Console.WriteLine("password: " + lPassword); Console.WriteLine("key: " + string.Join(" ", lKey)); Console.WriteLine("salt: " + string.Join(" ", lSalt)); Console.WriteLine(); Console.WriteLine("saving to database"); Console.WriteLine("[Base64String] key: " + Convert.ToBase64String(lKey)); Console.WriteLine("[Base64String] salt: " + Convert.ToBase64String(lSalt)); Console.WriteLine(); // backtest byte[] lKey2 = GenerateKey128(lPassword, lSalt); // do we get the same key? Console.WriteLine("key: " + string.Join(" ", lKey)); Console.WriteLine("key: " + string.Join(" ", lKey2)); if (lKey.SequenceEqual(lKey2)) Console.WriteLine("keys are equal"); else Console.WriteLine("Oh dear, something went wrong!"); Console.WriteLine(); // static salt Console.WriteLine("static salt"); for (int i = 0; i < 10; i++) { byte[] lStaticKey = GenerateKey128(lPassword); Console.WriteLine(i + " static key " + string.Join(" ", lStaticKey)); } Console.ReadLine(); } // private const int _Iterations = 2500; private static void GenerateSaltedKey(string xPassword, out byte[] xKey, out byte[] xSalt, int xKeySize = 256) { xKeySize /= 8; // now in bytes var keyGenerator = new Rfc2898DeriveBytes(xPassword, xKeySize, _Iterations); xKey = keyGenerator.GetBytes(xKeySize); xSalt = keyGenerator.Salt; } // private static byte[] GenerateKey128(string xPassword) { byte[] lSalt = { 252, 132, 52, 13, 64, 158, 12, 10, 50, 80, 74, 63, 15, 54, 76, 246 }; // 16 bytes == 128 bits return new Rfc2898DeriveBytes(xPassword, lSalt, _Iterations).GetBytes(16); } // private static byte[] GenerateKey128(string xPassword, byte[] xSalt) { return new Rfc2898DeriveBytes(xPassword, xSalt, _Iterations).GetBytes(16); } //
example output:
random salt
password: MySooperDooperSekr1tPa$$wörd
key: 139 209 30 237 82 66 102 245 193 89 175 218 62 190 7 8
salt: 58 247 144 225 92 236 82 167 255 185 6 135 45 104 86 98saving to database
[Base64String] key: i9Ee7VJCZvXBWa/aPr4HCA==
[Base64String] salt: OveQ4VzsUqf/uQaHLWhWYg==key: 139 209 30 237 82 66 102 245 193 89 175 218 62 190 7 8
key: 139 209 30 237 82 66 102 245 193 89 175 218 62 190 7 8
keys are equalstatic salt
0 static key 103 183 126 80 87 205 118 92 45 195 66 134 124 104 32 206
1 static key 103 183 126 80 87 205 118 92 45 195 66 134 124 104 32 206
2 static key 103 183 126 80 87 205 118 92 45 195 66 134 124 104 32 206
3 static key 103 183 126 80 87 205 118 92 45 195 66 134 124 104 32 206
4 static key 103 183 126 80 87 205 118 92 45 195 66 134 124 104 32 206
5 static key 103 183 126 80 87 205 118 92 45 195 66 134 124 104 32 206
6 static key 103 183 126 80 87 205 118 92 45 195 66 134 124 104 32 206
7 static key 103 183 126 80 87 205 118 92 45 195 66 134 124 104 32 206
8 static key 103 183 126 80 87 205 118 92 45 195 66 134 124 104 32 206
9 static key 103 183 126 80 87 205 118 92 45 195 66 134 124 104 32 206
I did set the number of iterations to 2500 (private const int _Iterations = 2500). This is an arbitrary number. A value of above 1000 is generally recommended. It tells the key/IV generator how many times it should run. Think of recursion to get an idea. Make sure you use the same number of iterations whenever you want the same results.