使用 Objective c 和 SwiftUI 解密 iOS
Decrypting iOS with Objective c and SwiftUI
我计划在我的应用程序中实施 AES 加密,为此我阅读了 Rob Napier 提供的内容丰富的教程:
这是一本精彩的读物,我能够使用以下方法加密一些字符串:
使用 ROB NAPIER RNCRYPTOR CLASS
NSString * const
kRNCryptManagerErrorDomain = @"net.robnapier.RNCryptManager";
const CCAlgorithm kAlgorithm = kCCAlgorithmAES128;
const NSUInteger kAlgorithmKeySize = kCCKeySizeAES128;
const NSUInteger kAlgorithmBlockSize = kCCBlockSizeAES128;
const NSUInteger kAlgorithmIVSize = kCCBlockSizeAES128;
const NSUInteger kPBKDFSaltSize = 8;
const NSUInteger kPBKDFRounds = 10000; // ~80ms on an iPhone 4
// ===================
+ (NSData *)encryptedDataForData:(NSData *)data
password:(NSString *)password
iv:(NSData **)iv
salt:(NSData **)salt
error:(NSError **)error {
NSAssert(iv, @"IV must not be NULL");
NSAssert(salt, @"salt must not be NULL");
*iv = [self randomDataOfLength:kAlgorithmIVSize];
*salt = [self randomDataOfLength:kPBKDFSaltSize];
NSData *key = [self AESKeyForPassword:password salt:*salt];
size_t outLength;
NSMutableData *
cipherData = [NSMutableData dataWithLength:data.length +
kAlgorithmBlockSize];
CCCryptorStatus
result = CCCrypt(kCCEncrypt, // operation
kAlgorithm, // Algorithm
kCCOptionPKCS7Padding, // options
key.bytes, // key
key.length, // keylength
(*iv).bytes,// iv
data.bytes, // dataIn
data.length, // dataInLength,
cipherData.mutableBytes, // dataOut
cipherData.length, // dataOutAvailable
&outLength); // dataOutMoved
if (result == kCCSuccess) {
cipherData.length = outLength;
}
else {
if (error) {
*error = [NSError errorWithDomain:kRNCryptManagerErrorDomain
code:result
userInfo:nil];
}
return nil;
}
return cipherData;
}
// ===================
+ (NSData *)randomDataOfLength:(size_t)length {
NSMutableData *data = [NSMutableData dataWithLength:length];
int result = SecRandomCopyBytes(kSecRandomDefault,
length,
data.mutableBytes);
NSAssert(result == 0, @"Unable to generate random bytes: %d",
errno);
return data;
}
// ===================
// Replace this with a 10,000 hash calls if you don't have CCKeyDerivationPBKDF
+ (NSData *)AESKeyForPassword:(NSString *)password
salt:(NSData *)salt {
NSMutableData *
derivedKey = [NSMutableData dataWithLength:kAlgorithmKeySize];
int
result = CCKeyDerivationPBKDF(kCCPBKDF2, // algorithm
password.UTF8String, // password
[password lengthOfBytesUsingEncoding:NSUTF8StringEncoding], // passwordLength
salt.bytes, // salt
salt.length, // saltLen
kCCPRFHmacAlgSHA1, // PRF
kPBKDFRounds, // rounds
derivedKey.mutableBytes, // derivedKey
derivedKey.length); // derivedKeyLen
// Do not log password here
NSAssert(result == kCCSuccess,
@"Unable to create AES key for password: %d", result);
return derivedKey;
}
但是在解密时我无法正确解密并且我在场景中得到空值:供您参考,解密代码是:
+ (NSData*)decryptData:(NSData*)data key:(NSData*)key error:(NSError **)error
{
if (key.length != 16 && key.length != 24 && key.length != 32) {
*error = [NSError errorWithDomain:@"keyLengthError" code:-1 userInfo:nil];
return nil;
}
CCCryptorStatus ccStatus = kCCSuccess;
int ivLength = kCCBlockSizeAES128;
size_t clearBytes = 0;
NSMutableData *dataOut = [NSMutableData dataWithLength:data.length - ivLength];
NSLog(@"Data Out String Decrypt%@", dataOut);
ccStatus = CCCrypt(kCCDecrypt,
kCCAlgorithmAES,
kCCOptionPKCS7Padding,
key.bytes,
key.length,
data.bytes,
data.bytes + ivLength,
data.length - ivLength,
dataOut.mutableBytes,
dataOut.length,
&clearBytes);
if (ccStatus == kCCSuccess) {
dataOut.length = clearBytes;
}
else {
if (error) {
*error = [NSError errorWithDomain:@"kEncryptionError" code:ccStatus userInfo:nil];
}
dataOut = nil;
}
return dataOut;
}
在这种情况下我哪里出错了?几天来我一直在努力解决这个问题。有人可以帮我吗?
你举的例子中给出的方法参考了Rob Napiers Github Repo。
只需使用您给定的密码、salt 等对其进行测试,它就可以正常工作!
是的,明白了,你想在解密时丢掉 password:
和 iv:
以及 salt:
参数,只使用 key:
。那么你至少需要 iv:
才能做到这一点。但正如 Rob 对您的其他问题所评论的那样,不要 re-invent 方向盘。
我上面链接的方法与您的解密参数配合使用效果很好。与您的代码的唯一区别是 password
、iv
和 salt
用于解密。
除了想要开发无需密码即可解密的东西的想法之外,您还必须更深入地了解 CCKeyDerivationPBKDF()
(CommonKeyDerivation.h) 的工作原理。
编辑:正如您所要求的那样,可以打包和解包您的 salt,iv 和 cypher 使用 NSData 非常简单。
+ (NSData *)packWithSalt:(NSData*)salt IV:(NSData*)iv Cypher:(NSData*)tocypher {
//adding Salt + IV + Cipher text
NSMutableData *combi = [NSMutableData data];
//[combi appendBytes:salt.bytes length:16];
//[combi appendBytes:iv.bytes length:16]; //16
//[combi appendBytes:tocypher.bytes length:tocypher.length];
[combi appendData:salt];
[combi appendData:iv];
[combi appendData:tocypher];
return combi;
}
+ (NSData*)cypherUnpackToSalt:(NSMutableData**)salt andIV:(NSMutableData**)iv fromPackData:(NSData*)pack {
void *sBuff[16] = {};
void *iBuff[16] = {};
NSUInteger len = pack.length - 16 - 16; //keep length flexible
void *pBuff = malloc(sizeof(Byte)*len); //needs dynamically size of buff
[pack getBytes:sBuff range:NSMakeRange(0, 16)];
[pack getBytes:iBuff range:NSMakeRange(16, 32)];
[pack getBytes:pBuff range:NSMakeRange(32, len)];
[(*salt) replaceBytesInRange:NSMakeRange(0, 16) withBytes:sBuff];
[(*iv) replaceBytesInRange:NSMakeRange(0, 16) withBytes:iBuff];
NSMutableData *unpack = [NSMutableData dataWithLength:len];
[unpack replaceBytesInRange:NSMakeRange(0, len) withBytes:pBuff];
free(pBuff);
return unpack;
}
将这两种方式的加解密结合起来应该很简单
概念验证:我们可以一起打包吗?并再次解压?
NSData *salt = [CryptAES randomDataOfLength:16];
NSData *iv = [CryptAES randomDataOfLength:16];
NSData *chunk = [CryptAES packWithSalt:salt IV:iv Cypher:plaintextData];
NSLog(@"salt=%@ iv=%@ pack=%@ ",[salt base64EncodedStringWithOptions:0], [iv base64EncodedStringWithOptions:0], [chunk base64EncodedStringWithOptions:0] );
NSMutableData *unSalt = [NSMutableData dataWithLength:16];
NSMutableData *unIv = [NSMutableData dataWithLength:16];
NSData *unchunk = [CryptAES cypherUnpackToSalt:&unSalt andIV:&unIv fromPackData:chunk];
NSString *plainAgain = [[NSString alloc] initWithData:unchunk encoding:NSUTF8StringEncoding];
NSLog(@"salt=%@ iv=%@ unpack=%@",[unSalt base64EncodedStringWithOptions:0], [unIv base64EncodedStringWithOptions:0], plainAgain );
所以你的解密方法仍然需要密码参数。
这并不完美,但你永远不应该将加密数据连同其密码一起扔掉——这应该没问题——你只需在用户端处理密码。我的意思是否则整个加密都没用!
我计划在我的应用程序中实施 AES 加密,为此我阅读了 Rob Napier 提供的内容丰富的教程:
这是一本精彩的读物,我能够使用以下方法加密一些字符串:
使用 ROB NAPIER RNCRYPTOR CLASS
NSString * const
kRNCryptManagerErrorDomain = @"net.robnapier.RNCryptManager";
const CCAlgorithm kAlgorithm = kCCAlgorithmAES128;
const NSUInteger kAlgorithmKeySize = kCCKeySizeAES128;
const NSUInteger kAlgorithmBlockSize = kCCBlockSizeAES128;
const NSUInteger kAlgorithmIVSize = kCCBlockSizeAES128;
const NSUInteger kPBKDFSaltSize = 8;
const NSUInteger kPBKDFRounds = 10000; // ~80ms on an iPhone 4
// ===================
+ (NSData *)encryptedDataForData:(NSData *)data
password:(NSString *)password
iv:(NSData **)iv
salt:(NSData **)salt
error:(NSError **)error {
NSAssert(iv, @"IV must not be NULL");
NSAssert(salt, @"salt must not be NULL");
*iv = [self randomDataOfLength:kAlgorithmIVSize];
*salt = [self randomDataOfLength:kPBKDFSaltSize];
NSData *key = [self AESKeyForPassword:password salt:*salt];
size_t outLength;
NSMutableData *
cipherData = [NSMutableData dataWithLength:data.length +
kAlgorithmBlockSize];
CCCryptorStatus
result = CCCrypt(kCCEncrypt, // operation
kAlgorithm, // Algorithm
kCCOptionPKCS7Padding, // options
key.bytes, // key
key.length, // keylength
(*iv).bytes,// iv
data.bytes, // dataIn
data.length, // dataInLength,
cipherData.mutableBytes, // dataOut
cipherData.length, // dataOutAvailable
&outLength); // dataOutMoved
if (result == kCCSuccess) {
cipherData.length = outLength;
}
else {
if (error) {
*error = [NSError errorWithDomain:kRNCryptManagerErrorDomain
code:result
userInfo:nil];
}
return nil;
}
return cipherData;
}
// ===================
+ (NSData *)randomDataOfLength:(size_t)length {
NSMutableData *data = [NSMutableData dataWithLength:length];
int result = SecRandomCopyBytes(kSecRandomDefault,
length,
data.mutableBytes);
NSAssert(result == 0, @"Unable to generate random bytes: %d",
errno);
return data;
}
// ===================
// Replace this with a 10,000 hash calls if you don't have CCKeyDerivationPBKDF
+ (NSData *)AESKeyForPassword:(NSString *)password
salt:(NSData *)salt {
NSMutableData *
derivedKey = [NSMutableData dataWithLength:kAlgorithmKeySize];
int
result = CCKeyDerivationPBKDF(kCCPBKDF2, // algorithm
password.UTF8String, // password
[password lengthOfBytesUsingEncoding:NSUTF8StringEncoding], // passwordLength
salt.bytes, // salt
salt.length, // saltLen
kCCPRFHmacAlgSHA1, // PRF
kPBKDFRounds, // rounds
derivedKey.mutableBytes, // derivedKey
derivedKey.length); // derivedKeyLen
// Do not log password here
NSAssert(result == kCCSuccess,
@"Unable to create AES key for password: %d", result);
return derivedKey;
}
但是在解密时我无法正确解密并且我在场景中得到空值:供您参考,解密代码是:
+ (NSData*)decryptData:(NSData*)data key:(NSData*)key error:(NSError **)error
{
if (key.length != 16 && key.length != 24 && key.length != 32) {
*error = [NSError errorWithDomain:@"keyLengthError" code:-1 userInfo:nil];
return nil;
}
CCCryptorStatus ccStatus = kCCSuccess;
int ivLength = kCCBlockSizeAES128;
size_t clearBytes = 0;
NSMutableData *dataOut = [NSMutableData dataWithLength:data.length - ivLength];
NSLog(@"Data Out String Decrypt%@", dataOut);
ccStatus = CCCrypt(kCCDecrypt,
kCCAlgorithmAES,
kCCOptionPKCS7Padding,
key.bytes,
key.length,
data.bytes,
data.bytes + ivLength,
data.length - ivLength,
dataOut.mutableBytes,
dataOut.length,
&clearBytes);
if (ccStatus == kCCSuccess) {
dataOut.length = clearBytes;
}
else {
if (error) {
*error = [NSError errorWithDomain:@"kEncryptionError" code:ccStatus userInfo:nil];
}
dataOut = nil;
}
return dataOut;
}
在这种情况下我哪里出错了?几天来我一直在努力解决这个问题。有人可以帮我吗?
你举的例子中给出的方法参考了Rob Napiers Github Repo。
只需使用您给定的密码、salt 等对其进行测试,它就可以正常工作!
是的,明白了,你想在解密时丢掉 password:
和 iv:
以及 salt:
参数,只使用 key:
。那么你至少需要 iv:
才能做到这一点。但正如 Rob 对您的其他问题所评论的那样,不要 re-invent 方向盘。
我上面链接的方法与您的解密参数配合使用效果很好。与您的代码的唯一区别是 password
、iv
和 salt
用于解密。
除了想要开发无需密码即可解密的东西的想法之外,您还必须更深入地了解 CCKeyDerivationPBKDF()
(CommonKeyDerivation.h) 的工作原理。
编辑:正如您所要求的那样,可以打包和解包您的 salt,iv 和 cypher 使用 NSData 非常简单。
+ (NSData *)packWithSalt:(NSData*)salt IV:(NSData*)iv Cypher:(NSData*)tocypher {
//adding Salt + IV + Cipher text
NSMutableData *combi = [NSMutableData data];
//[combi appendBytes:salt.bytes length:16];
//[combi appendBytes:iv.bytes length:16]; //16
//[combi appendBytes:tocypher.bytes length:tocypher.length];
[combi appendData:salt];
[combi appendData:iv];
[combi appendData:tocypher];
return combi;
}
+ (NSData*)cypherUnpackToSalt:(NSMutableData**)salt andIV:(NSMutableData**)iv fromPackData:(NSData*)pack {
void *sBuff[16] = {};
void *iBuff[16] = {};
NSUInteger len = pack.length - 16 - 16; //keep length flexible
void *pBuff = malloc(sizeof(Byte)*len); //needs dynamically size of buff
[pack getBytes:sBuff range:NSMakeRange(0, 16)];
[pack getBytes:iBuff range:NSMakeRange(16, 32)];
[pack getBytes:pBuff range:NSMakeRange(32, len)];
[(*salt) replaceBytesInRange:NSMakeRange(0, 16) withBytes:sBuff];
[(*iv) replaceBytesInRange:NSMakeRange(0, 16) withBytes:iBuff];
NSMutableData *unpack = [NSMutableData dataWithLength:len];
[unpack replaceBytesInRange:NSMakeRange(0, len) withBytes:pBuff];
free(pBuff);
return unpack;
}
将这两种方式的加解密结合起来应该很简单
概念验证:我们可以一起打包吗?并再次解压?
NSData *salt = [CryptAES randomDataOfLength:16];
NSData *iv = [CryptAES randomDataOfLength:16];
NSData *chunk = [CryptAES packWithSalt:salt IV:iv Cypher:plaintextData];
NSLog(@"salt=%@ iv=%@ pack=%@ ",[salt base64EncodedStringWithOptions:0], [iv base64EncodedStringWithOptions:0], [chunk base64EncodedStringWithOptions:0] );
NSMutableData *unSalt = [NSMutableData dataWithLength:16];
NSMutableData *unIv = [NSMutableData dataWithLength:16];
NSData *unchunk = [CryptAES cypherUnpackToSalt:&unSalt andIV:&unIv fromPackData:chunk];
NSString *plainAgain = [[NSString alloc] initWithData:unchunk encoding:NSUTF8StringEncoding];
NSLog(@"salt=%@ iv=%@ unpack=%@",[unSalt base64EncodedStringWithOptions:0], [unIv base64EncodedStringWithOptions:0], plainAgain );
所以你的解密方法仍然需要密码参数。 这并不完美,但你永远不应该将加密数据连同其密码一起扔掉——这应该没问题——你只需在用户端处理密码。我的意思是否则整个加密都没用!