如何在 iOS 中安全地保存一个字符串?
How to save a string safely in iOS?
我必须在钥匙串中保存一个字符串(密码)但原始字符串:
- 不是来自服务器;
- 不是用户生成的;
- 它必须在服务器上进行比较(我通过 https 发送);
因此,该字符串必须存在于应用程序中的某个位置(硬编码?)。
我很确定您无法为钥匙串准备数据以便在应用程序安装后随时可用,就像您可以将 plist 添加到应用程序包以便立即加载它一样如果应用程序是 运行(即使它是第一次启动)。
我读过 Data Protection:它允许处理敏感数据的应用程序利用某些设备上可用的加密功能。那是要走的路吗?那就是:我将我的数据存储到一个文本文件中,然后我保护该文件,然后我从该文件中检索我的数据,然后我将它保存到钥匙串中?
如有任何提示,我们将不胜感激。
要在钥匙串中保存一些字符串值,您可以使用 pod 库
pod 'SSKeychain'
您可以按如下方式将字符串保存到钥匙串
let appName = NSBundle.mainBundle().infoDictionary!["CFBundleName"] as! String
SSKeychain.setAccessibilityType(kSecAttrAccessibleAlways)
SSKeychain.setPassword(stringToSave, forService: appName, account: "MyAppName")
也可以使用
检索相同的内容
let appName = NSBundle.mainBundle().infoDictionary!["CFBundleName"] as! String
let stringRetrieved = SSKeychain.passwordForService(appName, account: "MyAppName")
安装上述 pod 后导入这些
import SystemConfiguration
import SSKeychain
即使删除并重新安装应用程序,这也会保留。如果你想要额外的加密,你可以使用任何加密算法,这个算法很有用
如果无法从外部(用户输入、服务器)提供字符串,开发人员将被迫以某种形式将其放入应用程序包中。它可以被硬编码、存储在文件中或由函数生成。这意味着应用程序具有 get/produce 这样一个字符串的所有必要信息。 因此它不能像加密那样保护信息。
iOS 应用程序受 Apple 的 DRM 保护,因此如果有人将您的二进制文件复制到 Mac 并开始反汇编,您可以放心。但是,不幸的是,如果黑客拥有越狱设备,则有一些工具可以将您的应用程序二进制文件从内存转储到磁盘。
所以它归结为混淆。您可以编写一个动态生成字符串的函数(例如,对硬编码字节数组进行一系列操作,然后将其转换为字符串)。这将使您的字符串更难被黑客拦截。
加密技术可能有助于解决需求。这是我们如何在 Diffie–Hellman key exchange
加密技术之上维护密钥的简单阐述。
先决条件:
- 在客户端和服务器端维护 public 和私钥对。
- 生成私钥的算法或逻辑应该对客户端和服务器端使用相同的算法或逻辑。
- 中断最终获取键以比较自己的字符串的逻辑。
进程:
应用程序和后端都应该有一个共同的 public 键,无论它是什么,在这种情况下,只需将它作为应用程序名称,如 MyApplication。
public key: MyApplication
应该使用某种加密过程或逻辑在两端生成私钥。假设生成随机数作为私钥,如下所示。
Suppose App private key is: 10
Suppose Backend private key is: 90
通过某种算法在应用程序和后端生成交换密钥。这里只是为了结合 public 和像
这样的私钥
App final key: MyApplication + 10
Backend final key: MyApplication + 90
在应用程序和后端之间交换密钥。
App Got key: MyApplication + 90
Backend Got key: MyApplication + 10
使用某种技术将接收到的密钥与自己的密钥进行比较
我。通过组合应用程序私钥和 App Got 密钥生成新的应用程序密钥,如:MyApplication + 90 + 10
二。通过组合后端私钥和后端 Got 密钥生成新的后端密钥,如:MyApplication + 10 + 90
要检查两个键是否相同需要遵循逻辑例如这里只需在键中添加最后两个数字结果将像
App owned key: MyApplication + 100
App acquired key: MyApplication + 100
BackEnd owned key: MyApplication + 100
BackEnd acquired key: MyApplication + 100
Hola 终于后端和应用程序具有相同的密钥。
取决于要求,需要为每个会话维护不同的私钥并将其存储在钥匙链上。或制作一个私钥并存储在应用程序端。
注意:这个描述只是概述了如何维护密钥,它可能涉及很多逻辑并且还提供了由其他人分解密钥的规定,这完全取决于关于用于生成和破解密钥的加密和逻辑。
参考文献:
这个问题的变体已经在 SO 上讨论过很多次了,不幸的是你不会找到一个完美的答案,因为没有一个。
本质上,您希望在您的应用程序中提供身份验证凭据,即您将一个秘密捆绑到其中。这意味着无论您做什么,攻击者都有可能通过逆向工程检索它。即使您使用技术上足够安全的硬算法对其进行加密,您的应用程序也会在某个时候对其进行解密,并且由于该应用程序可能落入攻击者手中,他们将能够嗅探出秘密。
这意味着最后你只能通过混淆你处理秘密的方式来让他们难以接受 ("make sniffing complicated")。捆绑一个被解密然后打包到钥匙串中的加密文件一开始对我来说似乎不是一个坏主意,但请记住,对于查看您的应用程序的人,尤其是在越狱 iPhone 上,这个消失的文件可以是一个很好的第一个提示。此外,删除它并没有真正的帮助,因为一个 re-install 很容易恢复它。
另一个想法可能是使用 On-Demand Resources 来分发您的秘密,这也可能更容易用更新版本替换它,以防您的秘密被泄露。不过,我自己对 on-demand 资源不是很熟悉,所以我无法告诉您它们在实际撤销方面有多适合。
这一切都假定您无法实现基于用户输入的身份验证机制。这样攻击者只能窃取他们自己的密码(假设他们不窃取别人的 iPhone...),而不是整个应用程序的重要部分(这可能会影响所有用户)。
我书签中可能对您有帮助的其他 SO 答案是:
好的,在您再次re-reading问题下的最后评论之后,这里有一个具体的建议。如果你已经有了用户身份验证(你谈论他们登录),我不知道为什么你需要第二个 password/token,但是你去吧:
- 您根本没有将 token/pass 与该应用捆绑在一起。当第一个 运行 你的应用程序时,用户无论如何都必须登录(如果我没猜错的话),这样你的服务器就可以参与进来,这就是你开始的地方。
- 在此登录期间,服务器 生成一个 token/pass 单词并将其发送给用户(取决于您的确切 implementation/custom 协议,这可能是由使用相同用户凭据的应用程序在一秒钟内完成 request/response,或者您将其放入对应用程序发送的第一个请求的响应的 headers 中)。请注意,只要您使用 https,就不需要对其进行加密,因为它已经对该数据进行了加密。因此,您的令牌在您的应用中到达的通道是安全的。
- 然后应用会立即将此 token/pass 保存在钥匙串中。这与在移动设备上获得的加密一样好。不要将令牌保存在其他任何东西中,即使是暂时的。通行证只有在内存中时才会以普通形式存在(你无法避免)。
- 您可以根据需要使用该令牌(我实际上想知道,如果您已经在使用 https 和用户登录系统,为什么还需要它)。
总而言之,如果您只是想使用令牌来避免始终依赖每个请求中的用户凭据(用户名和密码),那么这听起来像是一种非常标准的 pseudo-OAuth-like 令牌方法。从技术上讲,您还可以将用户名和密码保存在钥匙串中,并始终为每个请求获取这些信息。通常这些令牌有一个限制 time-to-live 之后它们无论如何都会失效并且应用程序必须再次依赖用户名和密码才能获得新的。
令牌的潜在缺点是它是静态的而不是 user-bound 或者它没有此 time-to-live 限制。您将不得不在服务器上以巧妙的方式生成它,并清楚地记下哪个用户使用哪个令牌。这样你就可以查明安全漏洞并做出相应的反应(而不是通过使你的唯一令牌无效来突然关闭所有用户的服务器)。不过,我还是不明白你为什么需要这个。
关于 man-in-the-middle 攻击:令牌,无论是 app-bundled(这本身就是一种风险)还是服务器生成的,本身并不能防止这种攻击。我不知道你的老板在这里的目的是什么,但也许我遗漏了一些信息。通常,https 已经保护您免受此影响(好吧,通常只有服务器经过身份验证,但如果您已经拥有用户名和密码系统,那么客户端也应该没问题)。一般来说,这实际上是一个重点。对我来说,这听起来越来越像是你原来的问题只是对现有基础设施的误解 and/or 一个 "boss-induced" 问题... :)
由于主要关注的是释义或访问令牌,它可以作为加密数据保存在钥匙串中,但只要我们发送请求,它就会被解密并且可以在越狱时跟踪它 devices.But 使用更多不可知论者加密请求数据的算法将增加 API 响应 time.However 在服务器端定义基于用户的权限是最好的方法,因为在最坏的情况下只能跟踪 1 个用户的数据。
SSL Pinning - 使用质询响应身份验证,我们可以防止应用程序受到中间人攻击。是的,可以绕过 ssl 固定,但这并不容易,而且只能在越狱设备上实现。
将过期时间定义为几秒可能是防止用户data.Identifying请求来源的另一种方法,这在授予权限之前也非常重要。
因此,通过多种方式的合作,我们可以尝试做出更好的系统。
编辑
通常我们使用 1 个密钥作为令牌加密数据,一旦该密钥被泄露就可以解密。
非对称加密使用 public 和私钥增加了一层加密。
https://developer.apple.com/library/content/documentation/Security/Conceptual/Security_Overview/CryptographicServices/CryptographicServices.html#//apple_ref/doc/uid/TP30000976-CH3-SW12
这是另一个示例,其中使用随机盐代替令牌对数据进行编码https://en.wikipedia.org/wiki/PBKDF2
你可以做的是在调用 API 时使用 AES 256 加密你的参数,然后服务器解密参数并再次发送加密的 response.You 可以解密并读取响应,前提是您的服务器和应用共享用于加密和解密的相同密钥。
我的应用程序中有类似的功能,所以我的 Util class 使用以下代码加密和解密请求和响应,
class func encrypt (stringToEncrypt: String) -> String {
let messageData = stringToEncrypt.data(using: .utf8)
let encryptedBytes = try! AES(key: "abcdefghijklmnopqrstuvwxyz012345", iv: "abcdefghijklmost").encrypt([UInt8](messageData!))
return encryptedBytes.toBase64()!
}
class func decrypt ( message: String) -> String {
let messageData = Data(base64Encoded: message, options: .ignoreUnknownCharacters)
let decryptedBytes: [UInt8] = try! AES(key: "abcdefghijklmnopqrstuvwxyz012345", iv: "abcdefghijklmost").decrypt([UInt8](messageData!))
let unencryptedString = String(bytes: decryptedBytes, encoding: .utf8)
return unencryptedString!
}
再次强调,这只是一个建议,您也可以使用其他方式。
我必须在钥匙串中保存一个字符串(密码)但原始字符串:
- 不是来自服务器;
- 不是用户生成的;
- 它必须在服务器上进行比较(我通过 https 发送);
因此,该字符串必须存在于应用程序中的某个位置(硬编码?)。
我很确定您无法为钥匙串准备数据以便在应用程序安装后随时可用,就像您可以将 plist 添加到应用程序包以便立即加载它一样如果应用程序是 运行(即使它是第一次启动)。
我读过 Data Protection:它允许处理敏感数据的应用程序利用某些设备上可用的加密功能。那是要走的路吗?那就是:我将我的数据存储到一个文本文件中,然后我保护该文件,然后我从该文件中检索我的数据,然后我将它保存到钥匙串中?
如有任何提示,我们将不胜感激。
要在钥匙串中保存一些字符串值,您可以使用 pod 库
pod 'SSKeychain'
您可以按如下方式将字符串保存到钥匙串
let appName = NSBundle.mainBundle().infoDictionary!["CFBundleName"] as! String
SSKeychain.setAccessibilityType(kSecAttrAccessibleAlways)
SSKeychain.setPassword(stringToSave, forService: appName, account: "MyAppName")
也可以使用
检索相同的内容let appName = NSBundle.mainBundle().infoDictionary!["CFBundleName"] as! String
let stringRetrieved = SSKeychain.passwordForService(appName, account: "MyAppName")
安装上述 pod 后导入这些
import SystemConfiguration
import SSKeychain
即使删除并重新安装应用程序,这也会保留。如果你想要额外的加密,你可以使用任何加密算法,这个算法很有用
如果无法从外部(用户输入、服务器)提供字符串,开发人员将被迫以某种形式将其放入应用程序包中。它可以被硬编码、存储在文件中或由函数生成。这意味着应用程序具有 get/produce 这样一个字符串的所有必要信息。 因此它不能像加密那样保护信息。
iOS 应用程序受 Apple 的 DRM 保护,因此如果有人将您的二进制文件复制到 Mac 并开始反汇编,您可以放心。但是,不幸的是,如果黑客拥有越狱设备,则有一些工具可以将您的应用程序二进制文件从内存转储到磁盘。
所以它归结为混淆。您可以编写一个动态生成字符串的函数(例如,对硬编码字节数组进行一系列操作,然后将其转换为字符串)。这将使您的字符串更难被黑客拦截。
加密技术可能有助于解决需求。这是我们如何在 Diffie–Hellman key exchange
加密技术之上维护密钥的简单阐述。
先决条件:
- 在客户端和服务器端维护 public 和私钥对。
- 生成私钥的算法或逻辑应该对客户端和服务器端使用相同的算法或逻辑。
- 中断最终获取键以比较自己的字符串的逻辑。
进程:
应用程序和后端都应该有一个共同的 public 键,无论它是什么,在这种情况下,只需将它作为应用程序名称,如 MyApplication。
public key: MyApplication
应该使用某种加密过程或逻辑在两端生成私钥。假设生成随机数作为私钥,如下所示。
Suppose App private key is: 10 Suppose Backend private key is: 90
通过某种算法在应用程序和后端生成交换密钥。这里只是为了结合 public 和像
这样的私钥App final key: MyApplication + 10 Backend final key: MyApplication + 90
在应用程序和后端之间交换密钥。
App Got key: MyApplication + 90 Backend Got key: MyApplication + 10
使用某种技术将接收到的密钥与自己的密钥进行比较
我。通过组合应用程序私钥和 App Got 密钥生成新的应用程序密钥,如:
MyApplication + 90 + 10
二。通过组合后端私钥和后端 Got 密钥生成新的后端密钥,如:
MyApplication + 10 + 90
要检查两个键是否相同需要遵循逻辑例如这里只需在键中添加最后两个数字结果将像
App owned key: MyApplication + 100
App acquired key: MyApplication + 100
BackEnd owned key: MyApplication + 100
BackEnd acquired key: MyApplication + 100
Hola 终于后端和应用程序具有相同的密钥。
取决于要求,需要为每个会话维护不同的私钥并将其存储在钥匙链上。或制作一个私钥并存储在应用程序端。
注意:这个描述只是概述了如何维护密钥,它可能涉及很多逻辑并且还提供了由其他人分解密钥的规定,这完全取决于关于用于生成和破解密钥的加密和逻辑。
参考文献:
这个问题的变体已经在 SO 上讨论过很多次了,不幸的是你不会找到一个完美的答案,因为没有一个。
本质上,您希望在您的应用程序中提供身份验证凭据,即您将一个秘密捆绑到其中。这意味着无论您做什么,攻击者都有可能通过逆向工程检索它。即使您使用技术上足够安全的硬算法对其进行加密,您的应用程序也会在某个时候对其进行解密,并且由于该应用程序可能落入攻击者手中,他们将能够嗅探出秘密。
这意味着最后你只能通过混淆你处理秘密的方式来让他们难以接受 ("make sniffing complicated")。捆绑一个被解密然后打包到钥匙串中的加密文件一开始对我来说似乎不是一个坏主意,但请记住,对于查看您的应用程序的人,尤其是在越狱 iPhone 上,这个消失的文件可以是一个很好的第一个提示。此外,删除它并没有真正的帮助,因为一个 re-install 很容易恢复它。
另一个想法可能是使用 On-Demand Resources 来分发您的秘密,这也可能更容易用更新版本替换它,以防您的秘密被泄露。不过,我自己对 on-demand 资源不是很熟悉,所以我无法告诉您它们在实际撤销方面有多适合。
这一切都假定您无法实现基于用户输入的身份验证机制。这样攻击者只能窃取他们自己的密码(假设他们不窃取别人的 iPhone...),而不是整个应用程序的重要部分(这可能会影响所有用户)。
我书签中可能对您有帮助的其他 SO 答案是:
好的,在您再次re-reading问题下的最后评论之后,这里有一个具体的建议。如果你已经有了用户身份验证(你谈论他们登录),我不知道为什么你需要第二个 password/token,但是你去吧:
- 您根本没有将 token/pass 与该应用捆绑在一起。当第一个 运行 你的应用程序时,用户无论如何都必须登录(如果我没猜错的话),这样你的服务器就可以参与进来,这就是你开始的地方。
- 在此登录期间,服务器 生成一个 token/pass 单词并将其发送给用户(取决于您的确切 implementation/custom 协议,这可能是由使用相同用户凭据的应用程序在一秒钟内完成 request/response,或者您将其放入对应用程序发送的第一个请求的响应的 headers 中)。请注意,只要您使用 https,就不需要对其进行加密,因为它已经对该数据进行了加密。因此,您的令牌在您的应用中到达的通道是安全的。
- 然后应用会立即将此 token/pass 保存在钥匙串中。这与在移动设备上获得的加密一样好。不要将令牌保存在其他任何东西中,即使是暂时的。通行证只有在内存中时才会以普通形式存在(你无法避免)。
- 您可以根据需要使用该令牌(我实际上想知道,如果您已经在使用 https 和用户登录系统,为什么还需要它)。
总而言之,如果您只是想使用令牌来避免始终依赖每个请求中的用户凭据(用户名和密码),那么这听起来像是一种非常标准的 pseudo-OAuth-like 令牌方法。从技术上讲,您还可以将用户名和密码保存在钥匙串中,并始终为每个请求获取这些信息。通常这些令牌有一个限制 time-to-live 之后它们无论如何都会失效并且应用程序必须再次依赖用户名和密码才能获得新的。
令牌的潜在缺点是它是静态的而不是 user-bound 或者它没有此 time-to-live 限制。您将不得不在服务器上以巧妙的方式生成它,并清楚地记下哪个用户使用哪个令牌。这样你就可以查明安全漏洞并做出相应的反应(而不是通过使你的唯一令牌无效来突然关闭所有用户的服务器)。不过,我还是不明白你为什么需要这个。
关于 man-in-the-middle 攻击:令牌,无论是 app-bundled(这本身就是一种风险)还是服务器生成的,本身并不能防止这种攻击。我不知道你的老板在这里的目的是什么,但也许我遗漏了一些信息。通常,https 已经保护您免受此影响(好吧,通常只有服务器经过身份验证,但如果您已经拥有用户名和密码系统,那么客户端也应该没问题)。一般来说,这实际上是一个重点。对我来说,这听起来越来越像是你原来的问题只是对现有基础设施的误解 and/or 一个 "boss-induced" 问题... :)
由于主要关注的是释义或访问令牌,它可以作为加密数据保存在钥匙串中,但只要我们发送请求,它就会被解密并且可以在越狱时跟踪它 devices.But 使用更多不可知论者加密请求数据的算法将增加 API 响应 time.However 在服务器端定义基于用户的权限是最好的方法,因为在最坏的情况下只能跟踪 1 个用户的数据。
SSL Pinning - 使用质询响应身份验证,我们可以防止应用程序受到中间人攻击。是的,可以绕过 ssl 固定,但这并不容易,而且只能在越狱设备上实现。
将过期时间定义为几秒可能是防止用户data.Identifying请求来源的另一种方法,这在授予权限之前也非常重要。 因此,通过多种方式的合作,我们可以尝试做出更好的系统。
编辑
通常我们使用 1 个密钥作为令牌加密数据,一旦该密钥被泄露就可以解密。 非对称加密使用 public 和私钥增加了一层加密。 https://developer.apple.com/library/content/documentation/Security/Conceptual/Security_Overview/CryptographicServices/CryptographicServices.html#//apple_ref/doc/uid/TP30000976-CH3-SW12
这是另一个示例,其中使用随机盐代替令牌对数据进行编码https://en.wikipedia.org/wiki/PBKDF2
你可以做的是在调用 API 时使用 AES 256 加密你的参数,然后服务器解密参数并再次发送加密的 response.You 可以解密并读取响应,前提是您的服务器和应用共享用于加密和解密的相同密钥。
我的应用程序中有类似的功能,所以我的 Util class 使用以下代码加密和解密请求和响应,
class func encrypt (stringToEncrypt: String) -> String {
let messageData = stringToEncrypt.data(using: .utf8)
let encryptedBytes = try! AES(key: "abcdefghijklmnopqrstuvwxyz012345", iv: "abcdefghijklmost").encrypt([UInt8](messageData!))
return encryptedBytes.toBase64()!
}
class func decrypt ( message: String) -> String {
let messageData = Data(base64Encoded: message, options: .ignoreUnknownCharacters)
let decryptedBytes: [UInt8] = try! AES(key: "abcdefghijklmnopqrstuvwxyz012345", iv: "abcdefghijklmost").decrypt([UInt8](messageData!))
let unencryptedString = String(bytes: decryptedBytes, encoding: .utf8)
return unencryptedString!
}
再次强调,这只是一个建议,您也可以使用其他方式。