iOS:iOS开发中用户密码保存位置

时间:2024-04-15 11:48:34

原文来自简书:http://www.jianshu.com/p/4af3b8179136/comments/1294203

 

如果要实现自动登录,不必每次打开应用都去登录,我们势必要把密码保存到本地。
一般我们的操作是:
每次打开应用后,如果存在密码,直接进入界面,然后再进行后台密码验证。如果没网络,我们可以跳过验证;如果有网络,我们可以后台去验证帐号密码的正确性,并根据服务器的response做一些操作。

 

为什么直接把密码存储在NSUserDefaults中不安全?

 iOS中沙盒有哪几个文件夹,都是用来干吗的

默认情况下,每个沙盒含有3个文件夹:Documents, Library 和 tmp。因为应用的沙盒机制,应用只能在几个目录下读写文件

  • Documents:苹果建议将程序中建立的或在程序中浏览到的文件数据保存在该目录下,iTunes备份和恢复的时候会包括此目录
  • Library:存储程序的默认设置或其它状态信息;
  • Library/Caches:存放缓存文件,iTunes不会备份此目录,此目录下文件不会在应用退出删除
  • tmp:提供一个即时创建临时文件的地方。

获取到沙盒Library路径

//获取Library目录路径
-  (void)getLibraryPath
 {
    NSArray * pathArray = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,NSUserDomainMask, YES);
   NSString * libraryStrPath = [pathArray objectAtIndex:0];
    NSLog(@"LibraryPath:%@“,libraryStrPath);
 }

如图就是NSUserDefaults对应的plist文件在sandbox中的位置

                蓝色部分为plist文件

如果sandbox被破解,或者你的手机被越狱,那么就能轻松拿到这个文件。
那么就能轻松读到存储的信息,密码就会不安全:

 

如何删除NSUserDefaults对应的plist文件?

其实就是删除plist文件中所有的键值对。

NSUserDefaults *userDefatluts = [NSUserDefaults standardUserDefaults];
NSDictionary *dictionary = [userDefaults dictionaryRepresentation];
for(NSString* key in [dictionary allKeys]){
     [userDefaults removeObjectForKey:key];
     [userDefaults synchronize];
}

 

 

如何解决“直接把密码存储在NSUserDefaults中不安全”的问题?

把密码加密后再存储到NSUserDefaults中

iOS中提供了很多种加密算法,对于存储密码,可以使用不可逆的MD5加密。
使用MD5加密需要导入头文件:
\'\'#import <CommonCrypto/CommonDigest.h>

##### 简单的MD5加密
+ ( NSString *)md5String:( NSString *)str

{

const char *myPasswd = [str UTF8String ];

unsigned char mdc[ 16 ];

CC_MD5 (myPasswd, ( CC_LONG ) strlen (myPasswd), mdc);

NSMutableString *md5String = [ NSMutableString string ];

for ( int i = 0 ; i< 16 ; i++) {

[md5String appendFormat : @"%02x" ,mdc[i]];

}

return md5String;

}

##### 复杂一些的MD5加密
+ ( NSString *)md5String:( NSString *)str

 {

 const char *myPasswd = [str UTF8String ];

 unsigned char mdc[ 16 ];

 CC_MD5 (myPasswd, ( CC_LONG ) strlen (myPasswd), mdc);

 NSMutableString *md5String = [ NSMutableString string ];

 [md5String appendFormat : @"%02x" ,mdc[ 0 ]];

 for ( int i = 1 ; i< 16 ; i++) {

 [md5String appendFormat : @"%02x" ,mdc[i]^mdc[ 0 ]];

不使用NSUserDefaults保存密码,使用keyChain来保存密码

更加保险的方法是把密码保存在iOS提供的keychina中,并且删除应用后,密码不会删除,下载安装还能使用。iOS系统提供了一些方法,进行一些简单的封装之后,就可以很方便的使用。

 

Github-chenhuaizhe-iOS-keychain
你也可以在这里直接下载,更多交流可以关注我的微博:@陈怀哲

下面是封装代码,使用时需要先导入Security.framework:

 

PassWordTool.h

#import <Foundation/Foundation.h>

@interface PassWordTool : NSObject
/**
 *    @brief    存储密码
 *
 *    @param     password     密码内容
 */
+(void)savePassWord:(NSString *)password;

/**
 *    @brief    读取密码
 *
 *    @return    密码内容
 */
+(id)readPassWord;

/**
 *    @brief    删除密码数据
 */
+(void)deletePassWord;

@end

 

PassWordTool.m

import "PassWordTool.h"
 import "KeychainTool.h"

 @implementation PassWordTool
 static NSString * const KEY_IN_KEYCHAIN = @"com.chenyuan.app.userid";
 static NSString * const KEY_PASSWORD = @"com.chenyuan.app.password";

+(void)savePassWord:(NSString *)password
 {
     NSMutableDictionary *usernamepasswordKVPairs = [NSMutableDictionary dictionary];
     [usernamepasswordKVPairs setObject:password forKey:KEY_PASSWORD];
     [KeychainTool save:KEY_IN_KEYCHAIN data:usernamepasswordKVPairs];
 }

+(id)readPassWord
 {
     NSMutableDictionary *usernamepasswordKVPair = (NSMutableDictionary *)[KeychainTool load:KEY_IN_KEYCHAIN];
     return [usernamepasswordKVPair objectForKey:KEY_PASSWORD];
 }

+(void)deletePassWord
 {
     [KeychainTool delete:KEY_IN_KEYCHAIN];
 }
 @end

 

KeychainTool.h

#import <Foundation/Foundation.h>

@interface KeychainTool : NSObject

+ (NSMutableDictionary *)getKeychainQuery:(NSString *)service ;

+ (void)save:(NSString *)service data:(id)data ;

+ (id)load:(NSString *)service ;

+ (void)delete:(NSString *)service ;

@end

 

KeychainTool.m

```
# import "KeychainTool.h"

 @implementation KeychainTool
+ (NSMutableDictionary *)getKeychainQuery:(NSString *)service {
     return [NSMutableDictionary dictionaryWithObjectsAndKeys:
             (__bridge_transfer id)kSecClassGenericPassword,(__bridge_transfer id)kSecClass,
             service, (__bridge_transfer id)kSecAttrService,
             service, (__bridge_transfer id)kSecAttrAccount,
             (__bridge_transfer id)kSecAttrAccessibleAfterFirstUnlock,(__bridge_transfer id)kSecAttrAccessible,
             nil];
 }

+ (void)save:(NSString *)service data:(id)data {
     //Get search dictionary
     NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
     //Delete old item before add new item
     SecItemDelete((__bridge_retained CFDictionaryRef)keychainQuery);
     //Add new object to search dictionary(Attention:the data format)
     [keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(__bridge_transfer id)kSecValueData];
     //Add item to keychain with the search dictionary
     SecItemAdd((__bridge_retained CFDictionaryRef)keychainQuery, NULL);
 }

+ (id)load:(NSString *)service {
     id ret = nil;
     NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
     //Configure the search setting
     [keychainQuery setObject:(id)kCFBooleanTrue forKey:(__bridge_transfer id)kSecReturnData];
     [keychainQuery setObject:(__bridge_transfer id)kSecMatchLimitOne forKey:(__bridge_transfer id)kSecMatchLimit];
     CFDataRef keyData = NULL;
     if (SecItemCopyMatching((__bridge_retained CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) {
         @try {
             ret = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge_transfer NSData *)keyData];
         } @catch (NSException *e) {
             NSLog(@"Unarchive of %@ failed: %@", service, e);
         } @finally {
         }
     }
     return ret;
 }

+ (void)delete:(NSString *)service {
     NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
     SecItemDelete((__bridge_retained CFDictionaryRef)keychainQuery);
 }
 @end

 

服务器密码验证登录请求

验证请求时,最好是不直接把明文密码包含在请求里面。
可以根据一系列字符串生成MD5加密后的签名,根据user-id 和 签名来验证登录。
比如:

NSString *sourceStr = [NSString stringWithFormat:@"attach=iOS&chartset=utf-8&format=json&partner=google&userid=%@&password=%@”,userid,password];
NSString *signStr = [NSString md5String:sourceStr];

这样得到的signStr和userid再作为网络请求的参数传给服务器做验证。

 

其他参考:

ios 利用钥匙串保存密码和获取密码

直接使用Security框架读写钥匙串,参考:http://useyourloaf.com/blog/2010/03/29/simple-iphone-keychain-access.html
 
我们使用第三方类SFHFKeychainUtils来操作钥匙串 ( GitHub代码下载 )

使用方法如下:

1、引入Security.framework框架。

2、引入头文件:#import"SFHFKeychainUtils.h"

3、存密码:   

   NSString *SERVICE_NAME=@"demo";
    [SFHFKeychainUtils storeUsername:@"dd"
                         andPassword:@"aa"
                      forServiceName:SERVICE_NAME
                      updateExisting:1
                               error:nil];

4、取密码:

    NSString *passWord =  [SFHFKeychainUtils getPasswordForUsername:@"dd"
                                                     andServiceName:SERVICE_NAME
                                                              error:nil];
    NSLog(@"%@",passWord);

5、删除用户:

[SFHFKeychainUtils deleteItemForUsername:@"dd" andServiceName:SERVICE_NAME error:nil];