Request a Resource
Responding to a Request
Sniffing HTTP Requests and Responses
Use Fiddler to sniff: http://www.fiddler2.com.
Understanding How ASP.NET Events Work
Examing the ASP.NET Pipeline
Writing HTTP Modules
XSS: Cross Site Scripting 跨站脚本攻击
The XSS Cheat Sheet: http://ha.ckers.org/xss.html.
The Microsoft AntiXSS Library: http://www.codeplex.com/antixss.
Encodings Supported by the Microsoft Anti-XSS Library:
ENCODING |
USAGE |
HtmlEncode | Use this when untrusted input is assigned to HTML output, unless it is assigned to an HTML attribute. |
HtmlAttributeEncode | Use this when untrusted input is assigned to an HTML attribute (such as id, name, width, or height). |
JavaScriptEncode | Use this when untrusted input is used within JavaScript. |
UrlEncode | Use this when untrusted input is used to produce (or is used within) a URL. |
VisualBasicScriptEncode | Use this when untrusted input is used to within VBScript. |
XmlEncode | Use this when untrusted input is assigned to XML output, unless it is assigned to an XML attribute. |
XmlAttributeEncode | Use this when untrusted input is assigned to an XML attribute. |
Cross Site Request Forgery (CSRF) 跨站请求伪造
For a CSRF attack to work, the following conditions must be met:
The common mitigation technique against CSRF for ASP.NET sites is to use ViewState in combination with a ViewStateUserKey.
If the ViewStateUserKey does not meet your needs, another method of mitigation is to add a token to every form, which is verified when the form is submitted. You must generate a token for every session, store it in session state or in a cookie on the user’s machine, insert the token (or a value generated from it ) into each form, and check it with every form submission. And, you can automate the entire process by implementing an HTTP Module.
To add CSRF protection to every form submission, you should implement the following actions:
HTTP Module to Protect Against CSRF Attacks:
using System; using System.Collections.Generic; using System.Linq; using System.Globalization; using System.Web; using System.Web.UI; namespace AntiCSRF { public class AntiCSRF : IHttpModule { public void Dispose() { } public void Init(HttpApplication context) { context.PreSendRequestHeaders += new EventHandler(PreSendRequestHeaders); context.PreRequestHandlerExecute += new EventHandler(PreRequestHandlerExecute); } private static void PreSendRequestHeaders(object source, EventArgs args) { HttpApplication application = (HttpApplication)source; HttpContext context = application.Context; if (context.Handler != null) { Page page = context.Handler as Page; if (page != null) { HttpCookie csrfCookie = new HttpCookie("__CSRFCOOKIE") { Value = context.Items["CSRFContext"].ToString(), HttpOnly = true }; context.Response.Cookies.Add(csrfCookie); } } } private static void PreRequestHandlerExecute(object source, EventArgs args) { HttpApplication application = (HttpApplication)source; HttpContext context = application.Context; if (context.Handler != null) { Page page = context.Handler as Page; if (page != null) { page.PreRender += PagePreRender; if (context.Request.HttpMethod.Equals("POST",StringComparison.Ordinal)) { if (context.Request != null) { HttpCookie csrfCookie = context.Request.Cookies["__CSRFCOOKIE"]; string csrfFormField = context.Request.Form["__CSRFTOKEN"]; if (string.IsNullOrEmpty(csrfFormField) && (csrfCookie == null || string.IsNullOrEmpty(csrfCookie.Value))) throw new Exception("Cookie and form field missing"); if (csrfCookie == null || string.IsNullOrEmpty(csrfCookie.Value)) throw new Exception("Cookie missing"); if (string.IsNullOrEmpty(csrfFormField)) throw new Exception("Form field missing"); string tokenField = string.Empty; ObjectStateFormatter formatter = new ObjectStateFormatter(); try { tokenField = formatter.Deserialize(context.Request.Form["__CSRFTOKEN"]) as string; } catch { throw new Exception("Form field format error"); } if (csrfCookie.Value.Equals(tokenField)) throw new Exception("Mismatched CSRF tokens"); } } } } } private static void PagePreRender(object source, EventArgs args) { Page page = source as Page; if (page != null && page.Form != null) { string csrfToken; HttpContext context = HttpContext.Current; if (context.Request == null || context.Request.Cookies == null || context.Request.Cookies["__CSRFCOOKIE"] == null || string.IsNullOrEmpty(context.Request.Cookies["__CSRFCOOKIE"].Value)) { csrfToken = Guid.NewGuid().ToString("D", CultureInfo.InvariantCulture); context.Items["CSRFContext"] = csrfToken; } else csrfToken = context.Request.Cookies["__CSRFCOOKIE"].Value; ObjectStateFormatter formatter = new ObjectStateFormatter(); page.ClientScript.RegisterHiddenField("__CSRFTOKEN", formatter.Serialize(csrfToken)); } } } }
ViewState validation ensures that no one can tamper with the contents.
ViewState encrytion ensures that no one can view the data.
To enforce ViewState encryption for entire application, set the viewStateEncrytionMode attribute on the pages element in web.config:
<pages ... viewStateEncryptionMode="Always" ... />
Prgrammatically request encryption on a per-page basis by calling Page.RegisterRequiresViewSateEncryption(), or by setting the ViewStateEncryptionMode attribute in the page directive:
<%@ Page Language="C#" ... ViewStateEncryptionMode="Always" %>
Encrypting ViewState will increase the time it takes for a page to render and respond, as well as affect the size of the hidden form field. Be sure to run tests to see if any increases are acceptable in terms of load time and bandwidth.
A replay attack occurs when an attacker takes a valid ViewState from a previous erquest and sends it at a later point, or under the context of another user.
Often, a ViewState replay attack can be used in the flavor of Cross Site Request Forgery (CSRF) called a one-click attack, where a form is submitted via JavaScript to a vulnerable page.
In light of this attack method, ASP.NET provides the ViewStateUserKey property as a way to lock ViewState to a specific user or session. Generally, this value is set to either the username of a currently authenticated user, or, the session identifier for the current session.
Setting a ViewState User Key in global.asax:
<%@ Application Language="C#" %> <script runat="server"> void Application_PreRequestHandlerExecute(object sender, EventArgs e) { HttpContext context = HttpContext.Current; // Check we are actually in a webforms page. Page page = context.Handler as Page; if (page != null) { // Use the authenticated user if one is available, // so as the user key does not expire over // application recycles. if (context.Request.IsAuthenticated) { page.ViewStateUserKey = context.User.Identity.Name; } else { page.ViewStateUserKey = context.Session.SessionID; } } } </script>
Setting a ViewState User Key in a Base Class
using System; using System.Web.UI; public class ProtectedViewStatePage : Page { protected override void OnInit(EventArgs e) { if (Request.IsAuthenticated) { ViewStateUserKey = User.Identity.Name; } else { ViewStateUserKey = Session.SessionID; } base.OnInit(e); } }
You should then change the class your pages inherit from to the new base class you creaed. If you do not use code behind, then you can set the base class application-wide by using the <pages> element in web.config:
<system.web> <pages pageBaseType="ProtectedViewStatePage" /> </system.web>
Store ViewState in session:
protected override PageStatePersister PageStatePersister { get { return new SessionPageStatePersister(this); } }
Using the OutputCache directive on a page to disable browser caching:
<%@ OutputCache Location="None" VaryByParam="None" %>
or by code, add following code to Page_Load event:
Response.Cache.SetCacheability(HttpCacheability.NoCache);
Always disable caching for pages that contain sensitive data.
ASP.NET provides error events you can respond to at both a page level (Page_Error) and application level (Application_Error).
Handle errors within in a page class:
public partial class MyPage : System.Web.UI.Page { protected void Page_Error(object sender, EventArgs e) { // Log Errors. Exception ex = Server.GetLastError(); Error.Log(ex); } }
Handle errors within global.asax:
<%@ Application Language="C#" %> <script runat="server"> void Application_Error(object sender, EventArgs e) { // Log Errors. Exception ex = Server.GetLastError(); Error.Log(ex); } </script>
Some examples of exceptions that indicate a potential threat:
EXCEPTION | WHEN OCCURS |
HttpRequestValidationException | Occurs when request validation is on, and potentially threatening characters are sent with a request. |
ArgumentException | Occurs when event validation fails, indicating an attempt to fire an event that is not valid for a page. |
ViewStateException | Occurs when an invalid ViewState has been sent. |
It is possible for unhandled exception to cause your entire application to crash if they occur outside of a page request. An example might be found in a background worker thread, or within the garbage colloctor. Micorsoft recommends that an HttpModule be used to catch these types of errors:
using System; using System.Diagnostics; using System.Globalization; using System.IO; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Web; namespace WebMonitor { public class UnhandledExceptionModule: IHttpModule { static int _unhandledExceptionCount = 0; static string _sourceName = null; static object _initLock = new object(); static bool _initialized = false; public void Init(HttpApplication app) { // Do this one time for each AppDomain. if (!_initialized) { lock (_initLock) { if (!_initialized) { string webenginePath = Path.Combine(RuntimeEnvironment.GetRuntimeDirectory(), "webengine.dll"); if (!File.Exists(webenginePath)) { throw new Exception(String.Format(CultureInfo.InvariantCulture, "Failed to locate webengine.dll at '{0}'. This module requires .NET Framework 2.0.", webenginePath)); } FileVersionInfo ver = FileVersionInfo.GetVersionInfo(webenginePath); _sourceName = string.Format(CultureInfo.InvariantCulture, "ASP.NET {0}.{1}.{2}.0", ver.FileMajorPart, ver.FileMinorPart, ver.FileBuildPart); if (!EventLog.SourceExists(_sourceName)) { throw new Exception(String.Format(CultureInfo.InvariantCulture, "There is no EventLog source named '{0}'. This module requires .NET Framework 2.0.", _sourceName)); } AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(OnUnhandledException); _initialized = true; } } } } public void Dispose() { } void OnUnhandledException(object o, UnhandledExceptionEventArgs e) { // Let this occur one time for each AppDomain. if (Interlocked.Exchange(ref _unhandledExceptionCount, 1) != 0) return; StringBuilder message = new StringBuilder("\r\n\r\nUnhandledException logged by UnhandledExceptionModule.dll:\r\n\r\nappId="); string appId = (string) AppDomain.CurrentDomain.GetData(".appId"); if (appId != null) { message.Append(appId); } Exception currentException = null; for (currentException = (Exception)e.ExceptionObject; currentException != null; currentException = currentException.InnerException) { message.AppendFormat("\r\n\r\ntype={0}\r\n\r\nmessage={1}\r\n\r\nstack=\r\n{2}\r\n\r\n", currentException.GetType().FullName, currentException.Message, currentException.StackTrace); } EventLog Log = new EventLog(); Log.Source = _sourceName; Log.WriteEntry(message.ToString(), EventLogEntryType.Error); } } }
EventLog.CreateEventSource("MyWebApplication", "Application"); EventLog.WriteEntry("MyWebApplication", "Something bad happened", EventLogEntryType.Error, 101);
Using asynchronmous sending email:
<%@ Page Async="true" ... %>
using System; using System.Collections.Generic; using System.ComponentModel; using System.Net.Mail; using System.Web; public partial class _Default : System.Web.UI.Page { protected void Page_Error(object sender, EventArgs e) { MailMessage mail = new MailMessage(); // Create the message mail.From = new MailAddress("[email protected]"); mail.To.Add("[email protected]"); mail.Subject = "Unhanded exception in "+Context.Request.Path; mail.Body = Server.GetLastError().ToString(); SmtpClient smtp = new SmtpClient(); object userState = mail; //wire up the Asynce event for the send is completed smtp.SendCompleted += new SendCompletedEventHandler(smtp_SendCompleted); smtp.SendAsync(mail, userState); } void smtp_SendCompleted(object sender, AsyncCompletedEventArgs e) { //Get the Original MailMessage object MailMessage mail = (MailMessage)e.UserState; if (e.Error != null) { LogErrorElsewhere("Error {1} occurred when sending mail [{0}] ", mail.Subject, e.Error.ToString()); } } }
string counterCategory = "SecuringASPNet"; if (!PerformanceCounterCategory.Exists(counterCategory)) { CounterCreationDataCollection counterCreationDataCollection = new CounterCreationDataCollection(); counterCreationDataCollection.Add( new CounterCreationData("BadGuysFound", "Total number of bad guys detected", PerformanceCounterType.NumberOfItems32) ); counterCreationDataCollection.Add( new CounterCreationData("BadGuysFoundPerSecond", "How many bad guys have been detected", PerformanceCounterType.RateOfCountsPerSecond32) ); PerformanceCounterCategory.Create(counterCategory, "My category description/Help", PerformanceCounterCategoryType.SingleInstance, counterCreationDataCollection); }
[RunInstaller(true)] public class CountersInstaller : PerformanceCounterInstaller { public CountersInstaller() { this.CategoryName = "SecuringASPNet"; Counters.Add( new CounterCreationData("BadGuysFound", "Total number of bad guys detected", PerformanceCounterType.NumberOfItems32) ); Counters.Add( new CounterCreationData("BadGuysFoundPerSecond", "How many bad guys have been detected", PerformanceCounterType.RateOfCountsPerSecond32) ); }
Use counters:
PerformanceCounter badGuysFound = new PerformanceCounter("SecuringASPNet", "BadGuysFound", false); PerformanceCounter badGuysFoundPerSecond = new PerformanceCounter("SecuringASPNet", "BadGuysFoundPerSecound", false); badGuysFound.Increment(); badGuysFoundPerSecond.IncrementBy(1);
Controlling Robots with a Metatag
<html> <head> <title> ... </title> <meta name="ROBOTS" content="NOINDEX. NOFOLLOW" /> </head> </html>
Controlling Robots with robots.txt
A sample robots.txt file:
# This is a comment in a robots.txt file User-Agent: r2d2 Disallow: User-Agent: c3po Disallow: /trashCompactor User-Agent: * Disallow: /deathStarPlans Disallow: /logs
A sample robots.txt file that stops all crawling
User-agent: * Disallow: /
Unfortunately, a robots.txt file can only contain disallowed areas. Attackers often check this file for directories where robots should not go, ant attemp to load those directoies to see what happens. One other thing to note is that the robots.txt file should be in a Unix text format — only LineFeed characters marking the end of a file, not the Carriage Return and Line Feed combination that Windows uses. You can use Visual Studio to save a file in the correct format by selecting File | Advanced Save and choosing Unix from the Line Endings drop-down menu.
encrypt a section in the web.config:
aspnet_regiis -pef SecretSection
The drawback to this apporach is that, if the web.config file moved to another server, decryption will fail. So, if you have multiple servers in a Web farm, the command must be run on every server. For this scenario, you must use a RSA key container that can be exported from one machine and imported onto another one. In an elevated command prompt, the following steps will create an RSA key container and then export it:
aspnet_regiis -pc MyConfigurationKey -size 2048 –exp
aspnet_regiis -px MyConfigurationKey c:\myconfi gurationkey.xml
aspnet_regiis -pi MyConfigurationKey myconfi gurationkey.xml
aspnet_regiis -pa MyConfigurationKey "machineName\Account"
Once you move to a key container, you must add a section to your configuration file to tell ASP.NET that you want to use your new key container:
<configuration> <configProtectedData> <providers> <remove name="RsaProtectedConfigurationProvider"/> <add name="RsaProtectedConfigurationProvider" type="System.Configuration.RsaProtectedConfigurationProvider, System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" keyContainerName="MyConfigurationKey" cspProviderName="" useOAEP="false" useMachineContainer="true" /> </providers> </configProtectedData> </configuration>
You can then encrypt sections by using your new provider:
aspnet_regiis -pe SectionName -prov RsaProtectedConfigurationProvider
Use the –pd switch, to decrypt a configuration section, use the –pz switch to delete a key container.
The most common algorithms in use are MD5 and SHA1. However, both of these algorithms have been shown to have weaknesses, and should be avoided whenever possible. The current recommended algorithms are SHA256 and SHA512.
Use the SHA256 algorithm to calculaet a hash for the provided string:
private string CalculateSHA256Hash(string input) { // Encode the input string into a byte array. byte[] inputBytes = Encoding.UTF8.GetBytes(input); // Create an instance of the SHA256 algorithm class // and use it to calculate the hash. SHA256Managed sha256 = new SHA256Managed(); byte[] outputBytes = sha256.ComputeHash(inputBytes); // Convert the outputed hash to a string and return it. return Convert.ToBase64String(outputBytes); }
The American National Security Agency recommended algorithms:
http://www.nsa.gov/ia/programs/suiteb_cryptography/index.shtml
Because a hashing algorithm is deterministic, it is posible to produce pre-calculated lists of common hashes (for example, to produce a database of hashes for every word in a dictionary). The initial time taken to produce these lists is significant. However, once produced, lookup is simple.
Salting involves the addition of entropy to the password hash by storing the combined has of a salt (a random value) and the password. The salt does not need to be kept secret, and so it can be stored with the password hash. The combination of a salt plus a password means that a dictionary of hash lookups would have to be produced for every possible salt value, which would take both a significant amount of time and space. For each value you hash, you should use a new salt value. Using a new salt means that a new dictionary would have to be produced for every single password stored in your system.
The .NET framework includes a random number class, Random, which can be used to generate random numbers and bytes. However, the Random class is not suitable for cryptography. It does not generate cryptographically secure random numbers.
The .NET Framework provides a cryptographically secure pseudo-random number generator (CSPRNG) class, System.Cryptography.RNGCryptoServiceProvider, which you should use whenever you want to generate generate random data for cryptographic purposes. For salting purposes:
byte[] saltBytes; int minSaltSize = 4; int maxSaltSize = 8; // Generate a random number to determine the salt size. Random random = new Random(); int saltSize = random.Next(minSaltSize, maxSaltSize); // Allocate a byte array, to hold the salt. saltBytes = new byte[saltSize]; // Initialize the cryptographically secure random number generator. RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); // Fill the salt with cryptographically strong byte values. rng.GetNonZeroBytes(saltBytes);
Once you have the salt value, you must combine it with the clear text to produce a salted hash, using the salt value as a prefix to the clear text, or appending it to the clear text before calculating the hash.
Calculate a SHA256 hash:
// Convert the clear text into bytes. byte[] clearTextBytes = Encoding.UTF8.GetBytes(clearText); // Create a new array to hold clear text and salt. byte[] clearTextWithSaltBytes = new byte[clearTextBytes.Length + saltBytes.Length]; // Copy clear text bytes into the new array. for (int i=0; i < clearTextBytes.Length; i++) clearTextWithSaltBytes[i] = clearTextBytes[i]; // Append salt bytes to the new array. for (int i=0; i < saltBytes.Length; i++) clearTextWithSaltBytes[clearTextBytes.Length + i] = saltBytes[i]; // Calculate the hash HashAlgorithm hash = new SHA256Managed(); byte[] hashBytes = hash.ComputeHash(clearTextWithSaltBytes);
Validate password:
private bool IsPasswordValid(string password, byte[] savedSalt, byte[] savedHash) { Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, savedSalt, NumberOfIterations); // Convert the provided password into bytes. byte[] clearTextBytes = Encoding.UTF8.GetBytes(clearText); // Create a new array to hold clear text and salt. byte[] clearTextWithSaltBytes = new byte[clearTextBytes.Length + saltBytes.Length]; // Copy clear text bytes into the new array. for (int i=0; i < clearTextBytes.Length; i++) clearTextWithSaltBytes[i] = clearTextBytes[i]; // Append salt bytes to the new array. for (int i=0; i < saltBytes.Length; i++) clearTextWithSaltBytes[clearTextBytes.Length + i] = saltBytes[i]; // Calculate the hash HashAlgorithm hash = new SHA256Managed(); byte[] currentHash = hash.ComputeHash(clearTextWithSaltBytes); // Now check if the hash values match. bool matched = false; if (currentHash.Length == savedHash.Length) { int i = 0; while ((i < currentHash.Length) & & (currentHash[i] == savedHash[i])) { i += 1; } if (i == currentHash.Length) { matched = true; } } return (matched); }
Symmetic algorithms are suitable for scenarios where an application needs to both encrypt and decrypt the data.
Follow these steps to encrypt data symmetrically:
Follow these steps to decrypt the data:
Remember that it is important to keep the key secret. Storage of encryption keys should be separated from the storage of the encrypted data, and locked down to only allow authorized use. An example would be separate databases on a SQL Server.
The Organization for the Advancement of Structured Information Standards (OASIS) has an entire Technical Committee dedicated to Key Management: http://www.oasis-open.org/committees/tc_home.php?wg_abbrev=ekmi.
The .NET framework provides the most common symmetric encryption algorithms:
Generally, you should use RijndaelManaged or the AesManaged classes because they are the most commonly used symmetric algorithms in use today.
Generally, a key size of 128 bits (the standard size for SSL) is considered sufficient for most applications. A key size of 168 bits or 256 bits should be considered for highly secure systems (such as large financial transactions). The length of the initialization vector for an algorithm is equal to its block size, which you can access by using the BlockSize property on an instance of the algorithm.
Generate secure keys and IVs:
static byte[] GenerateRandomBytes(int length) { byte[] key = new byte[length]; RNGCryptoServiceProvider provider = new RNGCryptoServiceProvider(); provider.GetBytes(key); return key; }
The Web site, http://www.keylength.com, contains the current, historical, and futrue requirements for recommended and mandated key sizes and algorithms by such organizaions.
You can also use the Rfc2898DriveBytes class to generate a key and initialization vector pair from a known value like a password combined with a random salt. Using a password to create a key is an easy way to provide secure data that only a user can unlock, or, if the password is not sourced from a user, it makes for easy storage in a configuration file as opposed to a binary key.
private void GetKeyAndIVFromPasswordAndSalt( string password, byte[] salt, SymmetricAlgorithm symmetricAlgorithm, ref byte[] key, ref byte[] iv) { Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, salt); key = rfc2898DeriveBytes.GetBytes(symmetricAlgorithm.KeySize / 8); iv = rfc2898DeriveBytes.GetBytes(symmetricAlgorithm.BlockSize / 8); }
Encrypt using the Rijndael algorithm:
static byte[] Encrypt(byte[] clearText, byte[] key, byte[] iv) { // Create an instance of our encyrption algorithm. RijndaelManaged rijndael = new RijndaelManaged(); // Create an encryptor using our key and IV ICryptoTransform transform = rijndael.CreateEncryptor(key, iv); // Create the streams for input and output MemoryStream outputStream = new MemoryStream(); CryptoStream inputStream = new CryptoStream( outputStream, transform, CryptoStreamMode.Write); // Feed our data into the crypto stream. inputStream.Write(clearText, 0, clearText.Length); // Flush the crypto stream. inputStream.FlushFinalBlock(); // And finally return our encrypted data. return outputStream.ToArray(); }
Decrypt using the Rijndael algorithm:
static byte[] Decrypt(byte[] cipherText, byte[] key, byte[] iv) { // Create an instance of our encyrption algorithm. RijndaelManaged rijndael = new RijndaelManaged(); // Create an decryptor using our key and IV ; ICryptoTransform transform = rijndael.CreateDecryptor(key, iv); // Create the streams for input and output MemoryStream outputStream = new MemoryStream(); CryptoStream inputStream = new CryptoStream( outputStream, transform, CryptoStreamMode.Write); // Feed our data into the crypto stream. inputStream.Write(cipherText, 0, cipher.Length); // Flush the crypto stream. inputStream.FlushFinalBlock(); // And finally return our decrypted data. return outputStream.ToArray(); }
Some cryptanalytic attacks are made easier with more data encrypted with a spcific key. The mitigation against this is to use a session key. A session key is used to encrypt a single set of data—for example, a single record in the database, or all messages within a single communication session.
If it’s not feasible to store session keys separately from your data, then you can still use session keys by using master key. The master key is kept in a secure key store, completely separate from the data to be encrypted, or derived from a password entered by the application user. A session key is then used to encrypt the data to be protected. The session key is then encrypted with the master key and stored alongside the data that it applies to, while the master key remains in a separate secure key store.
The standard approach for generating an encrypted hash is to create a Message Authentication Code (MAC).
Storing a MAC for encrypted data provides two benefits:
.NET provides a number of common algorithms for generating a keyed hash, all of which use KeyedHashAlgorithm as their base class. The generally recommended algorithm is HMACSHA256, which a key size of 64 bytes.
Generates a MAC:
static byte[] GenerateMac(byte[] clearText, byte[] key) { HMACSHA256 hmac = new HMACSHA256(key); return hmac.ComputeHash(clearText); }
Checking a MAC:
static bool IsMacValid(byte[] clearText, byte[] key, byte[] savedMac) { byte[] recalculatedMac = GenerateMac(clearText, key); bool matched = false; if (recalculatedMac.Length == savedMac.Length) { int i = 0; while ((i < recalculatedMac.Length) & & (recalculatedMac[i] == savedMac[i])) { i += 1; } if (i == recalculatedMac.Length) { matched = true; } return (matched); }
We want to keep LicenseNumber secure.
This record format needs to change to support encryption and integrity protection.
You need two master keys, kept safe away from your main database. These the master keys used to encrypt the session key for each record, and the validation key used to generate the MAC.
To add a new record:
To retrieve and validate the record:
Encyrpting Data Using The RSA class:
// Create an UTF8 encoding class to parse strings from and to byte arrays UTF8Encoding encoding = new UTF8Encoding(); // Setup the sample text to encrypt and convert it to a byte array. string clearText = "example"; byte[] clearTextAsBytes = encoding.GetBytes(clearText); // Create a new instance of the RSACryptoServiceProvider RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(1024); // Export the keys as XML string publicKeyAsXml = rsa.ToXmlString(false); string privateKeyAsXml = rsa.ToXmlString(true); // Create a new instance of the RSACryptoServiceProvider // and load the public key parameters so it can be used // to encrypt. RSACryptoServiceProvider publicKeyRSA = new RSACryptoServiceProvider(1024); publicKeyRSA.FromXmlString(publicKeyAsXml); byte[] encryptedData = publicKeyRSA.Encrypt(clearTextAsBytes, true); // Create a new instance of the RSACryptoServiceProvider // and load the private key parameters so it can be used // to decrypt. RSACryptoServiceProvider privateKeyRSA = new RSACryptoServiceProvider(); privateKeyRSA.FromXmlString(privateKeyAsXml); byte[] unencryptedBytes = privateKeyRSA.Decrypt(encryptedData, true); // And finally convert it back to a string to prove it works! string unecnryptedString = Encoding.UTF8.GetString(unencryptedBytes, 0, unencryptedBytes.Length);
Using asymmetric entryption without certificates has one drawback—you cannot tell from whom the encrypted data has come. This is where certificates, coupled with digital signatures, come into play.
Load a certificate:
X509Store myStore = new X509Store( StoreName.My, StoreLocation.CurrentUser ); myStore.Open(OpenFlags.ReadOnly); // Find my certificate X509Certificate2Collection certificateCollection = myStore.Certificates.Find( X509FindType.FindBySubjectKeyIdentifier, "8a7ec2d153cbb0827ddaabedc729c618f15239c4", true); // Retrieve the first certificate in the returned collection // There will only be one, as the subject key identifier // should be unique. X509Certificate2 myCertificate = certificateCollection[0]; // Use this certificate to perform operations myStore.Close();
static byte[] EncyrptWithCertificate(byte[] clearText, X509Certificate2 certificate) { // Load our clear text into the CMS/PKCS #7 data structure ContentInfo contentInfo = new ContentInfo(clearText); // Create an encrypted envelope for the encrypted data EnvelopedCms envelopedCms = new EnvelopedCms(contentInfo); // Set the certificate that we will encrypt for. // Remember we only need a cerificate with the public key CmsRecipient recipient = new CmsRecipient(certificate); // Encrypt it envelopedCms.Encrypt(recipient); // And return the encoded, encrypted data return envelopedCms.Encode(); }
static byte[] DecryptWithCertificate(byte[] cipherText) { EnvelopedCms envelopedCms = new EnvelopedCms(); envelopedCms.Decode(cipherText); envelopedCms.Decrypt(); return envelopedCms.ContentInfo.Content; }
static byte[] SignWithCertificate(byte[] clearText, X509Certificate2 certificate) { // Load our clear text into the CMS/PKCS #7 data structure ContentInfo contentInfo = new ContentInfo(clearText); // Set who is signing the data CmsSigner signer = new CmsSigner(certificate); // Create a suitable signed message structure SignedCms signedCms = new SignedCms(contentInfo); // Sign the data signedCms.ComputeSignature(signer); // Return the signed data structure return signedCms.Encode(); }
To check signature:
static bool IsSignatureValid(SignedCms signedMessage) { bool result = false; try { // Set the parameter to true if you want to check // certificate revocations. signedMessage.CheckSignature(false); // Perform other checks on the signing certificates // as required foreach (SignerInfo signerInfo in signedMessage.SignerInfos) { X509Certificate2 signingCertificate = signerInfo.Certificate; // Validate we know the signing cerificate } result = true; } catch (CryptographicException) { } return result; }
Because a certificate’s private key is extremely secret, certificates loaded into the machine store do not, by default, allow processes to access them, you must set the permission on the certificate to allow this if you want to programmatically encrypt using them.
The Visual Studio SDK comes with a utility, MAKECERT, that enables you to create certificates for use during development and testing.
Folloing code waps a piece of clear text data into a CMS envelope, signs it using a local certificate with a subject name of Barry_Dorrans, and then encrypts it against a public key certificate loaded from a file, myserver.cer. The data is then decrypted, the CMS envelope is re-created, and the signatures check. A list of the signing certificate subject name is created, and finally, the clear text is returned for further processing.
Signing, Encrypting, Unencrypting, and Verifying Signatures:
// Create an UTF8 encoding class to parse strings // from and to byte arrays UTF8Encoding encoding = new UTF8Encoding(); // Setup the sample text to encrypt and // convert it to a byte array. string clearText = "example"; byte[] clearTextAsBytes = encoding.GetBytes(clearText); // Get the certificate we are going to encrypt for. // As well as using the cerificate store you can also load // public key certificates from files, as demonstrated // below. X509Certificate2 serverPublicKeyCertificate = LoadCertificateFromFile("myserver.cer"); // Load the certificate we will be signing the data with // to provide data integrity and non-repudiation. X509Certificate2 signingCertificate = GetCertificateBySubjectName("Barry_Dorrans"); // Create our signed data envelope byte[] signedClearText = SignData(clearTextAsBytes, signingCertificate); // Now encrypt it byte[] encryptedAndSignedData = EncryptWithCertificate( signedClearText, serverPublicKeyCertificate); // Then you would send the data to the receiving system. // Now you're on the receiving system. // As the envelope contains a reference to the certificate // against which the data was encrypted it will get loaded // automatically if it is available. byte[] encodedUnencryptedCms = DecryptWithCertificate(encryptedAndSignedData); // Now you need to validate the signature // Create a list suitable for holding the signing subjects List < string > signingSubjects = new List < string > (); byte[] receivedClearText = ValidateSignatureAndExtractContent( encodedUnencryptedCms, signingSubjects); // And finally convert it back to a string to prove it works! string unecnryptedString = Encoding.UTF8.GetString(receivedClearText, 0, receivedClearText.Length); static byte[] SignData(byte[] clearText, X509Certificate2 signingCertificate) { // Load our clear text into the CMS/PKCS #7 data structure ContentInfo contentInfo = new ContentInfo(clearText); // Set who is signing the data CmsSigner signer = new CmsSigner(signingCertificate); // Create a suitable signed message structure SignedCms signedCms = new SignedCms(contentInfo); // Sign the data signedCms.ComputeSignature(signer); // Return the signed data structure return signedCms.Encode(); } static byte[] ValidateSignatureAndExtractContent( byte[] signedCmsAsBytes, ICollection < string > signingSubjects) { SignedCms signedCms = new SignedCms(); signedCms.Decode(signedCmsAsBytes); signedCms.CheckSignature(true); signingSubjects.Clear(); foreach(SignerInfo signerInfo in signedCms.SignerInfos) { // Reconstruct the signing certificate public parts X509Certificate2 signingCertificate = signerInfo.Certificate; // And extract the subject signingSubjects.Add(signingCertificate.Subject); } return signedCms.ContentInfo.Content; } static byte[] EncyrptWithCertificate(byte[] clearText, X509Certificate2 certificate) { // Load our clear text into the CMS/PKCS #7 data structure ContentInfo contentInfo = new ContentInfo(clearText); // Create an encrypted envelope for the encrypted data EnvelopedCms envelopedCms = new EnvelopedCms(contentInfo); // Set the certificate that we will encrypt for. // Remember we only need a cerificate with the public key CmsRecipient recipient = new CmsRecipient(certificate); // Encrypt it envelopedCms.Encrypt(recipient); // And return the encoded, encrypted data return envelopedCms.Encode(); } static byte[] DecryptWithCertificate(byte[] cipherText) { EnvelopedCms envelopedCms = new EnvelopedCms(); // Reconstruct the envelope and decrypt. envelopedCms.Decode(cipherText); envelopedCms.Decrypt(); return envelopedCms.ContentInfo.Content; } static X509Certificate2 LoadCertificateFromFile(string fileName) { X509Certificate2 certificate = new X509Certificate2(); byte[] cerFileContents = ReadBinaryFile(fileName); certificate.Import(cerFileContents); return certificate; } static X509Certificate2 GetCertificateBySubjectName( string subjectName) { X509Store store = null; try { store = new X509Store(StoreName.My, StoreLocation.LocalMachine); store.Open(OpenFlags.ReadOnly); X509Certificate2Collection certificates = store.Certificates.Find(X509FindType.FindBySubjectName, subjectName, true); return certificates[0]; } // finally { if (store != null) store.Close(); } } static byte[] ReadBinaryFile(string fileName) { FileStream f = new FileStream(fileName, FileMode.Open, FileAccess.Read); int size = (int)f.Length; byte[] data = new byte[size]; size = f.Read(data, 0, size); f.Close(); return data; }
The Windows Data Protection API (DPAPI) is a core system service proviced by Windows that is managed by the most secure process in the operating system: the Local Security Authority (LSA).
Encrypt and decrypt using DPAPI:
// This is an example of entropy. In a real application // it should be a cryptographically secure array of random data. private static byte[] entropy = {1, 3, 5, 7, 9, 11, 15, 17, 19}; static byte[] EncryptUsingDPAPI(byte[] clearText) { return ProtectedData.Protect( clearText, entropy, DataProtectionScope.LocalMachine); } static byte[] DecryptUsingDPAPI(byte[] encryptedText) { return ProtectedData.Unprotect( encryptedText, entropy, DataProtectionScope.LocalMachine); }