利用钥匙串,在应用里保存用户密码的方法

https://github.com/ldandersen/scifihifi-iphone/tree/master/security

//
//  SFHFKeychainUtils.h
//
//  Created by Buzz Andersen on 10/20/08.
//  Based partly on code by Jonathan Wight, Jon Crosby, and Mike Malone.
//  Copyright 2008 Sci-Fi Hi-Fi. All rights reserved.
//
//  Permission is hereby granted, free of charge, to any person
//  obtaining a copy of this software and associated documentation
//  files (the "Software"), to deal in the Software without
//  restriction, including without limitation the rights to use,
//  copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the
//  Software is furnished to do so, subject to the following
//  conditions:
//
//  The above copyright notice and this permission notice shall be
//  included in all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
//  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
//  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
//  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
//  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
//  OTHER DEALINGS IN THE SOFTWARE.
//

#import <UIKit/UIKit.h>

@interface SFHFKeychainUtils : NSObject {
  
}

+ (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;
+ (BOOL) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error;
+ (BOOL) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;

@end

//
//  SFHFKeychainUtils.m
//
//  Created by Buzz Andersen on 10/20/08.
//  Based partly on code by Jonathan Wight, Jon Crosby, and Mike Malone.
//  Copyright 2008 Sci-Fi Hi-Fi. All rights reserved.
//
//  Permission is hereby granted, free of charge, to any person
//  obtaining a copy of this software and associated documentation
//  files (the "Software"), to deal in the Software without
//  restriction, including without limitation the rights to use,
//  copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the
//  Software is furnished to do so, subject to the following
//  conditions:
//
//  The above copyright notice and this permission notice shall be
//  included in all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
//  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
//  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
//  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
//  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
//  OTHER DEALINGS IN THE SOFTWARE.
//

#import "SFHFKeychainUtils.h"
#import <Security/Security.h>

static NSString *SFHFKeychainUtilsErrorDomain = @"SFHFKeychainUtilsErrorDomain";

#if __IPHONE_OS_VERSION_MIN_REQUIRED < 30000 && TARGET_IPHONE_SIMULATOR
@interface SFHFKeychainUtils (PrivateMethods)
+ (SecKeychainItemRef) getKeychainItemReferenceForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;
@end
#endif

@implementation SFHFKeychainUtils

#if __IPHONE_OS_VERSION_MIN_REQUIRED < 30000 && TARGET_IPHONE_SIMULATOR

+ (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {
    if (!username || !serviceName) {
        *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
        return nil;
    }
    
    SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error];
    
    if (*error || !item) {
        return nil;
    }
    
    // from Advanced Mac OS X Programming, ch. 16
  UInt32 length;
  char *password;
  SecKeychainAttribute attributes[8];
  SecKeychainAttributeList list;
    
  attributes[0].tag = kSecAccountItemAttr;
  attributes[1].tag = kSecDescriptionItemAttr;
  attributes[2].tag = kSecLabelItemAttr;
  attributes[3].tag = kSecModDateItemAttr;
  
  list.count = 4;
  list.attr = attributes;
  
  OSStatus status = SecKeychainItemCopyContent(item, NULL, &list, &length, (void **)&password);
    
    if (status != noErr) {
        *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
        return nil;
  }
  
    NSString *passwordString = nil;
    
    if (password != NULL) {
        char passwordBuffer[1024];
        
        if (length > 1023) {
            length = 1023;
        }
        strncpy(passwordBuffer, password, length);
        
        passwordBuffer[length] = ‘\0‘;
        passwordString = [NSString stringWithCString:passwordBuffer];
    }
    
    SecKeychainItemFreeContent(&list, password);
  
  CFRelease(item);
  
  return passwordString;
}

+ (void) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error {    
    if (!username || !password || !serviceName) {
        *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
        return;
    }
    
    OSStatus status = noErr;
    
    SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error];
    
    if (*error && [*error code] != noErr) {
        return;
    }
    
    *error = nil;
    
    if (item) {
        status = SecKeychainItemModifyAttributesAndData(item,
                                                    NULL,
                                                    strlen([password UTF8String]),
                                                    [password UTF8String]);
        
        CFRelease(item);
    }
    else {
        status = SecKeychainAddGenericPassword(NULL,                                     
                                           strlen([serviceName UTF8String]), 
                                           [serviceName UTF8String],
                                           strlen([username UTF8String]),                        
                                           [username UTF8String],
                                           strlen([password UTF8String]),
                                           [password UTF8String],
                                           NULL);
    }
    
    if (status != noErr) {
        *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
    }
}

+ (void) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {
    if (!username || !serviceName) {
        *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: 2000 userInfo: nil];
        return;
    }
    
    *error = nil;
    
    SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error];
    
    if (*error && [*error code] != noErr) {
        return;
    }
    
    OSStatus status;
    
    if (item) {
        status = SecKeychainItemDelete(item);
        
        CFRelease(item);
    }
    
    if (status != noErr) {
        *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
    }
}

+ (SecKeychainItemRef) getKeychainItemReferenceForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {
    if (!username || !serviceName) {
        *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
        return nil;
    }
    
    *error = nil;
  
    SecKeychainItemRef item;
    
    OSStatus status = SecKeychainFindGenericPassword(NULL,
                                                   strlen([serviceName UTF8String]),
                                                   [serviceName UTF8String],
                                                   strlen([username UTF8String]),
                                                   [username UTF8String],
                                                   NULL,
                                                   NULL,
                                                   &item);
    
    if (status != noErr) {
        if (status != errSecItemNotFound) {
            *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
        }
        
        return nil;        
    }
    
    return item;
}

#else

+ (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {
    if (!username || !serviceName) {
        if (error != nil) {
            *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
        }
        return nil;
    }
    
    if (error != nil) {
        *error = nil;
    }
  
    // Set up a query dictionary with the base query attributes: item type (generic), username, and service
    
    NSArray *keys = [[[NSArray alloc] initWithObjects: (NSString *) kSecClass, kSecAttrAccount, kSecAttrService, nil] autorelease];
    NSArray *objects = [[[NSArray alloc] initWithObjects: (NSString *) kSecClassGenericPassword, username, serviceName, nil] autorelease];
    
    NSMutableDictionary *query = [[[NSMutableDictionary alloc] initWithObjects: objects forKeys: keys] autorelease];
    
    // First do a query for attributes, in case we already have a Keychain item with no password data set.
    // One likely way such an incorrect item could have come about is due to the previous (incorrect)
    // version of this code (which set the password as a generic attribute instead of password data).
    
    NSDictionary *attributeResult = NULL;
    NSMutableDictionary *attributeQuery = [query mutableCopy];
    [attributeQuery setObject: (id) kCFBooleanTrue forKey:(id) kSecReturnAttributes];
    OSStatus status = SecItemCopyMatching((CFDictionaryRef) attributeQuery, (CFTypeRef *) &attributeResult);
    
    [attributeResult release];
    [attributeQuery release];
    
    if (status != noErr) {
        // No existing item found--simply return nil for the password
        if (error != nil && status != errSecItemNotFound) {
            //Only return an error if a real exception happened--not simply for "not found."
            *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
        }
        
        return nil;
    }
    
    // We have an existing item, now query for the password data associated with it.
    
    NSData *resultData = nil;
    NSMutableDictionary *passwordQuery = [query mutableCopy];
    [passwordQuery setObject: (id) kCFBooleanTrue forKey: (id) kSecReturnData];
  
    status = SecItemCopyMatching((CFDictionaryRef) passwordQuery, (CFTypeRef *) &resultData);
    
    [resultData autorelease];
    [passwordQuery release];
    
    if (status != noErr) {
        if (status == errSecItemNotFound) {
            // We found attributes for the item previously, but no password now, so return a special error.
            // Users of this API will probably want to detect this error and prompt the user to
            // re-enter their credentials.  When you attempt to store the re-entered credentials
            // using storeUsername:andPassword:forServiceName:updateExisting:error
            // the old, incorrect entry will be deleted and a new one with a properly encrypted
            // password will be added.
            if (error != nil) {
                *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -1999 userInfo: nil];
            }
        }
        else {
            // Something else went wrong. Simply return the normal Keychain API error code.
            if (error != nil) {
                *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
            }
        }
        
        return nil;
    }
  
    NSString *password = nil;    
  
    if (resultData) {
        password = [[NSString alloc] initWithData: resultData encoding: NSUTF8StringEncoding];
    }
    else {
        // There is an existing item, but we weren‘t able to get password data for it for some reason,
        // Possibly as a result of an item being incorrectly entered by the previous code.
        // Set the -1999 error so the code above us can prompt the user again.
        if (error != nil) {
            *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -1999 userInfo: nil];
        }
    }
  
    return [password autorelease];
}

+ (BOOL) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error 
{        
    if (!username || !password || !serviceName) 
  {
        if (error != nil) 
    {
            *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
        }
        return NO;
    }
    
    // See if we already have a password entered for these credentials.
    NSError *getError = nil;
    NSString *existingPassword = [SFHFKeychainUtils getPasswordForUsername: username andServiceName: serviceName error:&getError];
  
    if ([getError code] == -1999) 
  {
        // There is an existing entry without a password properly stored (possibly as a result of the previous incorrect version of this code.
        // Delete the existing item before moving on entering a correct one.
    
        getError = nil;
        
        [self deleteItemForUsername: username andServiceName: serviceName error: &getError];
    
        if ([getError code] != noErr) 
    {
            if (error != nil) 
      {
                *error = getError;
            }
            return NO;
        }
    }
    else if ([getError code] != noErr) 
  {
        if (error != nil) 
    {
            *error = getError;
        }
        return NO;
    }
    
    if (error != nil) 
  {
        *error = nil;
    }
    
    OSStatus status = noErr;
  
    if (existingPassword) 
  {
        // We have an existing, properly entered item with a password.
        // Update the existing item.
        
        if (![existingPassword isEqualToString:password] && updateExisting) 
    {
            //Only update if we‘re allowed to update existing.  If not, simply do nothing.
            
            NSArray *keys = [[[NSArray alloc] initWithObjects: (NSString *) kSecClass, 
                        kSecAttrService, 
                        kSecAttrLabel, 
                        kSecAttrAccount, 
                        nil] autorelease];
            
            NSArray *objects = [[[NSArray alloc] initWithObjects: (NSString *) kSecClassGenericPassword, 
                           serviceName,
                           serviceName,
                           username,
                           nil] autorelease];
            
            NSDictionary *query = [[[NSDictionary alloc] initWithObjects: objects forKeys: keys] autorelease];            
            
            status = SecItemUpdate((CFDictionaryRef) query, (CFDictionaryRef) [NSDictionary dictionaryWithObject: [password dataUsingEncoding: NSUTF8StringEncoding] forKey: (NSString *) kSecValueData]);
        }
    }
    else 
  {
        // No existing entry (or an existing, improperly entered, and therefore now
        // deleted, entry).  Create a new entry.
        
        NSArray *keys = [[[NSArray alloc] initWithObjects: (NSString *) kSecClass, 
                      kSecAttrService, 
                      kSecAttrLabel, 
                      kSecAttrAccount, 
                      kSecValueData, 
                      nil] autorelease];
        
        NSArray *objects = [[[NSArray alloc] initWithObjects: (NSString *) kSecClassGenericPassword, 
                         serviceName,
                         serviceName,
                         username,
                         [password dataUsingEncoding: NSUTF8StringEncoding],
                         nil] autorelease];
        
        NSDictionary *query = [[[NSDictionary alloc] initWithObjects: objects forKeys: keys] autorelease];            
    
        status = SecItemAdd((CFDictionaryRef) query, NULL);
    }
    
    if (status != noErr) 
  {
        // Something went wrong with adding the new item. Return the Keychain error code.
        if (error != nil) {
            *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
        }
    
    return NO;
    }
  
  return YES;
}

+ (BOOL) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error 
{
    if (!username || !serviceName) 
  {
        if (error != nil) 
    {
            *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
        }
        return NO;
    }
    
    if (error != nil) 
  {
        *error = nil;
    }
  
    NSArray *keys = [[[NSArray alloc] initWithObjects: (NSString *) kSecClass, kSecAttrAccount, kSecAttrService, kSecReturnAttributes, nil] autorelease];
    NSArray *objects = [[[NSArray alloc] initWithObjects: (NSString *) kSecClassGenericPassword, username, serviceName, kCFBooleanTrue, nil] autorelease];
    
    NSDictionary *query = [[[NSDictionary alloc] initWithObjects: objects forKeys: keys] autorelease];
    
    OSStatus status = SecItemDelete((CFDictionaryRef) query);
    
    if (status != noErr) 
  {
      if (error != nil) {
          *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
      }
    
    return NO;
    }
  
  return YES;
}

#endif

@end

时间: 2024-10-11 01:44:03

利用钥匙串,在应用里保存用户密码的方法的相关文章

iOS中使用SFHFKeychainUtils保存用户密码

iOS中使用SFHFKeychainUtils保存用户密码,有需要的朋友可以参考下. 项目中需要保存用户密码,以实现自动登录的功能.于是,研究了下iOS保存密码的方法: 1.保存用户密码的安全方法 作为一名iPhone开发者,你需要对你的用户安全负责.请问,你是怎么保存用户的密码的?直接保存到plist文件里?加密?AES? DES?能保证你的代码不被反编译拿到你的加密Key? 这也未免太不苹果了吧.我Google了一下,国内的开发者根本没有注意到这个问题. 苹果系统中有个程序叫"钥匙串&quo

移动App该如何保存用户密码(转)

原文地址:http://blog.csdn.net/hengyunabc/article/details/34623957 移动App该如何保存用户密码? 这个实际上和桌面程序是一样的. 先看下一些软件是如何保存用户密码的: 我们先来看下QQ是怎么保存密码的: 参考:http://bbs.pediy.com/archive/index.php?t-159045.html, 桌面QQ在2012的时候把密码md5计算之后,保存到本地加密的Sqlite数据库里. 再来看下手机淘宝是怎么做的: 参考:h

移动App该如何保存用户密码

移动App该如何保存用户密码? 这个实际上和桌面程序是一样的. 先看下一些软件是如何保存用户密码的: 我们先来看下QQ是怎么保存密码的: 参考:http://bbs.pediy.com/archive/index.php?t-159045.html, 桌面QQ在2012的时候把密码md5计算之后,保存到本地加密的Sqlite数据库里. 再来看下手机淘宝是怎么做的: 参考:http://blog.csdn.net/androidsecurity/article/details/8666954 手机

在非域环境中修改域用户密码的方法

前几天有个单位,新配置了一台服务器,做文件服务器,为网络中提供共享文件夹服务,该单位大约有50多个用户.服务器采用Windows Server 2008 R2操作系统,升级到Active Directory,为单位每个职工创建了一个用户名,在服务器上除了为每个用户创建一个"共享文件夹"保存个人数据外,还创建了"公共"共享文件夹,保存单位的数据,并且在公共文件夹中创建了若干子目录,通过用户权限,设置只让指定用户上传.修改其中的文件. 在开始规划的时候,是计划让单位的所

MySQL重置root用户密码的方法

本教程适用于采用Win2003.WinXP操作系统的迅美VPS和云主机产品. 当管理员忘记MySQL密码怎么办?屡次输入密码,仍然提示错误,网站无法正常运行,数据库也无法管理,管理员束手无策. 网站程序或MySQL管理软件连接MySQL服务器时密码错误,会出现"1045 - Access denied for user 'root'@'localhost'(using password:YES)"的错误提示,如下图: 当确认已经忘记MySQL密码,则可以通过以下方案重置root用户密码

MySQL重置root用户密码的方法【亲测可用】

1. 报错截图 2.当确认已经忘记MySQL密码,则可以通过以下方案重置root用户密码.双击打开C:\Program Files\MySQL\MySQL Server 5.1\my.ini文件,如下图: 3. 点击“记事本”软件顶部的“编辑”,再选择“查找”,在“查找内容”处输入[mysqld],并点击“查找下一个”,它会自动转到[mysqld]字段行.在下面增加一行skip-grant-tables并保存,如下图:[mysql_5.6 没有my.ini文件,可以将my-default.ini

TortoiseGit保存用户名密码的方法

方法一: 设置 -> git 编辑本地 .git/config 增加 1 [credential]    2     helper = store 保存,输入一次密码后第二次就会记住密码了 方法二: 1. Windows中添加一个HOME环境变量,值为%USERPROFILE% 2. 在“开始>运行”中打开%Home%,新建一个名为“_netrc”的文件 3. 用记事本打开_netrc文件,输入Git服务器名.用户名.密码,并保存: 1 machine github.com      #git

登录时本地保存账号密码的方法

对于登录时保存用户名和密码,苹果官方使用的是KeychainItemWrapper,但使用时有些不便,如在引入KeychainItemWrapper的类中都要关闭arc,不能自定义key,必须使用该类提供的kSecValueData.kSecAttrAccount等.所以推荐使用第三方类库——SSkeychain,地址在 https://github.com/samsoffes/sskeychain/ 它的优点如下:①无需手动关闭arc,它会自动判断并运行在arc和非arc环境中②能存储多组账号

MySQL忘记root用户密码修改方法

一般来说在MySQL修改用户密码有好几种方法: 1.修改自己的密码可用: set password=password('123456'); 2.修改其它用户的密码可用: set password for 'username'@'host'=password('123456'); 3.通过修改mysql库中的user表中的password字段,可用: update mysql.user set password=password('123456') where User='username' an