WaxSealCore 是一个受 Cocoa 设计影响的代码库,由 @开源中国真理部部长 用 Objective-C 编写。其对 OS X Keychain Services API 进行了面向对象封装,使得 Mac 开发者更容易地将 Keychain 机制融入到自己的 app 中。相对于 Apple 官方的 Keychain Services API 来说:
完全面向对象
API 风格和 Cocoa 非常接近,熟练的 Mac 开发者可以迅速上手
支持基于 Unicode 字符搜索密码项
详尽的文档支持
OS X 和 iOS 开发者对 “钥匙串 API”(Keychain Services API,为消歧义,下文都使用英文名称)应该都有所耳闻,计算机用户总是必须管理许多用户 ID 和密码,比如在浏览器中的 Twitter,Facebook,OSChina.net 等网站,以及 Evernote,Skype,Telegram 等桌面和移动 app 的登录密码。这些服务在你能够使用之前都需要通过密码来验证使用者的身份。因为密码繁杂,所以很多用户总是通过起一个非常简单非常容易记住的密码,并且为多个服务使用相同的密码来应付这件事(甚至将密码写在随手能够找到的小纸条上的也大有人在)。这些做法都大大削弱了密码的安全性。
所以在 OS X 和 iOS 中有一个被称为 Keychain 的机制(平时你可以通过 OS X 自带的 Keychain Access 应用访问系统中的 Keychain),Keychain 是一种具有特殊格式的文件类型(.keychain 文件),其是一个安全的加密容器,其本身可以使用一个主密码(master password)进行锁定,除了密码的拥有人,没有人能够访问这个加密容器中的任何内容。OS X 和 iOS 用户在访问一个新的网站时,就会被询问是否要保存网站的密码,以便下次自动登录,当用户点击“保存”时,用户的密码就是被保存到这个加密容器内,Keychain 会对你输入的密码进行高强度的加密,然后存储在其中,下次访问时通过解密密码既可以实现自动登录。
Keychain Services 是 Keychain 机制的编程接口。OS X/iOS 开发者在开发应用时,只需要调用这套中的函数,就可以将自己的应用中用到的密码存储到 OS X/iOS 的 Keychain 中,下次需要使用密码时可以直接从 Keychain 中进行获取而不必每次都让用户重新输入。除此之外,对于 Mac 开发者来说,你的应用还可以和其他应用共享同一个服务的密码。Keychain Services 是一个很方便的 API,它无需开发者自己实现一套密码管理机制。
事实上,OS X 版的 Firefox 和 Thunderbird 就有一个广为诟病的问题,就是它们都使用自己实现的密码管理器而不使用 Keychain,这有两个弊端:
OS X 用户习惯使用 Keychain 并建立了信任。如果提供自己的密码管理器,那么用户对它的信任度跟对你的信任度是一样的,一般来说不如他们对 Apple 公司的信任度。
用户不能在你的应用程序之外访问密码。例如,Mac 版的 Chrome,Safari 和 Opera 就都能够共享 Web 的登录资料,因为它们都使用 Keychain,并且用户可以用 Keychain Access 应用来修改他们看到的密码。
-- David Chisnall, Cocoa Programming Developer's Handbook
上面只是简单介绍了一下 Keychain 机制和它的 API,它们的功能远不止存取密码这么简单,只不过这些功能是最常用到的。Keychain Services 这套 API 很强大,但是缺点就是,它的接口是纯 C 的,丑陋,复杂,并且因为它是基于 Core Foundation 的,所以需要你手动管理内存(不像 Cocoa/Cocoa-Touch 可以利用引用计数和自动释放池),所以极易产生 bug。再加上 Keychain Services 的文档很古老,有很多错误都会无故地增大学习曲线,所以,最终,我实在受够它了,懒惰是程序员得美德,于是我找了一些开源的 Objective-C wrapper,这些 wrapper 虽然简化了使用,但是功能上要么太简陋(只能存取 generic password 和 Internet password,而没有实现 Access Control List 这类强大的功能),要么年代久远。所以决定自己写一个全特性的封装,而不仅是限于存取密码这种简单的功能。
@红薯 说得好,框架的作者们不要总吹嘘自己的框架多么好用,而是要看你的框架能够实实在在地为开发者节省多少代码,所以用两个功能来比较一下 WaxSealCore 和纯 C 的 Keychain Services。
使用一个显示指定的密码常见一个空的 Keychain
使用 Keychain Services 的纯 C 接口实现:
OSStatus resultCode = errSecSuccess; SecKeychainRef secEmptyKeychain = NULL; NSURL* URL = [ [ [ NSBundle mainBundle ] bundleURL ] URLByAppendingPathComponent: @"EmptyKeychainForWiki.keychain" ]; char* passphrase = "waxsealcore"; // Create an empty keychain with given passphrase resultCode = SecKeychainCreate( URL.path.UTF8String , ( UInt32 )strlen( passphrase ) , ( void const* )passphrase , ( Boolean )NO , NULL , &secEmptyKeychain ); NSAssert( resultCode == errSecSuccess, @"Failed to create new empty keychain" ); resultCode = SecKeychainDelete( secEmptyKeychain ); NSAssert( resultCode == errSecSuccess, @"Failed to delete the given keychain" ); if ( secEmptyKeychain ) // Keychain Services is based on Core Foundation, // you have to manage the memory manually CFRelease( secEmptyKeychain );
使用 WaxSealCore 实现:
NSError* error = nil; // Create an empty keychain with given passphrase WSCKeychain* emptyKeychain = [ [ WSCKeychainManager defaultManager ] createKeychainWithURL: [ [ [ NSBundle mainBundle ] bundleURL ] URLByAppendingPathComponent: @"EmptyKeychainForWiki.keychain" ] passphrase: @"waxsealcore" becomesDefault: NO error: &error ]; // You have no need for managing the memory manually, // emptyKeychain will be released automatically.
查找下面截图中的这个密码项,并且获取它的账户名,密码和注释信息(注释信息含有中文,Keychain Services 无法进行查找)
使用 Keychain Services 的纯 C 接口实现:
OSStatus resultCode = errSecSuccess; // Attributes that will be used for constructing search criteria char* label = "secure.imdb.com"; SecProtocolType* ptrProtocolType = malloc( sizeof( SecProtocolType ) ); *ptrProtocolType = kSecProtocolTypeHTTPS; SecKeychainAttribute attrs[] = { { kSecLabelItemAttr, ( UInt32 )strlen( label ), ( void* )label } , { kSecProtocolItemAttr, ( UInt32 )sizeof( SecProtocolType ), ( void* )ptrProtocolType } }; SecKeychainAttributeList attrsList = { sizeof( attrs ) / sizeof( attrs[ 0 ] ), attrs }; // Creates a search object matching the given list of search criteria. SecKeychainSearchRef searchObject = NULL; if ( ( resultCode = SecKeychainSearchCreateFromAttributes( NULL , kSecInternetPasswordItemClass , &attrsList , &searchObject ) ) == errSecSuccess ) { SecKeychainItemRef matchedItem = NULL; // Finds the next keychain item matching the given search criteria. while ( ( resultCode = SecKeychainSearchCopyNext( searchObject, &matchedItem ) ) != errSecItemNotFound ) { SecKeychainAttribute theAttributes[] = { { kSecAccountItemAttr, 0, NULL } , { kSecCommentItemAttr, 0, NULL } }; SecKeychainAttributeList theAttrList = { sizeof( theAttributes ) / sizeof( theAttributes[ 0 ] ), theAttributes }; UInt32 lengthOfPassphrase = 0; char* passphraseBuffer = NULL; if ( ( resultCode = SecKeychainItemCopyContent( matchedItem , NULL , &theAttrList , &lengthOfPassphrase , ( void** )&passphraseBuffer ) ) == errSecSuccess ) { NSLog( @"\n==============================\n" ); NSLog( @"Passphrase: %@", [ [ [ NSString alloc ] initWithBytes: passphraseBuffer length: lengthOfPassphrase encoding: NSUTF8StringEncoding ] autorelease ] ); for ( int _Index = 0; _Index < theAttrList.count; _Index++ ) { SecKeychainAttribute attrStruct = theAttrList.attr[ _Index ]; NSString* attributeValue = [ [ [ NSString alloc ] initWithBytes: attrStruct.data length: attrStruct.length encoding: NSUTF8StringEncoding ] autorelease ]; if ( attrStruct.tag == kSecAccountItemAttr ) NSLog( @"IMDb User Name: %@", attributeValue ); else if ( attrStruct.tag == kSecCommentItemAttr ) NSLog( @"Comment: %@", attributeValue ); } NSLog( @"\n==============================\n" ); } SecKeychainItemFreeContent( &theAttrList, passphraseBuffer ); CFRelease( matchedItem ); } } if ( ptrProtocolType ) free( ptrProtocolType ); if ( searchObject ) CFRelease( searchObject );
使用 WaxSealCore 实现:
只需一个方法的调用即可实现:
NSError* error = nil; WSCPassphraseItem* IMDbLoginPassphrase = ( WSCPassphraseItem* )[ [ WSCKeychain login ] findFirstKeychainItemSatisfyingSearchCriteria: @{ WSCKeychainItemAttributeLabel : @"secure.imdb.com" , WSCKeychainItemAttributeProtocol : WSCInternetProtocolCocoaValue( WSCInternetProtocolTypeHTTPS ) , WSCKeychainItemAttributeComment : @"这是一个用于演示 WaxSealCore 的密码项" } itemClass: WSCKeychainItemClassInternetPassphraseItem error: &error ]; // WaxSealCore supports Unicode-based search, so you can use Emoji or Chinese in your search criteria. // One step. So easy, is not it?
打印账户名,密码,和注释,并且更改注释内容:
if ( IMDbLoginPassphrase ) { NSLog( @"==============================" ); // Use the `account` property NSLog( @"IMDb User Name: %@", IMDbLoginPassphrase.account ); // Use the `passphrase` property NSLog( @"Passphrase: %@", [ [ [ NSString alloc ] initWithData: IMDbLoginPassphrase.passphrase encoding: NSUTF8StringEncoding ] autorelease ] ); // Use the `comment` property NSLog( @"Comment: %@", IMDbLoginPassphrase.comment ); NSLog( @"==============================" ); // -setComment: IMDbLoginPassphrase.comment = @"IMDb Passphrase"; } else NSLog( @"I'm so sorry!" );
简单地进行批量搜索:
// Find all the Internet passphrases that met the given search criteria NSArray* passphrases = [ [ WSCKeychain login ] // Batch search findAllKeychainItemsSatisfyingSearchCriteria: @{ WSCKeychainItemAttributeLabel : @"secure.imdb.com" , WSCKeychainItemAttributeProtocol : WSCInternetProtocolCocoaValue( WSCInternetProtocolTypeHTTPS ) , WSCKeychainItemAttributeComment : @"IMDb Passphrase" } itemClass: WSCKeychainItemClassInternetPassphraseItem error: &error ]; if ( passphrases.count != 0 ) { for ( WSCPassphraseItem* _Passphrase in passphrases ) { NSLog( @"==============================" ); NSLog( @"IMDb User Name: %@", IMDbLoginPassphrase.account ); NSLog( @"Passphrase: %@", [ [ [ NSString alloc ] initWithData: IMDbLoginPassphrase.passphrase encoding: NSUTF8StringEncoding ] autorelease ] ); NSLog( @"Comment: %@", IMDbLoginPassphrase.comment ); NSLog( @"==============================" ); _Passphrase.comment = @"这是一个用于演示 WaxSealCore 的密码项"; } } else NSLog( @"I'm so sorry!" );
上面的演示可以看到,使用 Keychain Services 费很大力气需要完成的工作,用 WaxSealCore 寥寥几行代码即可做到。除此之外,WaxSealCore 还简化了 Keychain 中的 Access Control List 机制,你可以更容易地使用 Keychain 更强大的功能。更多 API 的使用方式,可以参考我正在维护的一个 Wiki,欢迎任何人来编辑这个 wiki 页面。
WaxSealCore 是自由软件,在 MIT 许可证下发布,你可以在这里获取源码,自由修改或重新分发源代码。如果不想自己编译代码,可以在这里获取到我用我的开发者证书签名的二进制框架包。
Keychain Services 不仅仅能够存取普通密码,同时还能够存取数字证书(digital certificates),私钥(private keys)等私密数据,WaxSealCore 下一个版本就要提供对跟 Keychain Services 同处于 Security.framework 框架中的 Certificate, Key, and Trust Services API 的封装,将融合对数字证书,对称密钥和非对称密钥的存取与操作,敬请期待。
如果你有任何问题,可以给我发邮件 dG9yaW5Aa3dvay5pbQ==(base64ed)。
WaxSealCore 是一个受 Cocoa 设计影响的 OOP 框架,由 @开源中国真理部部长 用 Objective-C 编写。其对 OS X Keychain Services API 进行了面向对象封装,使得 Mac 开发者更容易地将 Keychain 机制融入到自己的 app 中。相对于 Apple 官方的 Keychain Services API 来说: 1.完全面向对象 2.API
本文向大家介绍Javascript之面向对象--封装,包括了Javascript之面向对象--封装的使用技巧和注意事项,需要的朋友参考一下 第一步:做一个“手机的类" 第二步:考虑这个类,里需要那些类的私有属性,这里我想定义的是实例出来手机的数量 第三步:创建一个构造函数,即实例时候,对产生的新象的一个初始化,例如属性,方法的初始化;在这个例子中,每一个手机都会有颜色,大小,价格属性.这里的构造函
封装 封装:是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。 好处: 将变化隔离。 便于使用。 提高重用性。 提高安全性。 封装原则: 将不需要对外提供的内容都隐藏起来。 把属性都隐藏,提供公共方法对其访问。如,getXXX,setXXX。 示例: /** 人: 属性:年龄 行为:说话 */ class Person{
本文向大家介绍js实现对ajax请求面向对象的封装,包括了js实现对ajax请求面向对象的封装的使用技巧和注意事项,需要的朋友参考一下 AJAX 是一种用于创建快速动态网页的技术。通过在后台与服务器进行少量数据交换,AJAX 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。 在js中使用ajax请求一般包含三个步骤: 1、创建X
面向对象是一种符合人类思维习惯的编程思想。现实生活中存在各种形态不同的事物,这些事物之间存在着各种各样的联系。在程序中使用对象来映射现实中的事物,使用对象的关系来描述事物之间的联系,这种思想就是面向对象。 提到面向对象,自然会想到面向过程,面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一一实现,使用的时候依次调用就可以了。面向对象则是把构成问题的事务按照一定规则划分为多个独立的对象,
本文向大家介绍Python面向对象封装操作案例详解 II,包括了Python面向对象封装操作案例详解 II的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了Python面向对象封装操作。分享给大家供大家参考,具体如下: 士兵突击案例 身份运算符 封装 封装 是面向对象编程的一大特点 面向对象编程的 第一步 —— 将 属性 和 方法 封装 到一个抽象的 类 中 外界 使用 类 创建 对象,然后
面向对象 程序设计方法 程序设计描述系统如何通过程序来实现的过程,其为一种设计方法与语言实现无关。常见的设计方法有面向流程与面向对象。 面向过程 以程序的过程为中心,采用自定而下逐步细化的方法来实现。常见的面向过程语言有 C、Fortran、Pascall。 面向对象 将对象作为程序的基本单元,将程序分解为数据和操作的集合。常见的面向过程语言有 smalltalk(也是 Objective-C 的
面向对象 三大特性是:封装,继承,多态 所谓封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。封装是面向对象的特征之一,是对象和类概念的主要特性。 简单的说,一个类就是一个封装了数据以及操作这些数据的代码的逻辑实体。在一个对象内部,某些代码或某些数据可以是私有的,不能被外界访问。通过这种方式,对象对内部数据提供了不同级别的保护,以防
之前的文章都是使用Sublime Text来编写 Python 的,主要是为了更好的熟悉和了解 Python ,可是开发效率不高,也不方便,从这章开始,改为使用 Pycharm 了,在之前的篇节集成开发环境(IDE): PyCharm中介绍了 PyCharm ,如果如要激活软件可以通过授权服务器来激活,具体看这个网址。JetBrains激活(http://www.imsxm.com/jetbrai