邪恶八进制信息安全团队技术讨论组's Archiver

EvilOctal 2006-4-15 19:07

[转载]HttpSecureCookie (一种加密ASP.NET 2.0的Cookies的方法)

原始连接:<A href="http://www.codeproject.com/aspnet/HttpSecureCookie.asp">[url]http://www.codeproject.com/aspnet/HttpSecureCookie.asp[/url]</A><BR><BR>
<H2>Introduction</H2>
<P>I really have some good laughs when I tamper with cookies on my machine and watch the results when it is submitted back to the site. On the other hand, I don’t want any one to do the same to the cookies that I make!</P>
<P>Cookies, most of the times, shouldn’t be in plain text, at least, they should be tamper-proof! Revealing the content of your cookies might give curious and malicious people an idea about your application’s architecture, and that might help hacking it.</P>
<P>ASP.NET encodes and hashes its authorization ticket, making it secure and tamper-proof. However, the methods used to secure authorization cookies are inaccessible from outside the .NET framework libraries, so you can’t protect your own cookie using these methods; you need to protect it yourself using your own encryption key, encoding and hashing algorithms. <CODE><FONT face=新宋体>HttpSecureCookie</FONT></CODE> works around this by accessing the same methods ASP.NET uses for cookie authorization.</P>
<P>Of course, you shouldn’t save valuable information in your cookies, but if you have to, then this library is at your disposal.</P>
<H2>Background</H2>
<P>Before you start using this code, if you do not know what <CODE><FONT face=新宋体>MachineKey</FONT></CODE> is, I highly recommend checking this MSDN article: <A href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpag2/html/paght000007.asp" target=_blank>How To: Configure MachineKey in ASP.NET 2.0</A>.</P>
<P>ASP.NET uses the <CODE><FONT face=新宋体>System.Web.Security.CookieProtectionHelper</FONT></CODE> internal class to decode and encode the content of a cookie before submitting it to the client. This class is based on the <CODE><FONT face=新宋体>MachineKey</FONT></CODE>. I wonder why Microsoft kept this class internal!?</P>
<P>To be able to access this internal class, I had to use reflection to be able to access the <CODE><FONT face=新宋体>Decode</FONT></CODE> and <CODE><FONT face=新宋体>Encode</FONT></CODE> methods of <CODE><FONT face=新宋体>CookieProtectionHelper</FONT></CODE>.</P>
<P>Eric Newton has a similar and good article on CP: <A href="http://www.codeproject.com/aspnet/HttpCookieEncryption.asp" target=_blank>Encrypting cookies to prevent tampering</A>. However, that code is made for .NET 1.1 and it doesn't work with .NET 2.0 (but it does with some modifications); moreover, its resulting cipher text is in binary format versus being in encrypted format, and I don't know if this is a security risk. Also, I am accessing a higher level class <CODE><FONT face=新宋体>System.Web.Security.CookieProtectionHelper</FONT></CODE> than the one used by that article, <CODE><FONT face=新宋体>System.Web.Configuration.MachineKey</FONT></CODE>, to obtain the cryptography service, and that saved me time by not writing some low level code.</P>
<P>There is also another available method for encoding cookies, by using the <CODE><FONT face=新宋体>FormsAuthenticationTicket</FONT></CODE> and <CODE><FONT face=新宋体>FormsAuthentication.Encrypt</FONT></CODE>; for more information, check the section "Creating the Forms Authentication Cookie" on <A href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpag2/html/PAGExplained0002.asp" target=_blank>Explained: Forms Authentication in ASP.NET 2.0</A>. However, I believe, the method mentioned in this article is more flexible.</P>
<H2>Obtaining Reference to the CookieProtectionHelper Class via Reflection</H2>
<P>To be able to access <CODE><FONT face=新宋体>System.Web.Security.CookieProtectionHelper</FONT></CODE>, I had to create a wrapper class <CODE><FONT face=新宋体>CookieProtectionHelperWrapper</FONT></CODE> which uses reflection to obtain a reference to the underlying methods and exposes the same methods as of the original class:</P><PRE lang=cs><SPAN class=cs-keyword>public</SPAN> <SPAN class=cs-keyword>static</SPAN> <SPAN class=cs-keyword>class</SPAN> CookieProtectionHelperWrapper {

    <SPAN class=cs-keyword>private</SPAN> <SPAN class=cs-keyword>static</SPAN> MethodInfo _encode;
    <SPAN class=cs-keyword>private</SPAN> <SPAN class=cs-keyword>static</SPAN> MethodInfo _decode;

    <SPAN class=cs-keyword>static</SPAN> CookieProtectionHelperWrapper() {
        <SPAN class=cs-comment>// obtaining a reference to System.Web assembly</SPAN>
        Assembly systemWeb = <SPAN class=cs-keyword>typeof</SPAN>(HttpContext).Assembly;
        <SPAN class=cs-keyword>if</SPAN> (systemWeb == <SPAN class=cs-keyword>null</SPAN>) {
            <SPAN class=cs-keyword>throw</SPAN> <SPAN class=cs-keyword>new</SPAN> InvalidOperationException(
                <SPAN class=cpp-string>"Unable to load System.Web."</SPAN>);
        }
        <SPAN class=cs-comment>// obtaining a reference to the internal class CookieProtectionHelper</SPAN>
        Type cookieProtectionHelper = systemWeb.GetType(
                <SPAN class=cpp-string>"System.Web.Security.CookieProtectionHelper"</SPAN>);
        <SPAN class=cs-keyword>if</SPAN> (cookieProtectionHelper == <SPAN class=cs-keyword>null</SPAN>) {
            <SPAN class=cs-keyword>throw</SPAN> <SPAN class=cs-keyword>new</SPAN> InvalidOperationException(
                <SPAN class=cpp-string>"Unable to get the internal class CookieProtectionHelper."</SPAN>);
        }
        <SPAN class=cs-comment>// obtaining references to the methods of CookieProtectionHelper class</SPAN>
        _encode = cookieProtectionHelper.GetMethod(
                <SPAN class=cpp-string>"Encode"</SPAN>, BindingFlags.NonPublic | BindingFlags.Static);
        _decode = cookieProtectionHelper.GetMethod(
                <SPAN class=cpp-string>"Decode"</SPAN>, BindingFlags.NonPublic | BindingFlags.Static);

        <SPAN class=cs-keyword>if</SPAN> (_encode == <SPAN class=cs-keyword>null</SPAN> || _decode == <SPAN class=cs-keyword>null</SPAN>) {
            <SPAN class=cs-keyword>throw</SPAN> <SPAN class=cs-keyword>new</SPAN> InvalidOperationException(
                <SPAN class=cpp-string>"Unable to get the methods to invoke."</SPAN>);
        }
    }

    <SPAN class=cs-keyword>public</SPAN> <SPAN class=cs-keyword>static</SPAN> <SPAN class=cs-keyword>string</SPAN> Encode(CookieProtection cookieProtection,
                                <SPAN class=cs-keyword>byte</SPAN>[] buf, <SPAN class=cs-keyword>int</SPAN> count) {
        <SPAN class=cs-keyword>return</SPAN> (<SPAN class=cs-keyword>string</SPAN>)_encode.Invoke(<SPAN class=cs-keyword>null</SPAN>,
                <SPAN class=cs-keyword>new</SPAN> <SPAN class=cs-keyword>object</SPAN>[] { cookieProtection, buf, count });
    }

    <SPAN class=cs-keyword>public</SPAN> <SPAN class=cs-keyword>static</SPAN> <SPAN class=cs-keyword>byte</SPAN>[] Decode(CookieProtection cookieProtection,
                                <SPAN class=cs-keyword>string</SPAN> data) {
        <SPAN class=cs-keyword>return</SPAN> (<SPAN class=cs-keyword>byte</SPAN>[])_decode.Invoke(<SPAN class=cs-keyword>null</SPAN>,
                <SPAN class=cs-keyword>new</SPAN> <SPAN class=cs-keyword>object</SPAN>[] { cookieProtection, data });
    }

}</PRE>
<H2>MachineKeyCryptography: A Cryptography Class Based on MachineKey</H2>
<P><CODE><FONT face=新宋体>MachineKeyCryptography</FONT></CODE> is a static class that provides text ciphering and tamper-proofing services, it provides higher level access to <CODE><FONT face=新宋体>CookieProtectionHelperWrapper</FONT></CODE>. So, if you want to cipher any text based on the machine key, this class is the right one to use.</P><PRE lang=cs><SPAN class=cs-keyword>public</SPAN> <SPAN class=cs-keyword>static</SPAN> <SPAN class=cs-keyword>string</SPAN> Encode(<SPAN class=cs-keyword>string</SPAN> text, CookieProtection cookieProtection) {
    <SPAN class=cs-keyword>if</SPAN> (<SPAN class=cs-keyword>string</SPAN>.IsNullOrEmpty(text) || cookieProtection == CookieProtection.None) {
        <SPAN class=cs-keyword>return</SPAN> text;
    }
    <SPAN class=cs-keyword>byte</SPAN>[] buf = Encoding.UTF8.GetBytes(text);
    <SPAN class=cs-keyword>return</SPAN> CookieProtectionHelperWrapper.Encode(cookieProtection, buf, buf.Length);
}

<SPAN class=cs-keyword>public</SPAN> <SPAN class=cs-keyword>static</SPAN> <SPAN class=cs-keyword>string</SPAN> Decode(<SPAN class=cs-keyword>string</SPAN> text, CookieProtection cookieProtection) {
    <SPAN class=cs-keyword>if</SPAN> (<SPAN class=cs-keyword>string</SPAN>.IsNullOrEmpty(text)) {
        <SPAN class=cs-keyword>return</SPAN> text;
    }
    <SPAN class=cs-keyword>byte</SPAN>[] buf;
    <SPAN class=cs-keyword>try</SPAN> {
        buf = CookieProtectionHelperWrapper.Decode(cookieProtection, text);
    }
    <SPAN class=cs-keyword>catch</SPAN>(Exception ex) {
        <SPAN class=cs-keyword>throw</SPAN> <SPAN class=cs-keyword>new</SPAN> InvalidCypherTextException(
            <SPAN class=cpp-string>"Unable to decode the text"</SPAN>, ex.InnerException);
    }
    <SPAN class=cs-keyword>if</SPAN> (buf == <SPAN class=cs-keyword>null</SPAN> || buf.Length == <SPAN class=cs-literal>0</SPAN>) {
        <SPAN class=cs-keyword>throw</SPAN> <SPAN class=cs-keyword>new</SPAN> InvalidCypherTextException(
            <SPAN class=cpp-string>"Unable to decode the text"</SPAN>);
    }
    <SPAN class=cs-keyword>return</SPAN> Encoding.UTF8.GetString(buf, <SPAN class=cs-literal>0</SPAN>, buf.Length);
}</PRE>
<H2>HttpSecureCookie Class</H2>
<P>This static class will handle the service of securing the content of a cookie. Also, it provides a service to clone a cookie. This class uses <CODE><FONT face=新宋体>MachineKeyCryptography</FONT></CODE> internally to provide crypting services:</P><PRE lang=cs><SPAN class=cs-keyword>public</SPAN> <SPAN class=cs-keyword>static</SPAN> <SPAN class=cs-keyword>class</SPAN> HttpSecureCookie {

    <SPAN class=cs-keyword>public</SPAN> <SPAN class=cs-keyword>static</SPAN> HttpCookie Encode(HttpCookie cookie) {
        <SPAN class=cs-keyword>return</SPAN> Encode(cookie, CookieProtection.All);
    }

    <SPAN class=cs-keyword>public</SPAN> <SPAN class=cs-keyword>static</SPAN> HttpCookie Encode(HttpCookie cookie,
                  CookieProtection cookieProtection) {
        HttpCookie encodedCookie = CloneCookie(cookie);
        encodedCookie.Value =
          MachineKeyCryptography.Encode(cookie.Value, cookieProtection);
        <SPAN class=cs-keyword>return</SPAN> encodedCookie;
    }

    <SPAN class=cs-keyword>public</SPAN> <SPAN class=cs-keyword>static</SPAN> HttpCookie Decode(HttpCookie cookie) {
        <SPAN class=cs-keyword>return</SPAN> Decode(cookie, CookieProtection.All);
    }

    <SPAN class=cs-keyword>public</SPAN> <SPAN class=cs-keyword>static</SPAN> HttpCookie Decode(HttpCookie cookie,
                  CookieProtection cookieProtection) {
        HttpCookie decodedCookie = CloneCookie(cookie);
        decodedCookie.Value =
          MachineKeyCryptography.Decode(cookie.Value, cookieProtection);
        <SPAN class=cs-keyword>return</SPAN> decodedCookie;
    }

    <SPAN class=cs-keyword>public</SPAN> <SPAN class=cs-keyword>static</SPAN> HttpCookie CloneCookie(HttpCookie cookie) {
        HttpCookie clonedCookie = <SPAN class=cs-keyword>new</SPAN> HttpCookie(cookie.Name, cookie.Value);
        clonedCookie.Domain = cookie.Domain;
        clonedCookie.Expires = cookie.Expires;
        clonedCookie.HttpOnly = cookie.HttpOnly;
        clonedCookie.Path = cookie.Path;
        clonedCookie.Secure = cookie.Secure;

        <SPAN class=cs-keyword>return</SPAN> clonedCookie;
    }
}</PRE>
<H2>Using the Code</H2>
<P>Using <CODE><FONT face=新宋体>HttpSecureCookie</FONT></CODE> is easy; for a complete demo, please check the sample application. To encode a cookie:</P><PRE lang=cs>HttpCookie cookie = <SPAN class=cs-keyword>new</SPAN> HttpCookie(<SPAN class=cpp-string>"UserName"</SPAN>, <SPAN class=cpp-string>"Terminator"</SPAN>);
cookie.Expires = DateTime.Now.AddDays(<SPAN class=cs-literal>1</SPAN>);
HttpCookie encodedCookie = HttpSecureCookie.Encode(cookie);
Response.Cookies.Add(encodedCookie);</PRE>
<P>To decode an encoded cookie:</P><PRE lang=cs>HttpCookie cookie = Request.Cookies[<SPAN class=cpp-string>"UserName"</SPAN>];
lblDisplayBefore.Text = cookie.Value;
HttpCookie decodedCookie = HttpSecureCookie.Decode(cookie);</PRE>
<P>To use <CODE><FONT face=新宋体>HttpSecureCookie</FONT></CODE> on a web farm, you need to set the correct <CODE><FONT face=新宋体>MachineKey</FONT></CODE> configuration in <I>Web.Config</I>. For more information, check the "Web Farm Deployment Considerations" section on <A href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpag2/html/paght000007.asp" target=_blank>How To: Configure MachineKey in ASP.NET 2.0</A>. To generate a machine key, please check <A href="http://support.microsoft.com/default.aspx?scid=kb;en-us;Q312906" target=_blank>How to create keys by using Visual C# .NET for use in Forms authentication</A>.</P>
<H2>Limitations</H2>
<P>This library uses reflection, so it might break with the next version of .NET. Also, it doesn't work with .NET 1.1.</P>
<P>Reflection might have some performance implication; however, I used an assembly that is already loaded, "<I>System.Web.dll</I>", and I am only using reflection once across the life time of the application, to gain extra performance.</P>
<H2>Conclusion</H2>
<P>If you don't want a sophisticated cookie encryption service, if you don't want to mind the encryption key, and if you don't want to create your own encryption algorithm, then this library is for you!</P>
<P>Comments and suggestions are welcome. Please vote if you like this article (or if you didn't).</P>
<H2>History</H2>
<UL>
<LI>1 April 2006 - First version. </LI></UL><!-- Article Ends -->
<H2>About Adam Tibi</H2>
<TABLE width="100%" border=0>
<TBODY>
<TR vAlign=top>
<TD class=smallText noWrap><IMG src="http://www.codeproject.com/script/profile/images/{1CD9697B-AEF9-4ADC-8119-2A14A87E73B6}.gif"><BR></TD>
<TD class=smallText width="100%">New technology addict and C# fan. Started developing commercial applications in 2000 with VB6 and MFC then moved to VB.NET, landed on C# and ASP.NET and never looked back.<BR>Currently focusing on enterprise architecture, design patterns, web controls and web services.<BR>In his free time, he likes playing chess, going to gym and discovering new places with his lovely wife.<BR>Lives in Guildford, UK and is a senior developer at <A href="http://www.yourinsurance.co.uk/" target=_blank>Your Insurance</A>!
<P class=smallText>Click <A href="http://www.codeproject.com/script/profile/whos_who.asp?vt=arts&id=582839">here</A> to view Adam Tibi's online profile.</P></TD></TR></TBODY></TABLE>

页: [1]
© 1999-2008 EvilOctal Security Team