在Objective-C中将加密的NSData转换为NSString?

15
我有一个iPhone应用程序,使用CCCryp(AES256)和纯文本密钥加密输入的NSString。将字符串和密钥提供给加密方法,该方法返回NSData对象。
请求[data description]其中“data”是加密的字符串数据,会得到一个类似于“<0b368353 a707e7de 3eee5992 ee69827e e3603dc2 b0dbbc0b 861ca87d f39ce72a>” 的NSString,但当我尝试将其转换为NSString时,我得到了“(null)”。
我需要向用户返回一个NSString,该字符串可以使用相同的明文密钥进行解密以返回原始字符串。如果NSData对象的“description”属性可以返回字符串,那么有没有办法从NSData对象中生成NSString而不会得到“(null)”?
更新:感谢Quinn的建议,他建议使用Base64编码生成混淆的字符串。据我所知,Base64编码不仅简单地交换字符,而且字符交换取决于位置,因此很好。
我的唯一担心是我想能够使用“密码短语”加密消息,并要求在需要解码混淆的字符串时输入相同的密码短语 - 有人能建议实现这种方式吗?

我已经更新了我的答案以解决这个问题。您是正确的,Base64不是一种替换算法——它基本上将3个字节扩展为4个字节,因此编码数据比其非编码对应物大1.37倍。基本上,它将3个8位块重新划分为4个6位块,然后重新解释每个块作为8位块,这些块可以很容易地用ASCII表示。维基百科有更多细节。 - Quinn Taylor
3个回答

33
首先,不要使用-[NSData description]创建NSString来实现此目的。 (最好将-description视为调试输出。如果我的先前答案误导了您,我只是打印描述以演示NSData可以加密和解密。)相反,请使用NSString的-dataUsingEncoding:-initWithData:encoding:方法在NSData和NSString之间进行转换。即使使用这些方法,AES加密数据也可能无法直接转换为字符串 - 某些字节序列可能无法正常处理,因此最好在创建字符串之前对数据进行编码。
我建议您尝试Base64编码NSData,因为Base64数据始终可以表示为ASCII字符串。 (当然,在此操作后,您需要先解码Base64,然后再进行解密。)
以下是一些有用的资源...

编辑:我假设您会将此与您先前的问题有关NSString对象的AES加密的答案结合起来。 将数据编码为Base64不会对数据本身施加任何限制 - 它肯定可以是经过AES加密的数据本身。如果您只需要字符串输入和输出,请执行以下操作:

  • 加密
    • 提供要加密的NSString以及用于加密的密码短语。
    • 将字符串转换为NSData并在其上执行AES加密(参见先前的问题)。
    • 对NSData进行Base64编码,然后创建并返回编码输出的NSString。
  • 解密
    • 提供要解密的加密和编码字符串以及用于解密的密码短语。
    • 从第一个字符串创建一个NSData,然后对数据进行Base64解码。
    • 对数据执行AES解密,然后创建并返回NSString。

这实际上只是将两个部分链接在一起并在出口时相反地执行它们的问题。根据我的先前答案,您可以修改encryptString:withKey:以执行最后一步并返回字符串,并将decryptData:withKey:更改为decryptString:withKey:并接受两个字符串。这很简单。


@FreeAsInBeer 请不要在编辑他人答案时将其标记为社区维基。那不是它的用途。 - Quinn Taylor
@QuinnTaylor FAQ中指出,您是唯一具有访问社区Wiki复选框的人。我的编辑可能无意中导致它进入了社区Wiki模式,但这完全是无意的。请不要指责其他人做他们不能做的事情。参考链接 - FreeAsInBeer
@FreeAsInBeer 很抱歉,这其实不是你的错。我不知道如果“原始所有者编辑了帖子十(10)次”,答案会自动转换为 CW。我只是碰巧在查看你的编辑时发现它已经变成了 CW,但那是在我的上一次编辑之后发生的。很抱歉我先入为主了。:-P - Quinn Taylor
@QuinnTaylor 一切都好。这些事情经常发生。毕竟,即使我们认为自己是无所不能的计算机巫师,我们也只是人类。 - FreeAsInBeer
NSData.description的文档仅说明“包含对象内容的十六进制表示形式的字符串,以属性列表格式表示。”没有建议表明仅用于调试目的。如果它适用于已知的数据格式,那么有没有实际的理由不使用它?【编辑:啊,也许我需要将这个答案阅读在原始问题关于加密数据的特定背景下。】 - Duncan Babbage

2
我已经为NSData和NSString准备了一整套类别,以提供字符串的AES256加密。
请查看“原始”问题上我的答案获取更多细节信息。

0

我有类似的需求,需要在用户输入密码进入应用程序时加密所有字符串,以便那些敏感字符串不会一直保持未加密状态。因此,我必须将这些字符串保持加密状态,并仅在需要时解密。

这是一个简单的要求,我希望保持轻量级。因此,我创建了一个小混淆器,使用@RobNapier在他的博客中分享的许多有用信息。它可能对那些正在寻找具有许多有趣评论的轻量级解决方案的人有所帮助。

import Foundation
import CommonCrypto
// 与接口交互的薄包装
public enum CrypticAlgo {
    case AlgoAES
    case AlgoDES

func blockSize() -> Int {
    switch self {
    case .AlgoAES:
        return kCCBlockSizeAES128
    case .AlgoDES:
        return kCCBlockSizeDES
    }
}

func keySize() -> size_t {
    switch self {
    case .AlgoAES:
        return kCCKeySizeAES128
    case .AlgoDES:
        return kCCKeySizeDES
    }
}

func algo() -> UInt32 {
    switch self {
    case .AlgoAES:
        return CCAlgorithm(kCCAlgorithmAES)
    case .AlgoDES:
        return CCAlgorithm(kCCAlgorithmDES)
    }
}

}

公共最终类 MGObfuscate {

私有变量ivData:[UInt8]? 私有变量derivedKey:数据? 私有让crypticAlgo:CrypticAlgo
公共init(密码:字符串,盐:字符串,algo:CrypticAlgo){ //快速获取数据以释放密码字符串 let passwordData = password.data(使用:.utf8)! // //需要回合以在生成哈希时延迟1秒。 // Salt是一个公共属性。如果攻击者以某种方式获得了drivedKey并尝试破解 //通过暴力攻击密码,由于Rounds的延迟,将使其感到沮丧 //获取实际密码并阻止他/她的努力。 // 让回合= CCCalibratePBKDF(CCPBKDFAlgorithm(kCCPBKDF2),密码计数, 盐计数,CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256),Int(CC_SHA256_DIGEST_LENGTH),1000)
让saltData = salt.data(使用:.utf8)! derivedKey = MGObfuscate.derivedKey(for:passwordData, saltData:saltData,rounds:rounds) self.crypticAlgo = algo var ivData = [UInt8](重复:0,计数:algo.blockSize()) //用于初始化向量的随机加密安全字节 让rStatus = SecRandomCopyBytes(kSecRandomDefault,ivData.count,&ivData) self.ivData = ivData // print(ivData) 守卫rStatus == errSecSuccess else { fatalError(“种子未生成\(rStatus)”) } }
@inline(__always)私有静态函数派生密钥(for passwordData:数据,saltData:数据,rounds:UInt32) ->数据{ var derivedData = Data(count:Int(CC_SHA256_DIGEST_LENGTH)) 让结果= derivedData.withUnsafeMutableBytes {(drivedBytes:UnsafeMutablePointer<UInt8>?)在 passwordData.withUnsafeBytes({(passwordBytes:UnsafePointer<Int8>!)在 saltData.withUnsafeBytes({(saltBytes:UnsafePointer<UInt8>!)在 CCKeyDerivationPBKDF(CCPBKDFAlgorithm(kCCPBKDF2),passwordBytes,passwordData.count,saltBytes,saltData.count,CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256),rounds,drivedBytes,Int(CC_SHA256_DIGEST_LENGTH)) }) }) } 如果kCCSuccess!=结果{ fatalError(“无法为密码生成哈希”) } 返回derivedData }
私有func runCryptic(operation:Int,inputData:Data,keyData:Data,ivData:Data) ->数据{ 让cryptLength = size_t(inputData.count + crypticAlgo.blockSize()) var cryptData = Data(count:cryptLength) 让keyLength = crypticAlgo.keySize()
var bytesProcessed:size_t = 0 让cryptStatus = cryptData.withUnsafeMutableBytes {cryptBytes in inputData.withUnsafeBytes { dataBytes in keyData.withUnsafeBytes { keyBytes in ivData.withUnsafeBytes{ ivBytes in CCCrypt(CCOperation(operation), crypticAlgo.algo(), CCOptions(kCCOptionPKCS7Padding), keyBytes,keyLength, ivBytes, dataBytes,inputData.count, cryptBytes,cryptLength, &bytesProcessed) } } } } 如果cryptStatus == CCCryptorStatus(kCCSuccess){ cryptData.removeSubrange(bytesProcessed..<cryptData.count) } else { fatalError(“错误:\(cryptStatus)”) } 返回cryptData }
公共func encriptAndPurge(inputString:inout String?) ->数据?{ 如果let inputdata = inputString?.data(使用:.utf8){ inputString = nil 返回runCryptic(operation:

}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接