Duwamish
是
Microsoft
提供一个企业级的分布式系统架构,如果开发企业级的分布式系统,可以模仿这种架构,如果是开发一些简单的系统,则完全可以简化。
以前也学习过
Duwamish
范例,只是发现不同时间,不同经历,有不同的体会。正如卢彦所说的一样:通过研究
Duwamish
示例,高手能够领悟到
.Net
应用架构的设计思想,低手能够学习到
.Net
的编程技巧,实在是老少皆宜。
因此,这里再次学习并体验一次
Duwamish
范例。
1
,
Duwamish 7.0
结构分为四个逻辑层(
FROM MSDN
):
Web
层
-
Presentation
Web
层为客户端提供对应用程序的访问。这一层是作为
Duwamish.sln
解决方案文件中的
Web
项目实现的。
Web
层由
ASP.NET Web
窗体和代码隐藏文件组成。
Web
窗体只是用
HTML
提供用户操作,而代码隐藏文件实现各种控件的事件处理。
业务外观层
-
Business Facade
业务外观层为
Web
层提供处理帐户、类别浏览和购书的界面。这一层是作为
Duwamish.sln
解决方案文件中的
BusinessFacade
项目实现的。业务外观层用作隔离层,它将用户界面与各种业务功能的实现隔离开来。除了低级系统和支持功能之外,对数据库服务器的所有调用都是通过此程序集进行的。
业务规则层
-
Business Rules
业务规则层是作为
Duwamish.sln
解决方案文件中的
BusinessRules
项目实现的,它包含各种业务规则和逻辑的实现。业务规则完成如客户帐户和书籍订单的验证这样的任务。
数据访问层
-
Data Access
数据访问层为业务规则层提供数据服务。这一层是作为
Duwamish.sln
解决方案文件中的
DataAccess
项目实现的。
除了上述四个逻辑层外,
Duwamish 7.0
还包含封装在
Duwamish.sln
解决方案文件中的
Common
项目内的共享函数。“通用”
(Common)
层包含用于在各层间传递信息的数据集。
Common
项目还包含
Duwamish.sln
解决方案文件中的
SystemFramework
项目内的应用程序配置和跟踪类。
2,各个逻辑层之间的关系图(FROM MSDN)及其调用Sequeance
图示例:


下面是
Categories.aspx web
页面获取
Category
的
Description
的整个调用过程。
(
1
)实例化
ProductSystem
对象
(
2
)调用
ProductSystem
的
GetCategories()
方法
(
3
)检测参数的合法性
(
4
)创建
Categories::DataAccess
对象实例
(
5
)返回上述对象
(
6
)调用
Categories::DataAccess
对象的
GetCategories()
方法
(
7
)创建
CategoryData::Common
对象实例
(
8
)返回上述对象
(
9
)返回
CategoryData::Common
对象实例,该实例中已经包含了需要的数据
(
10
)返回
CategoryData::Common
对象实例给
web/Client
端
(
11
)检测数据的合法性
(
12
)读取并显示结果:
Category
的
Description

SystemFramework
项目包含一些
application
需要的配置参数,
ApplicationLog
日志类和
ApplicationAssert
参数校验类。
SystemFramework
项目为所有其他的项目所引用。
Common
项目包含了用于在各层间传递信息的数据集,如上述的
CategoryData
继承
System.Data.DataSet
,既不是所谓的
typed DataSet
,也不是一般的
DataSet
,不过简单实用,这是基于
.Net Remoting
开发分布式系统用来
tier
与
tier
之间交互数据的一种方法。
Common
项目也被其他的项目引用,
SystemFramework
项目除外。
BusinessFacade
项目中所有的
Classes
继承
MarshalByRefObject
class
,显然是让准备将
BusinessFacade tier
部署为
Remote Objects
。不过,实际上默认这里并没有将其部署为
Remote Objects
,
Web
层仍然调用本地对象(《
Duwamish
部署方案篇
》将分析这个问题)。
3
,
Summary
在开发基于
.Net Framework
企业级分布式系统时,上述架构值得推荐,但也并非完美无暇,实际上存在一些值得改进的地方。显然,不可能一个范例适合所有的实际情况么,要求太苛刻了。其实,
Enterprise Samples
中的另外一个范例
Fitch and Mather 7.0
,其架构和
Duwamish
就有些不同了。
如果是开发本地的系统,就不要模仿
Duwamish
架构(看看上面获取
Category
的
Description
调用过程就知道了,太费劲。),如
Business Facade
和
Business Rules
中
Classes
应采用
fine-grained interface
设计,层与层之间的交互参数也不必全部采用
DataSet
,适当的时候采用
setter/getter
就可以了,这样不仅可以提高开发效率,而且有助于提高
performance, maintainability and
reusability
。
References:
1,
卢彦
, Duwamish
深入剖析
-
结构篇
, http://www.microsoft.com/china/community/program/originalarticles/TechDoc/duwamish.mspx
2, MSDN, Duwamish
Duwamish
部署方案篇
Duwamish 7.0
支持两种多计算机部署方案。非分布式部署方案在一台
Web
主机上部署
Web
层、业务外观、业务规则和数据访问层,但可以在群集间复制该
Web
主机以达到负载平衡。分布式方案在单独的服务器上部署特定的组件。例如,业务外观、业务规则和数据访问层可能位于独立于
Web
主机的服务器上。在实际部署中数据库服务器通常位于单独的计算机上。
1,
非分布式部署方案
在一台
Web
主机上部署
Web
层、业务外观、业务规则和数据访问层,然后通过软件(如
Application Center 2000
)或硬件来实现网络场(
Web Farm
)内各个
Web Server
的负载平衡。
在本机默认安装
Duwamish 7.0
时,是采用非分布式部署方案。
2,
分布式部署方案
使用
.NET Framework
远程处理技术将应用程序分布到多台计算机中。简单而言,就是
IIS Web Server
和
Application Server
分离,其中
Web
层(包括
SystemFramework
和
Common
项目)部署在
IIS Web
上,
BusinessFacde/BusinessRules/DataAccess
层(包括
SystemFramework
和
Common
项目)一起部署在
Application Server
上。

Duwamish 7.0
使用
HTTP/
二进制而不是
HTTP/SOAP
。使用
HTTP
的决定基于要通过端口
80
上的防火墙的要求。使用二进制而不是
SOAP
的决定基于性能上的考虑。对于大的数据块,二进制的性能优于
SOAP
。因此,如果要传递大的数据块(例如,数组、数据集或数据表),则使用二进制格式化程序。如果要传递小的数据块,则选择使用
SOAP
还是二进制格式化程序是无关紧要的。传递整数时两者的性能都很好。
3,
如何将
Duwamish 7.0
部署为基于
.Net Remoting
的分布式系统
下面采用
Microsoft
提供的
Deploytool
工具自动进行(其实手工也很方便):
C:/Program Files/Microsoft Visual Studio .NET 2003/Enterprise Samples/Duwamish 7.0 CS>deploytool deploy RemoteMachine=localhost path="C:/Program Files/Microsoft Visual Studio .NET 2003/Enterprise Samples/Duwamish 7.0 CS/Duwamish7_Remote"
在
command line
窗口输入上述命令行代码。
[10/29/2004 6:43:43 AM] Creating directory C:/Program Files/Microsoft Visual Studio .NET 2003/Enterprise Samples/Duwamish 7.0 CS/Duwamish7_Remote on W1MIS38
[10/29/2004 6:43:43 AM] Stopping all internet services on W1MIS38
[10/29/2004 6:43:59 AM] Deploying Duwamish7 Business Facade on W1MIS38
[10/29/2004 6:43:59 AM] Creating web site on W1MIS38
[10/29/2004 6:44:00 AM] Generating remoting configuration files
[10/29/2004 6:44:00 AM] Starting all internet services on W1MIS38
[10/29/2004 6:44:02 AM] Starting Default Web Site on W1MIS38
[10/29/2004 6:44:02 AM] Deployment successful
运行结果:
(
1
)在
IIS
创建中创建
Web Application
(
Duwamish7_Facade
),本地路径为:
C:/Program Files/Microsoft Visual Studio .NET 2003/Enterprise Samples/Duwamish 7.0 CS/Duwamish7_Remote/web
作为
Remote Server
端,
Bin
目录下是
BusinessFacde/BusinessRules/DataAccess
层(包括
SystemFramework
和
Common
项目)
DLL
文件。
其中
web.config
文件中包含所有
Remote Objects
的配置,如
<wellknown mode="Singleton" type="Duwamish7.BusinessFacade.ProductSystem, Duwamish7.BusinessFacade" objectUri="ProductSystem.rem" />
(
2
)
Web
层创建
remotingclient.cfg
配置文件,对
Application Server
而言,
Web
层相当与
Client
端。
remotingclient.cfg
配置文件中包含
formatter
的设置(
binary
),选择二进制格式化程序来序列化消息,注意是出于性能的考虑。
(
3
)
Web application
加载
remotingclient.cfg
配置文件
Web application
在
global.asax
文件包括如下代码,在
Application_OnStart
事件中加载
Retmoting
配置文件。
void Application_OnStart()
{
ApplicationConfiguration.OnApplicationStart(Context.Server.MapPath( Context.Request.ApplicationPath ));
string configPath = Path.Combine(Context.Server.MapPath( Context.Request.ApplicationPath ),"remotingclient.cfg");
if(File.Exists(configPath))
RemotingConfiguration.Configure(configPath);
}
其中前面代码
ApplicationConfiguration.OnApplicationStart()
是调用
Duwamish7
.SystemFramework
.ApplicaitonConfiguration
的
OnApplicationStart()
方法,用来初始化
application root
和读取
web.config
中的配置信息(将在《
Duwamish
代码分析篇》
中进行具体分析)。
Reference:
1, MSDN, Duwamish 7.0
Duwamish
代码分析篇
Written by: Rickie Lee
Nov. 02, 2004
继续前面的
2
篇
POST
《
Duwamish
架构分析篇》
和《
Duwamish
部署方案篇》
,这里在代码层次上分析
Duwamish 7.0
范例,主要目的是解析
Duwamish
范例中值得推荐的编码风格和提炼出可以重用的代码或
Class
。
1
,读取配置文件类-
SystemFramework/ApplicationConfiguration.cs
ApplicationConfiguration
类用来读取
web.config
文件中自定义
section
的配置信息,初始化一些基本设置。
ApplicationConfiguration
类实现
IconfigurationSectionHandler
接口,并需要实现
[C#]
object Create(
object parent,
object configContext,
XmlNode section
)
方法,以分析配置节的
XML
。返回的对象被添加到配置集合中,并通过
GetConfig
访问。
部分代码片断解释:
(
1
)
Code Snippet 1 – ApplicationConfiguration. OnApplicationStart()
方法
public static void OnApplicationStart(String myAppPath)
{
appRoot = myAppPath;
System.Configuration.ConfigurationSettings.GetConfig("ApplicationConfiguration");
System.Configuration.ConfigurationSettings.GetConfig("DuwamishConfiguration");
System.Configuration.ConfigurationSettings.GetConfig("SourceViewer");
}
ConfigurationSettings
类还提供了一个公共方法
ConfigurationSettings.GetConfig()
用于返回用户定义的配置节的配置设置,传入的参数
section name
,如
"ApplicationConfiguration"
,表示要读取的配置节。
NameValueCollection nv=new NameValueCollection();
//
实例化
NameValueCollection
类对象
nv=(NameValueCollection)ConfigurationSettings.GetConfig("ApplicationConfiguration ");
//
返回用户定义的配置节的设置
return nv["SystemFramework.Tracing.Enabled"].ToString();
//
返回特定键值,如
SystemFramework.Tracing.Enabled
不过,
ConfigurationSettings.GetConfig()
方法在调用时,自动调用
Create()
方法,可以看到
ApplicationConfiguration.Create()
方法正是用来读取指定
section
的配置,并初始化设置参数。
Global.asax
的
Application_OnStart
事件处理程序向
SystemFramework
的
ApplicationConfiguration
类
OnApplicationStart
方法发出调用,正是上述的代码片断。
(
2
)
Code Snippet 2
-
Global.asax
中
Application_OnStart()
方法
void Application_OnStart()
{
ApplicationConfiguration.OnApplicationStart(Context.Server.MapPath( Context.Request.ApplicationPath ));
string configPath = Path.Combine(Context.Server.MapPath( Context.Request.ApplicationPath ),"remotingclient.cfg");
if(File.Exists(configPath))
RemotingConfiguration.Configure(configPath);
}
该方法肩负二大任务:(
1
)调用
ApplicationConfiguration.OnApplicationStart()
方法,并传入
application
的根目录(
Root Directory
)。(
2
)检测
Client
端的
remoting
配置文件是否存在(其实是
web server
端),如果存在,则读取并初始化
remoting
配置信息,如配置通道
Channel
等等,详见
《
Duwamish
部署方案篇
》
。
2
,读取
web.config
中
Duwamish
相关的一些配置-
Common/DuwamishConfiguration.cs
Common/DuwamishConfiguration.cs
也实现
IconfigurationSectionHandler
接口,与
SystemFramework/ApplicationConfiguration.cs
类相似。
DuwamishConfiguration
配置节包括如下一些配置信息:
Database connection string
(
Database
连接串)
Duwamish.DataAccess.ConnectionString
,是否允许页面缓存
Duwamish.Web.EnablePageCache
,页面缓存过期时间
Duwamish.Web.PageCacheExpiresInSeconds
,是否允许
SSL
连接
Duwamish.Web.EnableSsl
等等。
如上所述,调用
DuwamishConfiguration Class
是由
SystemFramework/ApplicationConfiguration.cs
的
OnApplicationStart()
方法完成的:
System.Configuration.ConfigurationSettings.GetConfig("DuwamishConfiguration");
看看页面缓存配置在
web page
中如何使用的(
web/book.aspx.cs
文件为例):
//
// If everything succeeded, then enable page caching as indicated
// by the current application configuration.
//
if ( DuwamishConfiguration.EnablePageCache )
{
//Enable Page Caching...
Response.Cache.SetExpires ( DateTime.Now.AddSeconds(DuwamishConfiguration.PageCacheExpiresInSeconds));
Response.Cache.SetCacheability(HttpCacheability.Public);
}
在
Page_Load
事件中最后判断是否允许页面缓存。
3
,验证数据合法性类-
SystemFramework/ApplicationAssert.cs
SystemFramework/ApplicationAssert.cs Class
用来进行错误检测,并调用
SystemFramework/ApplicationLog.cs Class
记录错误日志。
学习其中的部分代码片断:
(
1
)
Code Snippet 1 – Check Method
[ConditionalAttribute("DEBUG")]
public static void Check(bool condition, String errorText, int lineNumber)
{
if ( !condition )
{
String detailMessage = String.Empty;
StringBuilder strBuilder;
GenerateStackTrace(lineNumber, out detailMessage);
strBuilder = new StringBuilder();
strBuilder.Append("Assert: ").Append("/r/n").Append(errorText).Append("/r/n").Append(detailMessage);
ApplicationLog.WriteWarning(strBuilder.ToString());
System.Diagnostics.Debug.Fail(errorText, detailMessage);
}
}
[ConditionalAttribute("DEBUG")]
定义
Check()
方法为
conditional method
,如果预处理符号(
preprocessor symbol
)没有定义,
compiler
不仅忽略该方法,而且忽略对该方法的调用,和
#if DEBUG / #else / #endif
有些类似。
该方法用来判断条件
condition
是否为
true
,如果为
false
,则调用
SystemFramework/ApplicationLog.WriteWarning()
方法记录错误日志。
(
2
)
Code Snippet 2 – CheckCondition Method
public static void CheckCondition(bool condition, String errorText, int lineNumber)
{
//Test the condition
if ( !condition )
{
//Assert and throw if the condition is not met
String detailMessage;
GenerateStackTrace(lineNumber, out detailMessage);
Debug.Fail(errorText, detailMessage);
throw new ApplicationException(errorText);
}
}
该方法一般用来在进行前置条件判断,如
condition
为
false
,则抛出
exception
。
4
,
log
日志类-
SystemFramework/ApplicationLog.cs
ApplicationLog
类实现
Duwamish 7.0
中的记录和跟踪。
Web.Config
文件中的配置设置确定是输出到
EventLog
文件、跟踪日志文件还是两者。下面是
Web.Config
文件中的
<ApplicationConfiguration>
节,它指定
EventLog
设置:
<ApplicationConfiguration>
<!-- Event log settings -->
<add key="SystemFramework.EventLog.Enabled" value="True" />
<add key="SystemFramework.EventLog.Machine" value="." />
<add key="SystemFramework.EventLog.SourceName" value="Duwamish7" />
<add key="SystemFramework.EventLog.LogLevel" value="1" />
<!-- Use the standard TraceLevel values:
0 = Off
1 = Error
2 = Warning
3 = Info
4 = Verbose -->
Web.Config
文件的同一节还指定跟踪配置。
Duwamish 7.0
跟踪日志的默认位置是:
[
安装
Visual Studio .NET
的驱动器号
]:/Program Files/Microsoft Visual Studio .NET 2003/Enterprise Samples/Duwamish 7.0 CS/Web/DuwamishTrace.txt
在实际的应用系统开发中,用来提供
Log
功能的类应该比这个
ApplicationLog
类要好,这样就不去分析了,如
Microsoft Exception Management Application Block
就不错。
5
,
Web.config
配置文件-使用
Web.congfig
文件存储
application
设置
Duwamish 7.0
通过使用
Forms
身份验证来实现安全性。
Forms
身份验证将未经授权的用户重定向到
Web
窗体,该窗体提示用户输入其电子邮件地址和密码。
(
1
)配置
Forms
身份验证
Web.config
文件中的设置配置
Forms
身份验证。对于
Duwamish 7.0
,
Web.Config
文件按如下所述指定
Forms
身份验证的使用:
<authentication mode="Forms">
<forms name=".ADUAUTH" loginUrl="secure/logon.aspx" protection="All">
</forms>
</authentication>
<authorization>
<allow users="*" />
</authorization>
authentication
元素只能在计算机、站点或应用程序级别声明。如果试图在配置文件中的子目录或页级别上进行声明,则将产生分析器错误信息。
如上所示,
Web.Config
将
.ADUAUTH
指定为身份验证
Cookie
的名称。当用户请求受限资源时,公共语言运行库将未经授权的用户重定向到在上面的
Web.Config
设置中指定的
Login.aspx
。
protection="All"
设置指定应用程序使用数据验证和加密来保护
Cookie
。若要进一步限制资源,
Duwamish 7.0
会将安全资源放置到名为
secure
的子文件夹中并使用额外的
Web.Config
文件(在
secure
文件夹),在该文件中指定只有经过身份验证的用户才可访问该子文件夹的内容。
<authorization>
<deny users="?" />
<allow users="*" />
</authorization>
<deny users="?" />
标记指定拒绝对所有匿名用户的访问。
<allow users="*" />
标签允许访问所有已验证身份的用户。
经过
secure/logon.aspx
认证通过的请求,重新定向最初的
URL
。
//
将已验证身份的用户重定向回最初请求的
URL
。
FormsAuthentication.RedirectFromLoginPage("*", false);
(
2
)用户定义的配置节
<configSections>
<section name="ApplicationConfiguration" type="Duwamish7.SystemFramework.ApplicationConfiguration, Duwamish7.SystemFramework" />
<section name="DuwamishConfiguration" type="Duwamish7.Common.DuwamishConfiguration, Duwamish7.Common" />
<section name="SourceViewer" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</configSections>
section
元素包含配置节声明,
name
指定配置节的名称,
type
指定从配置文件中读取节的配置节处理程序类的名称。配置节处理程序
(
即实现
IConfigurationSectionHandler
接口的类
)
读取设置。
上面定义了
3
个配置节处理程序:
Duwamish7.SystemFramework.ApplicationConfiguration
Duwamish7.Common.DuwamishConfiguration
System.Configuration.NameValueSectionHandler
前面两个配置节处理程序是由
application
提供的,后面的
System.Configuration.NameValueSectionHandler
是
.Net Framework
提供的,该类也实现了
IconfigurationSectionHandler
接口,用来提供配置节中的名称
/
值对配置信息。
6
,
web/PageBase.cs
基类
最后谈谈
web/PageBase.cs
基类吧,所有
Duwamish 7.0
中的所有
web
页都继承名为
PageBase
的基类,该类实现
Duwamish 7.0
应用程序的
ASP.Net
页中使用的常见属性和方法,这种设计方法值得推荐。
Code Snippet
分析:
/// <summary>
/// Handles errors that may be encountered when displaying this page.
/// <param name="e">An EventArgs that contains the event data.</param>
/// </summary>
protected override void OnError(EventArgs e)
{
ApplicationLog.WriteError(ApplicationLog.FormatException(Server.GetLastError(), UNHANDLED_EXCEPTION));
base.OnError(e);
}
重载
OnError
方法,使
application
遭遇到未处理错误的时候,自动调用
ApplicationLog.WriteError()
来记录错误日志。
另外,觉得
Duwamish
的
password
处理有些特别,是以
byte
形式存放在
Database
中,避免明文的方式,以提高安全性(将在《
Duwamish
密码分析篇
》进行中分析)。
Reference:
1, MSDN, Duwamish 7.0
2, Rickie,
Duwamish架构分析篇
3, Rickie,
Duwamish部署方案篇
Duwamish
密码分析篇
, Part 1
Written by: Rickie Lee
Nov. 05, 2004
继续前面关于
Duwamish
的
POST
,这里将学习
Duwamish
中关于
Password
的处理方式。
Duwamish 7.0
范例中的帐户密码通过
SHA1
散列运算和对散列执行
Salt
运算后,
是以
byte
形式存放在
Database
中,避免明文的方式
,以提高系统的安全性。
Duwamish
的用户注册部分是封装在
/web/modules/accountmodule.ascx
用户控件内。随便提一下,
Duwamish web tier
中采用了大量的
user control
,并且所有的
user control
都继承
/web/ModuleBase.cs
类,与
web page
继承
PageBase.cs
类相似,这种做法值得推荐。
Duwamish
中
user control
主要是封装一些相应的功能,模块化。这样不仅可以在本
web
项目内重用,而且以后维护也比较方便,如
/web/modules/accountmodule.ascx user control
就封装了用户注册部分的功能。
下面看看【用户注册】功能模块具体的实现代码(
/web/modules/accountmodule.ascx
):
1
,
获取用户登记
/
注册
password
,并帐户密码执行散列
运算。
byte [] bytePassword = null;
String tmpPassword = PasswordTextBox.Text;
if (tmpPassword == ConfirmPasswordTextBox.Text)
{
SHA1 sha1 = SHA1.Create();
bytePassword = sha1.ComputeHash(Encoding.Unicode.GetBytes(tmpPassword));
}
……
retVal = (new CustomerSystem()).CreateCustomer(EmailTextBox.Text,
bytePassword,
AcctNameTextBox.Text,
AddressTextBox.Text,
CountryTextBox.Text,
PhoneTextBox.Text,
FaxTextBox.Text,
out moduleCustomerInfo);
先使用实现
160
位
SHA-1
标准的
System.Security.Cryptography
命名空间对密码进行散列运算。然后调用
BusinessFacade/CustomerSystem
类的
CreateCustomer()
方法。
知识点:
散列简介
散列(
Hash
)是一种单向算法,一旦数据被转换,将无法再获得其原始值。大多数开发人员使用数据库存储密码,如果密码直接以明文的形式存放在数据库中,则开发人员也能够看到这些密码,甚至包括用户的
Credit Card
信息。
不过,我们可以使用散列算法对密码进行加密,然后再将其存储在数据库中。用户输入密码后,可以再次使用散列算法对其进行转换,然后将其与存储在数据库中的散列进行比较。散列的特点之一是,即使原始数据只发生一个小小的改动,数据的散列也会发生非常大的变化。
Rickie
和
Ricky
这两个单词非常相似,但使用散列算法加密后的结果却相去甚远。你可能根本看不出二者之间有什么相似之处。
.NET
开发人员可以使用多种散列算法类。最常用的是
SHA1
和
MD5
。下面我们看一下如何为
Rickie
这样的普通字符串生成散列,使任何人都无法识别它。
(
1
)使用
SHA1
生成散列
通过如下的示例代码,来演示如何通过
SHA1
生成散列:
byte [] bytePassword = null;
string tmpPassword = txtPassword.Text.Trim();
//
创建新的加密服务提供程序对象
SHA1 sha1 = SHA1.Create();
//
将原始字符串转换成字节数组,然后计算散列,并返回一个字节数组
bytePassword = sha1.ComputeHash(Encoding.Unicode.GetBytes(tmpPassword));
// Releases all resources used by the System.Security.Cryptography.HashAlgorithm.
sha1.Clear();
//
返回散列值的
Base64
编码字符串
txtResults.Text = Convert.ToBase64String(bytePassword);
传递不同的字符串值来调用该例程,查看散列值的变化。例如,如果将字符串
Rickie
传递给该例程,输出结果:
v8ocXHBvlh4EqY/2HsJNH5XBVG0=
现在,将此过程中的输入值更改为
Ricky
。你将看到以下输出结果:
luQsSa61sB/7PT9piDx+OAGqCnI=
如此可见,输入字符串的一个小小变化就会产生完全不同的字符组合。这正是散列算法之所以有效的原因,它使我们很难找到输入字符串的规律,也很难根据加密后的字符弄清楚字符串原来的模样。
(
2
)使用
MD5
也可以生成散列
通过如下的示例代码,来演示如何通过
MD5
生成散列:
byte [] bytePassword = null;
string tmpPassword = txtPassword.Text.Trim();
MD5 md5 = MD5.Create();
bytePassword = md5.ComputeHash(Encoding.Unicode.GetBytes(tmpPassword));
// Releases all resources used by the System.Security.Cryptography.HashAlgorithm.
md5.Clear();
txtResults.Text = Convert.ToBase64String(bytePassword);
输入
Rickie
,
MD5
散列算法的输出结果:
YUqR1JfNxrciyG0ixNj58A==
同样,加密后的字符串看起来也与原始输入相去甚远。这些散列算法对于创建没有任何意义的密码来说非常有用,也使黑客很难猜出这些密码。之所以使用散列算法,是因为可以用这种算法对密码进行加密并将其存储在数据库中。然后,当用户输入真实密码时,需要先对用户输入的密码进行同样的散列,然后通过网络发送到数据库中,比较它与数据库中的密码是否匹配。
请记住,散列是单向操作。使用散列算法对原始密码加密后将无法再恢复。
上述两种散列算法都执行同一种操作。不同之处只在于生成散列的密钥大小以及使用的算法。使用的密钥越大,加密就越安全。例如,
MD5
使用的加密密钥比
SHA1
使用的密钥大,因此
MD5
散列较难破解。
对于散列算法要考虑的另外一点是,从实践或理论的角度上看是否存在冲突的可能性。冲突是我们所不希望的,因为两个不同的单词可能会生成相同的散列。例如,
SHA1
从实践或理论上来讲没有发生冲突的可能性。
MD5
从理论上讲有发生冲突的可能性,但从实践上讲没有发生冲突的可能性。因此,选择哪种算法归根结底取决于所需要的安全级别。
(
3
)
Summary
一般情况下,将上述加密的字节数组,通过使用
Convert.ToBase64String(bytePassword)
方法把字节数组转换成
Base64
编码的字符串,然后存储在数据库中即可完成一般的商业应用。
2
,调用
BusinessFacade/CustomerSystem
类,对散列执行
Salt
运算。
到目前为止,散列算法暴露出来的问题之一是,如果两个用户碰巧使用相同的密码,那么散列值将完全相同。如果黑客看到您存储密码的表格,会从中找到规律并明白您很可能使用了常见的词语,然后黑客会开始词典攻击以确定这些密码。要确保任何两个用户密码的散列值都不相同,一种方法是在加密密码之前,在每个用户的密码中添加一个唯一的值。这个唯一值称为“盐”值(
Salt
)。
虽然对密码执行散列运算是一个好的开端,但若要增加免受潜在攻击的安全性,则可以对密码散列执行
Salt
运算。
Salt
就是在已执行散列运算的密码中插入的一个随机数字。这一策略有助于阻止潜在的攻击者利用预先计算的字典攻击。字典攻击是攻击者使用密钥的所有可能组合来破解密码的攻击。当您使用
Salt
值使散列运算进一步随机化后,攻击者将需要为每个
Salt
值创建一个字典,这将使攻击变得非常复杂且成本极高。
Salt
值随散列存储在一起,并且未经过加密。所存储的
Salt
值可以在随后用于密码验证。
下面看看
Duwamish 7.0
中是如何实现
Salt
运算:
(
1
)
BusinessFacade/CustomerSystem class
中
Create Customer()
方法
public bool CreateCustomer(String emailAddress,
byte [] password,
String name,
String address,
String country,
String phoneNumber,
String fax,
out CustomerData custData)
{
// create a salted password
byte [] saltedPassword = CreateDbPassword(password);
//
// Create a new row
//
custData = new CustomerData();
DataTable table = custData.Tables[CustomerData.CUSTOMERS_TABLE];
DataRow row = table.NewRow();
//
// Fill input data into new row
//
row[CustomerData.EMAIL_FIELD] = emailAddress;
row[CustomerData.PASSWORD_FIELD] = saltedPassword;
row[CustomerData.NAME_FIELD] = name;
row[CustomerData.ADDRESS_FIELD] = address;
row[CustomerData.COUNTRY_FIELD] = country;
row[CustomerData.PHONE_FIELD] = phoneNumber;
row[CustomerData.FAX_FIELD] = fax;
//
// Add it to the table
//
table.Rows.Add(row);
//
调用
Business rules tier
的
Customer Class
// Insert the customer using the business rules
//
return (new Customer()).Insert(custData);
}
首先调用
Facade/CustomerSystem
类的私有方法
CreateDbPassword()
,获取对散列执行
Salt
运算结果(长度为
24
个字节的
byte
数组),然后调用
Business rules tier
中的
Customer class
的
Insert()
方法,将用户信息,包括密码存放在数据库中。
(
2
)
Facade/CustomerSystem
类的私有方法
CreateDbPassword()
// create salted password to save in Db
private byte [] CreateDbPassword(byte[] unsaltedPassword)
{
//Create a salt value
byte[] saltValue = new byte[saltLength];
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
//
用加密型强随机字节填充的数组
rng.GetBytes(saltValue);
return CreateSaltedPassword(saltValue, unsaltedPassword);
}
上述代码片断使用
.NET Framework
类
RNGCryptoServiceProvider
创建一个随机的数字字符串。
RNG
表示随机数生成器。该类可以创建一个任意长度的随机字节数组,长度由您指定。您可以使用此随机字节数组作为散列算法的
Salt
值。要采用这种方法,必须安全地存储该
Salt
值。
saltLength=4
(常量),
Duwamish 7
示例用
RNGCryptoServiceProvider
创建一个
4
字节
Salt
值。然后调用
Facade/CustomerSystem
类的私有方法
CreateSaltedPassword()
,获取对散列执行
Salt
运算后的结果。
(
3
)
Facade/CustomerSystem
类的私有方法
CreateSaltedPassword()
// create a salted password given the salt value
private byte[] CreateSaltedPassword(byte[] saltValue, byte[] unsaltedPassword)
{
// add the salt to the hash
byte[] rawSalted = new byte[unsaltedPassword.Length + saltValue.Length];
// Copies all the elements of the current one-dimensional System.Array to the specified one-dimensional System.Array starting at the specified destination System.Array index.
unsaltedPassword.CopyTo(rawSalted,0);
saltValue.CopyTo(rawSalted,unsaltedPassword.Length);
//Create the salted hash
SHA1 sha1 = SHA1.Create();
byte[] saltedPassword = sha1.ComputeHash(rawSalted);
// add the salt value to the salted hash
byte[] dbPassword = new byte[saltedPassword.Length + saltValue.Length];
saltedPassword.CopyTo(dbPassword,0);
saltValue.CopyTo(dbPassword,saltedPassword.Length);
return dbPassword;
}
该方法根据传入的
Salt
值(长度为
4
个字节的
byte
数组)和已执行散列运算的密码(长度为
20
个字节的
byte
数组),拼接为长度为
24
的
byte
数组。然后对上述拼接后的数组再进行
SHA1
散列运算,得到结果
saltedPassword
(长度为
20
个字节的
byte
数组)。
最后将
saltedPassword
(长度为
20
个字节的
byte
数组)和
Salt
值(长度为
4
个字节的
byte
数组)拼接为
dbPassword
(长度为
4
个字节的
byte
数组)返回。
3
,调用
BusinessRules/Customer
类的
Insert()
方法。
Insert()
方法根据传入的
CustomerData
对象,验证数据的合法性,然后调用
Data Access tier
的
Customers
对象的
InsertCustomer()
方法。
具体代码请参考
Duwamish 7.0
范例。
4
,调用
DataAccess/Customers
类的
InsertCustomer()
方法。
InsertCustomer()
方法根据传入的
CustomerData
对象,调用
Database
端的
Stored Procedure
,执行真正的数据库
insert
操作。可以观察到
Duwamish7 Database
中
Customers
表的
Password
字段类型为
binary
且长度为
24
。
具体代码请参考
Duwamish 7.0
范例。
下一篇
POST
《
Duwamish
密码分析篇
Part 2
》
将分析【用户登录】流程的密码验证过程。
References:
1, MSDN, Duwamish 7.0
2, Paul D. Sheriff, Microsoft .NET
中的简化加密
, http://www.microsoft.com/china/MSDN/library/archives/library/dnnetsec/html/cryptosimplified.asp
Duwamish
密码分析篇
, Part 2
继续前面关于
Duwamish
的
POST
,这里将学习
Duwamish
中关于
Password
的处理方式。
Duwamish 7.0
范例中的帐户密码通过
SHA1
散列运算和对散列执行
Salt
运算后,是以
byte
形式存放在
Database
中,避免明文的方式,以提高系统的安全性。
1
,【用户登录】过程概述
在
Web
层中启动登录过程。用户输入电子邮件地址和密码(凭据),然后单击“
Logon
”(登录)按钮,这将调用
Duwamish7.Web.Logon.LogonButton_Click
方法。下一步,
Duwamish7.Web.Logon.LogonButton_Click
方法创建密码的散列表示形式,并将凭据传递给业务外观层的
Duwamish7.BusinessFacade.CustomerSystem.GetCustomerByEmail
方法。接着
Duwamish7.DataAccess.Customers.LoadCustomerByEmail
方法调用数据访问层,后者又调用
GetCustomerByEmail
存储过程
(SPROC)
。然后通过
ComparePasswords
方法,相对于从数据库中检索的经过
salt
和散列运算的密码来验证散列密码。如果凭据有效,则客户帐户信息成功地存储到
Cart
对象,并且
ASP.NET Forms
身份验证通过
pageBase
类
ShoppingCart.Customer()
属性验证凭据。如果凭据无效,则
MismatchLabel
设置为可见,它在
ASP.NET
页上显示下面的内容:“
Invalid email address or password- please try again
”(电子邮件地址或密码无效,请再试一次)。
2
,下面看看【用户登录】验证功能模块具体的实现代码
(
1
)
Duwamish7.Web.Logon.LogonButton_Click
方法
该方法首先创建用户输入密码的
SHA1
散列形式,然后调用业务外观层的
Duwamish7.BusinessFacade.CustomerSystem.GetCustomerByEmail
方法。
//
// Check the Email and Password combination
//
SHA1 sha1 = SHA1.Create();
byte [] password = sha1.ComputeHash(Encoding.Unicode.GetBytes(LogonPasswordTextBox.Text));
custData = (new CustomerSystem()).GetCustomerByEmail(LogonEmailTextBox.Text, password);
if (custData != null) //were they valid?
{
//
// 1. Update customer in session.
// 2. Update customer in cart.
//
base.Customer = custData;
base.ShoppingCart().Customer = custData;
//
将已验证身份的用户重定向回最初请求的
URL
FormsAuthentication.RedirectFromLoginPage("*", false);
}
else
{
MismatchLabel.Visible = true;
}
如果凭据有效,则客户帐户信息成功地存储到
Cart
对象,并且
ASP.NET Forms
身份验证通过
pageBase
类
ShoppingCart.Customer()
属性验证凭据。如果凭据无效,则
MismatchLabel
设置为可见,它在
ASP.NET
页上显示下面的内容:“
Invalid email address or password- please try again
”(电子邮件地址或密码无效,请再试一次)
(
2
)业务外观层的
CustomerSystem.GetCustomerByEmail
方法
根据用户的
email
,获取
Database
中
Customer
的
Password
,该
Password
已执行散列运算和对散列执行过
Salt
运算,是
24
个字节长度的
byte
数组。
public CustomerData GetCustomerByEmail(String emailAddress, byte [] password)
{
//
// Check preconditions
//
ApplicationAssert.CheckCondition(emailAddress != String.Empty, "Email address is required", ApplicationAssert.LineNumber);
ApplicationAssert.CheckCondition(password.Length != 0, "Password is required", ApplicationAssert.LineNumber);
//
// Get the customer dataSet
//
CustomerData dataSet;
using (DataAccess.Customers customersDataAccess = new DataAccess.Customers())
{
dataSet = customersDataAccess.LoadCustomerByEmail(emailAddress);
}
//
// Verify the customer's password
//
DataRowCollection rows = dataSet.Tables[CustomerData.CUSTOMERS_TABLE].Rows;
if ( ( rows.Count == 1 ))
{
byte [] dbPassword = (byte[])rows[0][CustomerData.PASSWORD_FIELD];
if (ComparePasswords (dbPassword, password))
return dataSet;
else
return null;
}
else
return null;
}
在获取到
DataAccess
层返回的
CustomerData
对象后,进一步调用类中的私有方法
ComparePasswords
()。该方法负责从
Password
字段值中提取
Salt
值,然后运用该
Salt
值对传入的
SHA1
散列执行
Salt
运算。
其中
CreateSaltedPassword()
方法和前面【用户注册】过程相同,用来对散列结果再次执行
Salt
运算。
// compare the hashed password against the stored password
private bool ComparePasswords(byte[] storedPassword, byte[] hashedPassword)
{
if (storedPassword == null || hashedPassword == null || hashedPassword.Length != storedPassword.Length - saltLength)
return false;
// get the saved saltValue
//
获取已保存在
password
数据字段中的
Salt
值
byte[] saltValue = new byte[saltLength];
int saltOffset = storedPassword.Length - saltLength;
for (int i = 0; i < saltLength; i++)
saltValue[i] = storedPassword[saltOffset + i];
byte[] saltedPassword = CreateSaltedPassword(saltValue, hashedPassword);
// compare the values
return CompareByteArray(storedPassword, saltedPassword);
}
按字节比较两个字节数组是否相等,分别传入数据表中
Password
字段
byte
数组和对当前散列执行
Salt
运算后的
byte
数组。
// compare the contents of two byte arrays
private bool CompareByteArray(byte[] array1, byte[] array2)
{
if (array1.Length != array2.Length)
return false;
for (int i = 0; i < array1.Length; i++)
{
if (array1[i] != array2[i])
return false;
}
return true;
}
(
3
)数据访问层的
Customers.LoadCustomerByEmail()
方法
该方法根据传入的
emailAddress
参数,查询
Database
,返回
CustomerData
对象。过程比较简单,详细代码请查询
Duwamish 7.0
范例。
3
,
Summary
通过对
Duwamish 7.0
范例中用户注册和用户登录验证过程的分析,我们确信用户
Password
以安全的
Salt
运算结果存放在后台的
Database
中。
其实,在实际的应用系统中,上述的加密过程有一个小问题:就是当有大量的用户忘记了自己的
Password
,如何帮助他们恢复自己的
Password
呢?这个问题地球人都不知道,只有通过另外的
application
来将这些
Password
重新
Update
为新的
Password
,因为散列是单向操作,使用散列算法对原始密码加密后将无法再恢复。
因此,将在《
Duwamish
密码分析篇
, Part 3
》
中分析如何实现双向的加密
/
解密操作,来克服上面提出的问题。
References:
1, MSDN, Duwamish 7.0
2, Rickie,
Duwamish密码分析篇, Part 1
Duwamish
密码分析篇
, Part
3
Written by: Rickie Lee
Nov. 07, 2004
通过前面关于《
Duwamish
密码分析篇
, Part 1
-
2
》的
POST
,可以了解到
Duwamish
中关于
Password
的处理方式。
Duwamish 7.0
范例中的帐户密码通过
SHA1
散列运算和对散列执行
Salt
运算后,然后以
byte
形式存放在
Database
中,避免明文的方式,以提高系统的安全性。
但是,由于散列是单向操作,使用散列算法对原始密码加密后将无法再恢复。因此,在实际的应用系统中,上述的加密过程有一个小问题:就是当有大量的用户忘记了自己的
Password
,如何帮助他们恢复自己的
Password
呢?这个问题地球人都不知道。
因此,对于一般的商业应用,通过
.Net
的加密
/
解密类库来同时实现上述目的,既加密
Password
等重要信息,同时也可以在必要的时候解密这些信息。
加密算法使可以将数据掩盖起来,除了特定人员能够对其解密外,其他人员不大可能通过数学方法读取该数据。但如果希望读取该数据,则可以为其提供一个特定的“密钥”,使其能够解密并读取数据。
.NET Framework
中有多种可用的加密
/
解密算法。
目前有两种加密方法:
对称算法(或密钥算法)
的速度非常快,非常适于加密大型的数据流。这些算法可以加密数据,也可以解密数据。对称加密技术的数据交换两边(即加密方和解密方)必须使用一个保密的私有密钥。
不对称算法(或公钥算法)
没有对称算法快,但其代码较难破密。这些算法取决于两个密钥,一个是私钥,另一个是公钥。公钥用来加密消息,私钥是可以解密该消息的唯一密钥。公钥和私钥通过数学方法链接在一起,因此要成功进行加密交换,必须获得这两个密钥。由于可能会影响到计算机性能,因此不对称算法不太适用于加密大量数据。不对称算法的常见用法是将对称密钥和初始化向量加密并传输给对方。然后在双方之间来回发送的消息中使用对称算法加密和解密数据。不对称算法主要有
RSA
、
DSA
等,主要用于网络数据的加密。保护
HTTP
传输安全的
SSL
就是使用非对称技术。
本文先主要学习
.Net
中如下对称算法(或密钥算法)类库,包括以下几种:
DES
(
Data Encryption Standard
),
TripleDES
,
RC2
,
Rijndael
对称算法(或密钥算法)使用一个密钥和一个初始化向量
(Initialization Vector
,
IV)
来保证数据的安全。加密的功效取决于所用密钥的大小,密钥越长,保密性越强。典型的密钥长度有
64
位、
128
位、
192
位、
256
位和
512
位。使用该数据的双方都必须知道这个密钥和初始化向量才能够加密和解密数据。必须确保该密钥的安全,否则其他人将有可能解密该数据并读取该消息。初始化向量只是一个随机生成的字符集,使用它可以确保任何两个文本都不会生成相同的加密数据。然后,在此基础上学习开发一套标准的加密
/
解密通用类库,供今后开发应用系统时使用。
.Net Framework
内置加密
/
解密算法类库
.NET Framework
为各种最广泛使用的对称加密算法提供了支持。
.NET
构架从基本的
SymmetricAlgorithm
类扩展出来四种算法:
·
System.Security.Cryptography.DES
·
System.Security.Cryptography.TripleDES
·
System.Security.Cryptography.RC2
·
System.Security.Cryptography.Rijndael
.NET
的对称加密和解密通过
CryptoStream
类来处理的,它继承自
System.IO.Stream
,使用
CryptoStream
对象的
Write
方法将数据写入到内存数据流(
Memory Stream
),这就是进行实际加密
/
解密的方法。
先以
DES
算法为例,了解
.Net
为我们提供的简单加密
/
解密过程:
(
1
)
DES
加密

上图
Encrypt
实例代码:
private void btnEncrypt_Click(object sender, System.EventArgs e)
{
SymmetricAlgorithm mCSP = new DESCryptoServiceProvider();
ICryptoTransform ct;
MemoryStream ms;
CryptoStream cs;
byte[] byt;
byte [] key, iv;
// If this property is a null reference (Nothing in Visual Basic) when it is used,
// GenerateKey is called to create a new random value.
key = mCSP.Key;
// If this property is a null reference (Nothing in Visual Basic) when it is used,
// GenerateIV is called to create a new random value.
iv = mCSP.IV;
// Creates a symmetric Data Encryption Standard (DES) encryptor object.
ct = mCSP.CreateEncryptor(key, iv);
// Refresh textboxes
txtKey.Text = Convert.ToBase64String(key);
txtIV.Text = Convert.ToBase64String(iv);
byt = Encoding.Unicode.GetBytes(txtOriginal.Text.Trim());
ms = new MemoryStream();
cs = new CryptoStream(ms, ct, CryptoStreamMode.Write);
cs.Write(byt, 0, byt.Length);
cs.FlushFinalBlock();
cs.Close();
txtResults.Text = Convert.ToBase64String(ms.ToArray());
}
首先创建
DESCryptoServiceProvider
对象,要使用对称算法,必须提供要使用的密钥。每个
CryptoSymmetricAlgorithm
实现都提供一种
GenerateKey
方法。它们实际上使用的是公共语言运行时
(CLR)
类中内置的随机数生成器类。在首次调用
DESCryptoServiceProvider
对象的
Key
属性时,因为该属性为
Null
,则自动调用
GenerateKey
方法来生成随机
Key
。密钥的大小取决于用来加密的特定提供程序。例如,
DES
密钥的大小为
64
位,而
TripleDES
密钥的大小为
192
位。每个
SymmetricAlgorithm
类上都有一个
KeySize
属性,它将返回用于生成密钥的密钥大小。
然后是需要生成初始化向量
(IV)
,如果
IV
属性为
Null
,也是自动调用
GenerateIV
方法,生成
IV
。只要使用的
IV
不同,即使密钥相同,同一个数据也会被加密成完全不同的值。当然,你可以自己指定特定的
Key
和
IV
,但要注意的不同的加密算法,可能采用不同的
Key
长度和
IV
长度。
ICryptoTransform
是一个接口。需要此接口才能在任何服务提供程序上调用
CreateEncryptor
方法,服务提供程序将返回定义该接口的实际
encryptor
对象。
Encoding.Unicode.GetBytes()
方法负责将原始字符串转换成字节数组。大多数
.NET
加密算法处理的是字节数组而不是字符串。
现在可以执行实际的加密了。此进程需要创建一个数据流,用于将加密的字节写入到其中。要使用名为
ms
的
MemoryStream
对象、
ICryptoTransform
对象(提供给
CryptoStream
类的构造函数)以及说明您希望在何种模式(读、写等)下创建该类的枚举常数。创建
CryptoStream
对象
cs
后,现在使用
CryptoStream
对象的
Write
方法将数据写入到内存数据流。这就是进行实际加密的方法,加密每个数据块时,数据将被写入
MemoryStream
对象。
创建
MemoryStream
后,该代码将在
CryptoStream
对象上执行
FlushFinalBlock
方法,以确保所有数据均被写入
MemoryStream
对象。并调用
CryptoStream
对象的
Close
方法关闭
CryptoStream
对象。
最后,调用
Convert.ToBase64String()
方法,该方法接受字节数组输入并使用
Base64
编码方法将加密结果编码为可读内容,输出到
TextBox
显示。
(
2
)
DES
解密

上图
Decrypt
实例代码:
private void btnDecrypt_Click(object sender, System.EventArgs e)
{
SymmetricAlgorithm mCSP = new DESCryptoServiceProvider();
ICryptoTransform ct;
MemoryStream ms;
CryptoStream cs;
byte[] byt;
// Sets the key for the symmetric algorithm.
mCSP.Key = Convert.FromBase64String(txtKey.Text.Trim());
// Sets the initialization vector (IV) for the symmetric algorithm.
mCSP.IV = Convert.FromBase64String(txtIV.Text.Trim());
// Creates a symmetric Data Encryption Standard (DES) decryptor object.
ct = mCSP.CreateDecryptor(mCSP.Key, mCSP.IV);
byt = Convert.FromBase64String(txtOriginal.Text.Trim());
ms = new MemoryStream();
cs = new CryptoStream(ms, ct, CryptoStreamMode.Write);
cs.Write(byt, 0, byt.Length);
cs.FlushFinalBlock();
cs.Close();
txtResults.Text = Encoding.Unicode.GetString(ms.ToArray());
}
Decrypt
过程代码与
Encrypt
过程很相似,只有细微的差别。首先,
Decrypt
过程不需要重新生成随机的
Key
和
IV
,必须使用与上述
Encrypt
过程中相同的
Key
和
IV
,否则无法解密。
另外,
ICryptoTransform
是一个接口,这里是在
DESCryptoServiceProvider
服务提供程序上调用
CreateDecryptor
方法,服务提供程序将返回定义该接口的实际
decryptor
对象。
.Net Framework
也支持其他加密
/
解密算法,如
TripleDES
,
RC2
,
Rijndael
,并且只要简单替换上述代码的
DESCryptoServiceProvider
服务提供程序对象,就可以切换到对应的加密算法。正是基于这一点,我们可以非常方便地构建一套标准的加密
/
解密通用类库,请参考
Microsoft
的《
如何创建加密库
》,这里不重复了。

知识点:
Base64
编码
前面的加密
/
解密过程中,使用到
.Net Framework
提供的关于
Base64
编码的方法:
(
1
)
Convert.ToBase64String()
方法,该方法接受字节数组输入并使用
Base64
编码方法将加密结果编码为可读内容
(
2
)
Convert.FromBase64String()
方法,该方法接受
Base64
编码的字符串,输出字节数组。
Base64
是
MIME
邮件中常用的编码方式之一。它的主要思想是将输入的字符串或数据编码成只含有
{'A'-'Z', 'a'-'z', '0'-'9', '+', '/'}
这
64
个可打印字符的串,故称为“
Base64
”。
Base64
编码的方法是,将输入数据流每次取
6 bit
,不足
6bit
的补
0
,用此
6 bit
的值
(0-63)
作为索引去查表,输出相应字符。这样,每
3
个字节将编码为
4
个字符
(3
×
8
→
4
×
6)
(之后在
6
位的前面补两个
0
,形成
8
位一个字节的形式),不满
4
个字符的以
'='
填充。转换后的字符串理论上将要比原来的长
1/3
。
现在回到前面的
DES
算法中
Key
或者
IV
,
Key
长度为
64
位(
8
个字节),为了让
Base64
编码是
4
的倍数,就要补
1
个等号。
References:
1, MSDN, Duwamish 7.0
2, Rickie,
Duwamish密码分析篇, Part 1
3, Rickie,
Duwamish密码分析篇, Part 2
4, Paul D. Sheriff, Microsoft .NET
中的简化加密
, http://www.microsoft.com/china/MSDN/library/archives/library/dnnetsec/html/cryptosimplified.asp
Duwamish Web Services
分析篇
Written by: Rickie Lee
Nov. 08, 2004
Duwamish 7.0
在
web
项目中提供了一个
Web Service
(
service/catalogservice.asmx
),以向
Internet
公开它的书目录搜索功能。
CatalogService Web Service
由一个
asmx
文件和一个代码隐藏文件组成,其中
ASMX
文件充当调用
Web Services
的客户端的基
URL
,代码隐藏文件包含实现
Web
服务的代码。不过,在整个
Duwamish
项目中并没有调用该
web service
,正如以前的
POST
中所提及的:
If you need to communicate between applications (even .NET apps) then use web services. Note this is not between tiers, but between applications – as in SOA (Service-Oriented Architecture). SOA is not useful INSIDE applications. It is only useful BETWEEN applications.
1. Web Services
概述
Web Services
既可以在内部由单个应用程序使用,也可通过
Internet
公开以供外部的应用程序使用。由于可以通过标准接口访问,因此
Web Services
使异类系统能够作为单个计算网络资源协同运行。
Web Services
并不追求一般的代码可移植性功能,而是为实现数据和系统的互操作性提供了一种可行的解决方案。
Web Services
使用基于
XML
的消息处理作为基本的数据通讯方式,以帮助消除使用不同组件模型、操作系统和编程语言的系统之间存在的差异。开发人员可以用像过去在创建分布式应用程序时使用组件一样的方式创建将来自各种平台的
Web Services
组合在一起的应用程序。
Web Services
的核心特征之一是服务的实现与使用之间的高度抽象化。通过将基于
XML
的消息处理机制,
Web Services
客户端和
Web Services
提供程序之间除输入、输出和位置之外无需互相了解其他信息。
Web Services
向外界发布出一个能够通过
Web
进行调用的、平台无关的
API
。也就是说,你能够在任何你喜欢的平台上,用编程的方法通过
Web
来调用这个应用程序,进行基于
Web
的分布式计算和处理。
Web Services
平台是一套标准,它定义了应用程序如何在
Web
上实现互操作性。
Web Services
平台采用
XML
来表示数据的基本格式,采用
W3C
制定的
XML Schema(XSD)
来作为其数据类型系统。
组成
Web Services
平台的三个核心的技术规范分别为
SOAP
、
WSDL
和
UDDI
。
SOAP
规范定义了
SOAP
消息的格式,以及怎样通过
HTTP
协议来使用
SOAP
,来执行
Web Services
的调用。
WSDL
(
Web Services
描述语言)用来描述
Web Services
。因为其基于
XML
,所以
WSDL
文档既是机器可阅读的,又是人可阅读的。
UDDI
(统一描述,发现和集成协议)标准定义了
Web Services
的发布与发现的方法。
从技术的角度来看,
Web Services
可以被认为是一种部署在
Web
上的对象(
Web Object
),因此,具有对象技术所承诺的所有优点;同时,
Web Services
的基石是以
XML
为主的、开放的
Web
规范技术,因此,具有比任何现有对象技术更好的开放性。
2. Duwamish
中的
CatalogService Web Service
(1)CatalogServer.asmx文件中仅包含一行代码:
<%@ WebService Language="c#" Codebehind="CatalogService.cs" Class="Duwamish7.Web.Service.CatalogService" %>
(
2
)
CatalogService.cs
代码隐藏文件包含实现
web service
的代码:
CatalogService Web
服务实现
GetBooksByTopic
和
GetBooksByTopicSecure Web
方法,返回值为
DataSet
类型(支持
XML
编码和序列化)。
Web Service
发布的上述
Web
方法均都有
WebMethodAttribute
。
WebMethodAttribute
向使用
ASP.NET
创建的
XML Web services
中的某个方法添加此特性后,就可以从远程
Web
客户端调用该方法。
另外还有一些辅助的
class
和方法(调用
BusinessFacade tier
),代码比较简单。
(
3
)
Web.config
配置文件
<webServices>
元素:可以配置使用
ASP.NET
创建的
XML Web services
的设置。
3. Summary
Web Services
不仅可用于异构平台的相互集成,也是分布式应用开发的一种技术。
Microsoft
在推
.Net Framework
时,尽心尽力吹捧这项技术,并冠以
XML Web Services
。不过由于
Web Services
的性能不好的问题,感觉目前在企业内部应用并不多,估计还不及
.Net Remoting
技术的应用。
Microsoft
还有一个
Web Services
的增强软件开发包:
Web Services Enhancements (WSE) Version 2.0
,主要提供如下特性:安全特性(数字签名和加密),消息路由,消息附件等等,从
Reference 1
可以下载。
现在,
Web Services
方面的相关规范很多,如
WS-Security, WS-Policy, WS-Trust, WS-SecureConversation……
,令人目不暇接,在不断地向前发展。从另外一个方面也表示,
Web Services
技术目前在企业应用方面还不够成熟。
References:
1, MSDN, Web Services Enhancements,
http://msdn.microsoft.com/webservices/building/wse/default.aspx
2, MSDN, Duwamish 7.0