WaxSealCore 1.0 发布, OS X 下面向对象的 Keychain 编程接口 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
NSTongG

WaxSealCore 1.0 发布, OS X 下面向对象的 Keychain 编程接口

  •  
  •   NSTongG 2015 年 3 月 2 日 1826 次点击
    这是一个创建于 4070 天前的主题,其中的信息可能已经有所发展或是发生改变。

    什么是 WaxSealCore

    WaxSealCore 是一个受 Cocoa 设计影响的 OOP 框架,用 Objective-C 编写。其对 OS X Keychain Services API 进行了面向对象封装,使得 Mac 开发者更容易地将 Keychain 机制融入到自己的 app 中。相对于 Apple 官方的 Keychain Services API 来说:

    1. 完全面向对象
    2. API 风格和 Cocoa 非常接近,熟练的 Mac 开发者可以迅速上手
    3. 支持基于 Unicode 字符搜索密码项
    4. 详尽的文档支持

    什么是 Keychain Services

    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 和 Keychain Services 的比较

    用两个功能来比较一下 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. 
    • 查找下面截图中的这个密码项,并且获取它的账户名,密码和注释信息(注释信息使用了 Emoji 表情,Keychain Services 无法查找)*

    使用 Keychain Services 的纯 C 接口实现:

    keychain-access

    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 : @"" } 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 = @""; } 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 : @"" } 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 = @""; } } else NSLog( @"I'm so sorry!" ); 

    上面的演示可以看到,使用 Keychain Services 费很大力气需要完成的工作,用 WaxSealCore 寥寥几行代码即可做到。除此之外,WaxSealCore 还简化了 Keychain 中的 Access Control List 机制,你可以更容易地使用 Keychain 更强大的功能。更多 API 的使用方式,可以参考我正在维护的一个 Wiki,欢迎任何人来编辑这个 wiki 页面。

    WaxSealCore 是自由软件,在 MIT 许可证下发布,你可以在这里获取源码,自由修改或重新分发源代码。如果不想自己编译代码,可以在这里获取到我用我的开发者证书签名的二进制框架包。

    Next Step

    Keychain Services 可不仅仅能够存取普通密码,同时还能够存取数字证书(digital certificates),私钥(private keys)等私密数据,WaxSealCore 下一个版本就要提供对跟 Keychain Services 同处于 Security.framework 框架中的 Certificate, Key, and Trust Services API 的封装,将融合对数字证书,对称密钥和非对称密钥的存取与操作,敬请期待。

    获取 WaxSealCore

    联系作者

    如果你有任何问题:

    • 给我发邮件 [email protected]
    • 在 Twitter 上 DM 我: @NSTongG
    • 如果你使用 S/MIME 加密邮件,可以在这里获取我的 S/MIME 证书以及和其相关联的中间证书然后给我发送加密邮件
    • 如果你使用 GnuPG,可以给我发送 PGP 加密邮件,我的 GnuPG 公钥是 0x5604FA90,你可以在公钥服务器上 retrieve
    第 1 条附言    2015 年 3 月 2 日
    帖子里的代码居然自动换行,需要详细读代码的可以直接到这里看:http://bit.ly/1aIkvef,用的是 Gist 贴的代码,排版比上面的帖子更易读。
    目前尚无回复
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5661 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 148ms UTC 06:31 PVG 14:31 LAX 23:31 JFK 02:31
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86