课程内容
Ø 加密和解密
Ø 密码输入框
Ø 值转换
Ø DataTimeOffset
Ø 可观察集合
Ø INotifyPropertyChanged事件
Passwords & Secrets是一个类似记事本风格的应用,它有一个主人保护密码。因此,我们可以用它来存储大量的密码和一些不希望落入他人之手的秘密。当然,它的记事功能是一流的,支持:
Ø 自动保存,使得速记变得快速而简单。
Ø 提供每条笔记的快速预览。
Ø 可自定义每条笔记的背景色、前景色和字体大小。
Ø 可通过Email发送笔记。
此外,每条笔记的数据会通过256位的AES加密算法进行加密,确保数据的私密性。这个加密过程也是建立在主人保护密码的基础上的,所以,用户千万不能忘记各自的保护密码。没有它,应用程序就没有方法获取数据,出于安全考虑,应用程序不会存储该密码。
为了使管理主人密码尽量简单,Passwords & Secrets支持忘记密码提示。应用程序也允许改变主人密码(这也是以知道当前密码为前提的)。
为什么我需要对隔离存储空间中的数据进行加密?不是只有应用程序才能获取吗?
除非Windows Phone OS存在漏洞,其他应用程序是无法读取本应用程序的隔离存储空间的。而且,也没有人可以远程获取隔离存储空间的数据。但是,如果有能力的黑客从物理上攻破了你的设备,那么他们当然可以读取存储在其中的数据。数据加密以后,黑客们实际上很难再读懂数据的意义了。
Basic Cryptography
Silverlight的System.Security.Cryptography命名空间中包含了一些具备加密功能的函数。本应用程序封装了这些函数,形成了一个简单易用的Crypto类。该类包含两个简单的方法:Encrypt 和 Decrypt,它们利用密钥和解密/加密的数据来进行解密/加密的操作。
Encrypt 和 Decrypt都调用了GetAlgorithm helper方法(在文件的最后有定义)。返回的算法可以创建encryptor 或者 decryptor,它会被传送给crypto stream,从而完成加密/解密过程。
在Encrypt中,输入的字符串被转换成为UTF8编码的字符。然后,这些字符被写入crypto stream,来完成加密过程。通过ToArray方法,就可以在crypto stream使用的内存中获取加密后的字符。这些字符通过Base64编码转换为stream,这是一种代表字符串中二进制数据的常用方法。
Decrypt从Base64编码的字符串开始,将其转换为写入crypto stream的字符。然后,使用相应的ToArray方法将解密后的UTF8编码的数据转换为字符串。
对输入字符串使用SHA256(256位Secure Hash Algorithm)哈希加密,在其前面添加一个随机的“salt”。这有时候被称为salted hash。本应用程序调用这个方法来存储密钥的salted hash,而不是密钥本身,来确保安全性。毕竟,如果黑客得到了隔离存储空间中的数据,而密钥是以普通文本的形式存放,那么,对数据加密就显得毫无意义了。
GenerateNewSalt方法随机产生一个指定长度的byte数组。与其他应用程序中使用Random类不同,该方法使用了一种高度伪随机数产生器RNGCryptoServiceProvider,更适用于加密应用。正如下一节所示,就在应用程序第一次运行时,调用一次该方法。它将随机产生的salt存放于隔离存储空间中,用于随后所有的加密、加密和哈希算法。
GetAlgorithm方法用于构造唯一内置的加密算法,即AesManaged,一种AES对称加密算法。该算法初始化时,需要一个密钥和一个初始化向量(IV),因此,它由Rfc2898DeriveBytes实例来处理。
Rfc2898DeriveBytes是基于密码的密钥派生功能- PBKDF2的实现。它使用密码和一个随机的salt值,将基于SHA1哈希功能的随机函数执行很多次(默认是1000次)。这就使得密码更难被破译。
AesManaged中KeySize属性的默认值也是它所支持的最大值:256。这意味着密钥的长度是256,也就是为什么这个过程被称为256比特的加密。
Salt in Cryptography
使用salt可以带来一些好处,特别是salt可以用来保密,使得黑客破译的速度变慢。在本应用中,虽然salt值必须传递给Rfc2898DeriveBytes的构造函数,但是它并没有改变,因为salt必须和加密后的数据一同存放。Hash函数中的salting也是一样的道理。这对于管理多个密码的服务器来说,是一种好的方法(因此,以字典为基础的攻击必须为每个用户重新产生数据,即使用户的密码相同,它们的hash值也不同),这在本应用程序中得到了较好的体现。
The LoginControl User Control
有了Crypto类,我们可以创建一个登陆控件,用来处理所有应用程序密码输入的交互。LoginControl用户控件有3种不同的模式:
Ø 新用户模式:用户第一次使用时,必须选定主控密码。
Ø 普通登陆模式:用户登录时,必须输入之前选定的密码。
Ø 修改密码模式:在用户输入当前的密码以后,可以修改其主控密码。
注意:
Ø 该控件在需要输入密码的地方使用了password box。password box和text box类似,只是每个字符都是以小圆点显示(在你输入字符一段时间以后将会看到)。这与内置应用的密码输入行为相匹配。与Text属性不同,它使用的是Password属性。
Ø 第17章“Pick a Card Magic Trick”中的不透明度屏蔽技巧用来给出具有当前主题强调色的锁的图案。它的不透明度为50%,所以有点融入了背景中。
注意:
该列表使用了以下在Settings.cs文件中定义的一些设置:
Crypto类使用的Rfc2898DeriveBytes方法中,salt的长度至少是8个字节。本应用程序调用GenerateNewSalt,产生salt的长度是16个字节。
在普通登录模式中,该控件必须判断输入的密码是否正确。但是应用程序并没有存储用户密码。然而,它存储了密码的salted hash值。因此,为了验证输入的密码,应用程序调用相同的Crypto.Hash函数,并检查它与存储的值是否一致。
虽然未加密的密码没有被存储,但是应用程序将它保存在RAM中,所以应用程序能够解密用户保存的数据,并且对新数据进行加密。这些在CurrentContext类中完成,其定义如CurrentContext.cs所示。
在修改密码模式中,旧密码起到了非常重要的作用。因为那些使用旧密码加密的数据必须通过旧密码来解密,然后再使用新密码进行加密。否则的话,原来存储的数据会无法读取,因为无法使用新密码来解密旧密码加密的数据。
在Close中,每个password box的Password属性被设置为一个空字符串,而非空,因为如果设置为空的话,Password属性会跑出一个异常。
我们可以发现,LoginControl并不是一个通用的控件,而是为本应用定制的(虽然在更改密码过程中,通过给用户提供钩子来完成数据的重新加密并不是一件难事)。如本章的下面三节所示,它被使用在三个不同的地方。
The Main Page
本应用的主页面包含了用户笔记的列表,如图21.2所示。点击每条记录,可以对其进行浏览和编辑。应用程序栏上的按钮使得我们可以增加新的记录。但在列表形成并显示之前,用户必须输入正确的密码。在用户没有登录的情况下,LoginControl除了header以外,会占据整个页面,应用程序栏中也没有了新增记录的按钮。
图21.2 应用程序主页面
注意:
应用程序标题中的“&”符号使用XML编码,避免XAML解析错误。
LoginControl用户控件作为本页面的一部分,而非独立的登录页面,这样是为了确保流畅的导航体验。当用户打开应用程序,登录,看到主页面上的数据,按硬件“Back”按钮,应该退出应用程序,而非回到登录页面!
LoginControl并不是简单地通过视觉掩盖来保护数据的,从背后的代码中我们可以看到,只有用户登录了以后,数据才会显示出来。而且,在用户登录之前,应用程序是无法显示数据的,因为对存储的数据进行解密的话,需要正确的密码。
list box中的条目模板与每个记录中的多个属性进行绑定(用于显示记录的Note类在本章的后面部分讲述)。与Modified属性的绑定使用了值转换器,它用来改变结果的显示。值转换器会在下面讲述。
Value Converters
在数据绑定中,值转换器可以将源数据转换为一个完全不同的目标类型,使得我们可以在不丢失数据绑定好处的情况下,嵌入自定义逻辑。
值转换器被经常用来在源数据和目标数据类型之间进行转换。比如,我们可以使用一些nonbrush的数据源来改变元素的背景色或者前景色,就像Microsoft Excel中的条件格式一样。另外一个例子,Silverlight for Windows Phone Toolkit中的toggle switch控件具有一个称为OnOffConverter的值转换器,它把非空的布尔类型值IsChecked转变为“On” 或者 “Off”为默认内容的字符串。
在Passwords & Secrets应用中,我们想要对每条记录的Modified属性显示做轻微的自定义。它的数据类型是DateTimeOffset,如果没有值转换器,它的显示效果如下:
12/11/2012 10:18:49 PM -08:00
-08:00代表时区,它表示与国际标准时间(UTC)之间的偏差。
我们使用的自定义值转换器省略了时区信息和秒,因为那些信息我们不需要。因此显示效果如下:
12/11/2010 10:18 PM
即使Modified属性是DateTime类型,而非DateTimeOffset,为了将秒从字符串中省去,值转换器仍旧是有用的。
DateTime和DateTimeOffset这两种数据类型有何区别?
DateTime是指与任何时区无关的逻辑时间点,而DateTimeOffset是指与UTC时间存在偏差的实际时间点。在本应用中,DateTimeOffset更适合给每条记录的修改时间使用,因为即使用户接下来会到另一个时区,他们也不希望时间点会改变。但是,在前一章“Alarm Clock”中,提醒时间使用了DateTime类型。假设你在一个时区设置了闹钟,但是在闹钟要响起的时候,你却在另一个时区。例如,我们设置闹钟在早晨8点,那么,无论我们在哪个时区,我们都希望闹钟准时响起。
对于大多数情况来说,使用DateTimeOffset要优于DateTime。但是,相对于DateTime来说,.NET Framework引入DateTimeOffset要晚几年,所以命名已经被使用了(类的设计者拒绝称之为DateTime2 或者 DateTimeEx)。幸运的是,这些数据类型的用户可以经常交换地使用它们。
为了创建一个值转换器,我们必须写一个实现System.Windows.Data 中的IValueConverter接口的类。该接口具有两个简单的方法:一个是Convert,它被传入必须转换为目标实例的源实例;另一个是ConvertBack,它正好相反。列表21.7包含了列表21.6中使用的DateConverter值转换器的实现。
每次源值改变时,Convert方法就会被调用。传入DateTimeOffset值,返回短格式的日期和时间字符串。ConvertBack方法不是必须的,因为它只在双向数据绑定中使用。因此,它返回一个虚值。
Additional Data for Value Converters
IValueConverter方法传入一个参数和一个本地化语言类型。默认情况下,参数设置为空,本地化语言设置为目标元素的Language属性值。但是,绑定的用户仍旧可以通过Binding.ConverterParameter 和 Binding.ConverterCulture来控制这两个值。
传递给值转换器类的ConverterParameter参数可以是任意的用户数据,就像元素的Tag属性一样。ConverterCulture可以设置为因特网工程任务推动小组(IETF)语言标签(如en-US 或者 ko-KR),值转换器接收合适的CultureInfo对象。在DateConverter中,ToString方法已经遵守了当前的语言规则,所以没有必要再做本地化相关的工作。
与基本的格式转换不同,值转换器是把用户逻辑融入数据绑定过程的关键。在原始数据进行显示之前想要对其进行某种改变,或者基于原始数据的目标更新,这些都可以通过实现了IValueConverter的类来轻松实现。
人们创建的最常见的值转换器是Boolean-to-Visibility(常常被称为BooleanToVisibilityConverter),它可以将数据在可视性枚举值和布尔值(或者是非空布尔值)进行转换。在一个方向上,true被转换为Visible,而false和null被映射为Collapsed。另一个方向上,Visible被映射为true,而Collapsed被映射为false。这对于以下的情况比较有用,即将一个XAML控件元素的可视性与另一个不相关的XAML控件元素的状态关联起来。比如,下面的XAML片段实现了一个Show Button check box,而不需要使用程序代码(除值转换器之外)。
在这种情况下,button只有在check box的IsChecked属性为true时可见。
图21.3 应用程序栏展开页面
注意:
如图21.3所示,应用程序栏的第一个menu起到下面的作用:在用户没有登录的情况下给出密码提示,在用户已经登录的情况下给出密码修改页面。
正如之前所提到的,作为list box 控件数据内容的NotesList集合与普通的集合不同(如List<Note>),它是一个可观察的集合。
在发生任何改变时(比如新增条目或者删除条目),可观察的集合会触发一个CollectionChanged事件。数据绑定自动将此消息发送给目标控件(本页面的list box),从而保持一致性。
尽管可观察的集合可以处理list box控件中条目的增加和删除,但是每个Note条目必须在其属性改变时发送通知,确保它反应在数据绑定的list box中。如列表21.9所示,Note通过实现INotifyPropertyChanged接口来完成此功能。
INotifyPropertyChanged只有一个成员- PropertyChanged事件。如果其中任何一个属性在一个合适的时间被改变,那么这个事件就会被触发,数据绑定负责目标控件的数据刷新。
被触发的PropertyChanged事件由OnPropertyChanged helper方法来处理。为了避免bug,将一个handler变量赋值给event handler字段。否则,如果当前线程在检查handler是否为空并调用它时,另一个线程对其进行删除操作,那么NullReferenceException异常便会抛出(在没有listener的情况下,event handler字段变为空)。
Dependency属性自动实现change-notification机制,其特性与INotifyPropertyChanged类似,但略有不同。因此,在使用Dependency属性时,不需要额外的代码,就可以完成与数据绑定配合的change notification。
某些属性会因为额外的属性发生改变而触发PropertyChanged事件。比如,当EncryptedContent被设置为一个新值的时候,PropertyChanged会因为readonly Title属性而被触发。这是因为Title的值是基于EncryptedContent的值,所以EncryptedContent的改变会导致Title的改变。
可观察集合通过实现INotifyCollectionChanged接口来完成出色的功能,INotifyCollectionChanged接口与INotifyPropertyChanged类似,包含了一个CollectionChanged事件。但是,让用户自己写collection 类,并且实现INotifyCollectionChanged接口,而不是单纯地使用ObservableCollection类,这种情况也是非常少见的。
The Details Page
详细页面如图21.4所示,当用户点击主页面上的list box控件中的记录时,便会弹出。该页面显示了一条记录的全部内容,允许用户对其进行编辑、删除或者利用其内容发送邮件。另外,通过它还可以进入每条记录的设置页面,用来控制字体的颜色和大小。在浏览模式中,应用程序栏可见。在编辑模式中,应用程序栏隐藏。
该页面的text box基本占据了整个屏幕,它已经被用户自定义了,我们移除了它的边界,无论text box是否获得焦点,其背景色一直可见。这种风格来源于
%ProgramFiles%\Microsoft SDKs\Windows Phone\v7.0\Design\System.Windows. xaml,不是新创建的。
注意:
该页面使用了navigatingFrom标志来检查页面是否处于被导航的状态。那是因为OnNavigatedFrom后,Loaded事件会被再次触发,此时如果将焦点给text box的话,会导致屏幕键盘闪现。
本页面的设置页面的代码会在下一章详述,因为它和本应用程序使用的方式相同。
在导航到别的页面时,页面的Loaded事件被错误触发!这是当前Windows Phone版本的一个bug。为了避免这种闪屏或其他的问题,考虑在OnNavigatedFrom中设置一个标志,在Loaded事件中进行检查。那样的话,我们就可以确保页面加载逻辑只在页面加载时执行。
本文转自施炯博客园博客,原文链接:http://www.cnblogs.com/dearsj001/archive/2012/08/09/101App4WP7_Passwords.html,如需转载请自行联系原作者