ASP.Net MVC和WebAPI加密

8
我希望使用一种“简单”的加密方式,既可以保证相对安全性,又可以对开发流程影响较小。假设在客户端<>Web服务的通信中,我拥有双方。我的应用程序是Windows手机/Win8/Silverlight/桌面应用程序,服务器是ASP.Net MVC或WebAPI。在我看来,我想要的东西就像这样简单:-
<security encryption="off|sometype|someothertype">
    <privatekey>12345MyKey54321</privatekey>
</security>

在客户端和服务器上,可以将某种形式的配置参数用作加密模式。此外,认证例程将返回并存储某种形式的公钥。

这样做将启用“加密模式”,使用提供的密钥以所选方式加密和散列任何http请求。最终结果是,本地、代理或远程机器上嗅探到的任何内容都无法在没有密钥和解密方法的情况下查看数据。在服务器上,在命中控制器操作之前,使用相同的密钥解密数据。

除了将HttpRequest / WebClient调用更换为类似EncryptedHttpRequest的内容,并在MVC/WebAPI方面添加适当的挂钩之外,所有其他客户端代码和控制器操作都不会意识到数据已经加密。

我有遗漏的东西吗?难道安装过程不可能如此简单?据我所搜寻,似乎没有提供如此简单的加密方法,因此我认为我的逻辑存在严重漏洞?


1
问题在于使用像 Fiddler 这样的工具进行中间人攻击是多么容易。 - DannyT
2个回答

19

通过使用HTTPS,您可以实现所有想要的功能。只需购买证书(或使用自签名证书),即可获得加密。

不要重新发明轮子。


4
使用 Fiddler 工具可以轻松解密 HTTPS 通信。详见 http://docs.telerik.com/fiddler/Configure-Fiddler/Tasks/DecryptHTTPS。 - Volkmar Rigo

8
我已经成功地完成了这个任务。它并不太难,而且效果很好。我用它来激活产品的许可证。最重要的是,您真正控制客户端和服务器,没有人能从客户端代码中提取您的私钥。
步骤1:创建一个MVC控制器动作方法,该方法不带参数:
[HttpPost]        public ActionResult Activate()        { ... }

步骤2:在控制器中,只需使用HttpRequest.InputStream获取来自客户端发送的字节即可。
var stream = this.HttpContext.Request.InputStream;
步骤3:创建CryptoStream进行反序列化。
我在这里提供了加密和解密示例。sharedSecret是一个足够长(512个字节)的随机字节的byte[] - 这就是您要保护的内容!
public CryptoStream CreateEncryptionStream(Stream writeStream)
{            
    TripleDESCryptoServiceProvider cryptoProvider = new TripleDESCryptoServiceProvider();               
    PasswordDeriveBytes derivedBytes = new PasswordDeriveBytes(this._sharedSecret, null);                
    CryptoStream cryptoStream = new CryptoStream(writeStream, cryptoProvider.CreateEncryptor(derivedBytes.GetBytes(16), derivedBytes.GetBytes(16)), CryptoStreamMode.Write);            
    return cryptoStream;        
}        

public CryptoStream CreateDecryptionStream(Stream readStream)        
{            
    TripleDESCryptoServiceProvider cryptoProvider = new TripleDESCryptoServiceProvider();            
    PasswordDeriveBytes derivedBytes = new PasswordDeriveBytes(this._sharedSecret, null);                
    CryptoStream cryptoStream = new CryptoStream(readStream, cryptoProvider.CreateDecryptor(derivedBytes.GetBytes(16), derivedBytes.GetBytes(16)), CryptoStreamMode.Read);            
    return cryptoStream;        
}

步骤四:使用CryptoStream和另一个流读取器进行解密。
我使用XmlReader,以便我的现有序列化代码可以在明文(读写磁盘或服务器上的数据库时)或加密(传输时)的情况下工作。
using (var reader = XmlReader.Create(decryptionStream, settings))                { ... }

步骤5:在您的控制器中制定一个安全响应。
这是对步骤1-4的反向操作,用于加密您的响应对象。然后,您只需将加密后的响应写入内存流,并将其作为文件结果返回。下面,我展示了如何为我的许可证响应对象执行此操作。
var responseBytes = GetLicenseResponseBytes(licenseResponse);
return File(responseBytes, "application/octet-stream");

private byte[] GetLicenseResponseBytes(LicenseResponse licenseResponse)        
{            
    if (licenseResponse != null)            
    {                
        using (MemoryStream memoryStream = new MemoryStream())                
        {                    
            this._licenseResponseSerializer.Write(memoryStream, licenseResponse);

            return memoryStream.ToArray();                
        }            
    }           
    return null;        
}

第六步:实现客户端请求响应。

您可以使用 HttpWebRequest 或 WebClient 类来构建请求。以下是我使用的代码中的一些示例。

byte[] postBytes = GetLicenseRequestBytes(licenseRequest);
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(licenseServerUrl);
request.Method = "POST";
request.ContentType = "application/octet-stream";
request.Proxy = WebRequest.DefaultWebProxy;
using (Stream requestStream = request.GetRequestStream())
{                
    requestStream.Write(postBytes, 0, postBytes.Length);
}            
return request;

private LicenseResponse ProcessHttpResponse(HttpWebResponse response)
{
    if ((response.StatusCode == HttpStatusCode.OK) && response.ContentType.Contains("application/octet-stream"))
    {
        var stream = response.GetResponseStream();
        if (stream != null)
        {
            var licenseResponse = this._licenseResponseSerializer.Read(stream);
            return licenseResponse;
        }
    }
    return new LicenseResponse(LicensingResult.Error);
}

总结和建议

  • 在客户端和服务器中使用请求/响应流来通信二进制八位组数据
  • 使用CryptoStream以及一个加密算法(考虑使用最强的加密方式)和一个好的私钥,在序列化/反序列化数据时进行加密。
  • 确保检查所有传入数据的大小和格式(避免缓冲区溢出并尽早抛出异常)。
  • 如果可能,使用混淆来保护客户端上的私钥(可以查看DeepSea混淆器)。

2
很棒的解决方案!但是Aliostad的答案更实用和直接 :) - Dmitrii Lobanov
这个实现非常适合嵌入式Linux设备与数据库进行通信。 - NTMS

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接