65.9K
CodeProject 正在变化。 阅读更多。
Home

客户端/服务器加密及其他

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.77/5 (23投票s)

2003 年 10 月 21 日

6分钟阅读

viewsIcon

300691

downloadIcon

3309

演示了如何在服务器和客户端环境中实现 RSA 加密算法,甚至可以通过 Web 浏览器实现!

引言

如今,我们应用程序的安全性是一个大问题。公司设有专门人员,他们的唯一工作就是审视您的代码,确保最厉害的黑客也无法攻破您的网站或应用程序。在最近的一个项目中,信息安全官(ISO)在实施前几天突然告诉我,使用 Microsoft Network Monitor(或更通俗地说 NetMon)可以看到密码在网络上传输。由于我的工作不仅限于此应用程序,我需要一种安全地将纯文本值从客户端传输到服务器的方法,无论是 EXE 应用程序还是 Web 应用程序。哦,而且没有钱购买 SSL 证书……那将是一条轻松的出路。

使用 .NET 框架的加密类,为可执行文件实现双向加密越来越容易。然而,当我在 Web 客户端上找不到简单使用这些加密类的方法时。您可能在想为什么不直接使用 .NET 加密类呢?嗯,相信我,我试过了。但是当我们需要在客户端 PC 上通过 Web 浏览器执行代码时,您就可以理解其中的挑战。我必须想办法保证客户端 PC 安装了 .NET 框架,即使安装了,我该如何执行代码呢?出于这个简单的原因,我不得不自己实现 RSA 算法的一个版本(在 Paul Johnston 的大力帮助下)。

简而言之,我提出的解决方案是:一个已编译的 .NET DLL(支持 COM 互操作),它可以使用 JavaScript 和一个衍生自已知 RSA 加密算法的公钥来加密 Web 客户端上的数据。此外,该 DLL 还可以被 .NET 可执行应用程序、COM 可执行应用程序甚至 COM Web 应用程序使用!

现在您知道了为什么我需要重写 RSA 算法,让我们来看看它……

代码

首先,我知道的任何与加密相关的算法都处理非常大的数字,甚至超过 64 位长整数。为了处理这些长数字,我使用了 BigInteger 类的一个版本(稍作修改以处理更大的基数),可以在 The Code Project 的 这里 找到。您可以在上面的链接中看到它的工作原理,但我想在此致敬,并且您现在知道了代码中的 BigInteger 是什么。

如果您查看 Paul Johnston 的网站,您会明白 RSA 公钥/私钥对有 3 个部分。N 和 E 被称为您的公钥,E 和 D 被称为您的私钥。您可以使用 N 和 E 进行加密,但只有在拥有 D 部分时才能解密。下面是 DLL 中创建此密钥对的一个重载。

public Encryption(int bitSize)

{if (!((bitSize == 8) | (bitSize == 16) | (bitSize == 32) | (bitSize == 64) | 
(bitSize == 128) | (bitSize == 256) | (bitSize == 512)))
{
throw new ArgumentException("Encryption supports only 8, 16, 32, " + 
    "64, 128, 256, 512 bit encryption");
};
 // I only want to allow certain bit lengths. Note that the higher the
 // bit length the longer the computation time

 BigInteger q;
 BigInteger p;
 BigInteger m;
 
 p = BigInteger.genPseudoPrime(bitSize,10,new System.Random()); 
 do
 {
 q = BigInteger.genPseudoPrime(bitSize,10, new System.Random());
 }
 while(p == q); 
 _key.N = (p*q);
 m = (p-1)*(q-1); 
 _key.E = new BigInteger("10001", 16);
 _key.D = _key.E.modInverse(m);
}

现在,我已经有了密钥对,几乎可以加密和解密任何东西。对于 Web 应用程序,您可以将所有 3 个部分存储在会话变量中,并使用 N 和 E 进行加密,而服务器可以访问 D 来进行解密!

那么我们如何加密呢?这有点棘手。首先,我将向您展示如何在 DLL 中进行数学上的操作。然后,我将向您展示如何在客户端上完成。

public BigInteger[] Encrypt(string message )
{
 if ((_key.E == 0) || (_key.N == 0)) {

   throw new ApplicationException("Invalid Key");};
 int i ;
 byte[] temp = new byte[1] ; 
 byte[] digits = System.Text.ASCIIEncoding.ASCII.GetBytes(message); 
 BigInteger[] bigdigits = new BigInteger[digits.Length] ; 
 for( i = 0 ; i < bigdigits.Length ; i++ )
 {
temp[0] = digits[i] ;
bigdigits[i] = new BigInteger( temp ) ;
 } 
 BigInteger[] encrypted = new BigInteger[bigdigits.Length] ; 
 for( i = 0 ; i < bigdigits.Length ; i++ )
encrypted[i] = bigdigits[i].modPow( _key.E,_key.N) ; 

 return( encrypted ) ;
}
那么如何在客户端上做到这一点?老式的 JavaScript。您可能会想:嘿,我可以在 Web 浏览器中查看 JavaScript 源代码。这有多安全?您说得对,您可以通过 Web 浏览器看到 JavaScript 代码,但我们在客户端上所做的只是加密。加密只需要公钥:E 和 N。缺失的部分 D 始终对用户隐藏。后台代码、ASP 或任何其他服务器代码都可以通过会话状态访问此 D 变量以进行所有解密。此时您可能会问:为什么使用会话状态?为什么不创建密钥对并一直使用它以提高效率?好吧,我最初也是这么想的,直到一位同事指出,他可以轻松地捕获表单提交给服务器的加密值,并在稍后将这些相同的值提交给服务器。这本来是没问题的,因为黑客只需要创建一个简单的脚本将加密数据 POST 到 Web 登录页面,它就会被正确解密,因为私钥从未更改。通过使用会话变量,每次启动新会话时,该客户端都有其自己唯一的公钥和私钥对。现在即使您捕获了加密值,它们也会毫无价值,因为新会话打开时私钥将不同。99% 的代码是 JavaScript 如何实现 BigInteger 类。这段代码来自 http://www.leemon.com/。我不会在这里发布它,因为您可以从源文件中获取。但是,要加密,我们会这样做:我们已经打开了到网站的会话,为我们创建了新密钥。我的服务器代码页调用 DLL 中的 GetJavaScriptClientCode,该函数可用于向客户端页面写入内容。这包括所有 BigInteger 代码和用于加密的小部分。
function Encrypt (str, n, e) {
 var n=str2bigInt(n,16,0);
 var x=str2bigInt(str,95,n.length);
 var y=str2bigInt(e,16,0);
 powMod(x,y,n,0);
 x = bigInt2str(x,16);
 return x;
 }

我们现在可以添加一个密码框供用户输入密码,以及一个隐藏字段来存储加密数据。

<INPUT type="submit" value="Login" OnClick="var s = txtPassword.value; 
password.value=Encrypt(s,'<%=Session("n")%>','<%=Session("e")%>'); 
var strStar = ''; for (i = 0; i < s.length; i++) {strStar = strStar + '*';}  
txtPassword.value = strStar">
<input id="password" type="hidden" name="password">
此代码调用 JavaScript 加密函数,将原始密码的值替换为一串星号,并将另一个隐藏变量赋值为实际加密的密码。我做 * 号处理是为了让加密对用户不可见。如果我将他们输入的密码替换为加密值,长度会明显不同。上面那个小程序只是将他们键入文本框中的密码替换为与他们输入的密码长度相同的星号字符。最终结果是表单被发布 2 个参数:password,其值将是实际加密的密码,以及 txtPassword,它仅仅是一串星号字符。服务器代码将关注 password 变量。

解密比加密稍微复杂一些,但遵循通用的 RSA 算法规则。这是在 DLL 中 Reside 的解密函数的代码。

public string Decrypt( BigInteger[] encrypted )
{
 if (_key.D == 0) {throw new ApplicationException("Invalid key");};
 int i ; 

 BigInteger[] decrypted = new BigInteger[encrypted.Length] ;
 
 for( i = 0 ; i < decrypted.Length ; i++ ) 
decrypted[i] = encrypted[i].modPow((_key.D),(_key.N)) ; 
 char[] charArray = new char[decrypted.Length] ; 
 for( i = 0 ; i < charArray.Length ; i++ )
charArray[i] = (char) ( (byte)decrypted[i].IntValue() ) ; 
 return( new string( charArray ) ) ;
}

结论

在许多应用程序中,安全可能是一项艰巨的任务。我希望这篇文章和我的代码能在您的应用程序及其任何需求中帮助到您。

更新

  • 2004 年 1 月 16 日 - 在源下载文件中添加了 Web 实现的示例源代码
  • 2004 年 1 月 16 日 - 添加了一个窗体控件功能,在没有域可用时加载计算机名称。
  • 2004 年 1 月 16 日 - 修复了命名空间不一致的问题(请参阅下面的线程)。
  • 2005 年 8 月 19 日 - 最新更新(版本 1.2.2.0)能够查询 ActiveDirectory 或 LDAP 中的用户属性并更改密码。

附加功能!

DLL 代码中还包含一个名为 Authentication 的类。此类中的方法与目录服务或 LDAP(LDAP 未经测试)通信,以在指定的域中验证用户,并获取用户的任何属性。它还允许您获取用户的 SID 以及其他酷炫功能。此外,还有一个 Windows 控件(参见图片),可用于任何 .NET exe,为您的应用程序实现通用登录屏幕。该控件显然与 Authentication 类集成。如果您想要有关其中任何一个的文章,请通过在此文章下发帖询问。

© . All rights reserved.