OS X Keychain 的面向对象封装 WaxSealCore

码农软件 · 软件分类 · 其他开发相关 · 2019-10-21 10:14:49

软件介绍

什么是 WaxSealCore

WaxSealCore 是一个受 Cocoa 设计影响的代码库,由 @开源中国真理部部长 用 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 这类强大的功能),要么年代久远。所以决定自己写一个全特性的封装,而不仅是限于存取密码这种简单的功能。

Keychain Services vs. WaxSealCore

@红薯 说得好,框架的作者们不要总吹嘘自己的框架多么好用,而是要看你的框架能够实实在在地为开发者节省多少代码,所以用两个功能来比较一下 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 许可证下发布,你可以在这里获取源码,自由修改或重新分发源代码。如果不想自己编译代码,可以在这里获取到我用我的开发者证书签名的二进制框架包。

Next Step

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

获取 WaxSealCore

联系作者

如果你有任何问题,可以我发邮件 dG9yaW5Aa3dvay5pbQ==(base64ed)。

本文地址:https://codercto.com/soft/d/17229.html

The Art and Science of CSS

The Art and Science of CSS

Jonathan Snooks、Steve Smith、Jina Bolton、Cameron Adams、David Johnson / SitePoint / March 9, 2007 / $39.95

Want to take your CSS designs to the next level? will show you how to create dozens of CSS-based Website components. You'll discover how to: # Format calendars, menus and table of contents usin......一起来看看 《The Art and Science of CSS》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

SHA 加密
SHA 加密

SHA 加密工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具