iOS 和 Python 之间的 AES 加密
AES encryption between iOS and Python
我在 iOS (CCCrypt
) 和 python (pycryptdome
) 上都具有使用 AES(128 和 256)encrypt/decrypt 的功能。所有测试用例都在每个平台上工作,但是......当我使用 AES 密钥和从 iOS 到 python 的加密字符串时,解密失败。我已经广泛查看并尝试了各种用例都无济于事。
我在这里创建了一个简单的测试用例,其中包含 iOS 加密和 python 解密,希望有人能告诉我我在平台上所做的不同之处。
iOS代码
测试用例
NSString *test_aes = @"XSmTe1Eyw8JsZkreIFUpNi7BhKEReHTP";
NSString *test_string = @"This is a test string";
NSData *clearPayload = [test_string dataUsingEncoding:NSUTF8StringEncoding];
NSData *encPayload = nil;
char keyPtr[kCCKeySizeAES256 + 1]; // room for terminator (unused)
bzero( keyPtr, sizeof( keyPtr ) ); // fill with zeroes (for padding)
// fetch key data
[test_aes getCString:keyPtr maxLength:sizeof( keyPtr ) encoding:NSUTF8StringEncoding];
NSUInteger dataLength = clearPayload.length;
size_t bufferSize = dataLength + kCCKeySizeAES256;
void *buffer = malloc( bufferSize );
size_t numBytesEncrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt( kCCEncrypt, kCCAlgorithmAES, kCCOptionPKCS7Padding,
keyPtr, kCCKeySizeAES256,
NULL /* initialization vector (optional) */,
[clearPayload bytes], dataLength, /* input */
buffer, bufferSize, /* output */
&numBytesEncrypted );
NSString *encString = @"Error";
if( cryptStatus == kCCSuccess )
{
//the returned NSData takes ownership of the buffer and will free it on deallocation
encPayload = [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
encString = [encPayload base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
}
//free( buffer ); //free the buffer
NSLog(@"Src = %@ AES = %@ String = %@",test_string, test_aes, encString);
encPayload = [[NSData alloc] initWithBase64EncodedString:encString options:NSDataBase64DecodingIgnoreUnknownCharacters];
clearPayload = nil;
char keyPtr2[kCCKeySizeAES256+1]; // room for terminator (unused)
bzero( keyPtr2, sizeof( keyPtr2 ) ); // fill with zeroes (for padding)
// fetch key data
[test_aes getCString:keyPtr2 maxLength:sizeof( keyPtr2 ) encoding:NSUTF8StringEncoding];
NSUInteger dataLength2 = [encPayload length];
//See the doc: For block ciphers, the output size will always be less than or
//equal to the input size plus the size of one block.
//That's why we need to add the size of one block here
size_t bufferSize2 = dataLength2 + kCCKeySizeAES256;
void *buffer2 = malloc( bufferSize2 );
size_t numBytesDecrypted = 0;
CCCryptorStatus cryptStatus2 = CCCrypt( kCCDecrypt, kCCAlgorithmAES, kCCOptionPKCS7Padding,
keyPtr, kCCKeySizeAES256,
NULL /* initialization vector (optional) */,
[encPayload bytes], dataLength2, /* input */
buffer2, bufferSize2, /* output */
&numBytesDecrypted );
NSString *clearString = @"Error";
if( cryptStatus2 == kCCSuccess )
{
//the returned NSData takes ownership of the buffer and will free it on deallocation
clearPayload = [NSData dataWithBytesNoCopy:buffer2 length:numBytesDecrypted];
clearString = [[NSString alloc] initWithData:clearPayload encoding:NSUTF8StringEncoding];
}
NSLog(@"Res = %@",clearString);
这段代码中的加密和解密工作正常,输出为:
Src = This is a test string
AES = XSmTe1Eyw8JsZkreIFUpNi7BhKEReHTP
String = hUbjWyXX4mB01gI0RJhYQRD0iAjQnkGTpsnKcmDpvaQ=
Res = This is a test string
当我将编码字符串和 aes 密钥带到 python 以使用此代码进行测试时:
key = "XSmTe1Eyw8JsZkreIFUpNi7BhKEReHTP"
data = "hUbjWyXX4mB01gI0RJhYQRD0iAjQnkGTpsnKcmDpvaQ="
usekey = key
useData = data
if isinstance(key, str):
usekey = key.encode('utf-8')
cipher = AES.new(usekey, AES.MODE_GCM, nonce=self.nonce)
print("nonce", cipher.nonce)
if isinstance(data, str):
useData = data.encode('utf-8')
useData = b64decode(useData)
puseData = useData # unpad(useData,32)
print("decrypt:In bytes=", puseData)
result = cipher.decrypt(puseData)
print ("decrypt:Out bytes=",result)
解密失败,输出为
nonce b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
decrypt:In bytes= b'\x85F\xe3[%\xd7\xe2`t\xd6\x024D\x98XA\x10\xf4\x88\x08\xd0\x9eA\x93\xa6\xc9\xcar`\xe9\xbd\xa4'
decrypt:Out bytes= b'\x08\xc58\x962q\x94\xff#\xfa\xab\xe2\xc8{b\xed\x0b\xedw\x8f\xe3\xec\x0b\x8e\xfb\xcc\x12\x7f\x9e\xb4\x8f\xd6'
以上两个例程都可以毫无问题地处理本地加密数据,我已经破解了这里的示例(包括不释放 malloc 缓冲区 :) 用于调试目的,所以我为有点脏的代码道歉。
注意:我曾尝试将 python 模式更改为 AES.MODE_CBC(并添加了填充代码),当我看到注释 iOS 可能会使用它而不是 GCM,这也失败了... 现在我将 nonce / iv 保留为 0 的数组,因为我被告知 iOS 将使用它作为默认值,因为没有提供 CCCrypt,当这个例子工作时我将过渡到指定iv.
如有任何指示,我将不胜感激。
编辑:
我继续并在 iOS 侧指定了一个空 IV
char iv[16]; // also tried 17
bzero( iv, sizeof( iv ) );
行为完全没有变化...
编辑:
我在两个系统上将 IV 设置为所有字符“1”并得到相同的结果。
iOS 添加代码:
NSString *hardCodeIV = @"1111111111111111";
char iv[17];
bzero( iv, sizeof( iv ) );
[hardCodeIV getCString:iv maxLength:sizeof(iv) encoding:NSUTF8StringEncoding];
产生了
Src = This is a test string
AES = XSmTe1Eyw8JsZkreIFUpNi7BhKEReHTP
String = sFoZ24VRN1hyMzegXT+GFzAn/YGPvaKO8p1eD+xhGaU=
Res = This is a test string
因此 iOS 它使用字节 0 和字符 1 IV 正确加密和解密....
并且 python 代码在使用 IV 在本地加密和解密时也能正常工作...但是当 iOS 加密的输出用于 python 解密时失败如此处所示。
将密钥和加密消息移动到 python 以解密为:
key = "XSmTe1Eyw8JsZkreIFUpNi7BhKEReHTP"
data = "sFoZ24VRN1hyMzegXT+GFzAn/YGPvaKO8p1eD+xhGaU="
usekey = key
useData = data
if isinstance(key, str):
usekey = key.encode('utf-8')
cipher = AES.new(usekey, AES.MODE_GCM, nonce=self.nonce)
print("nonce", cipher.nonce)
if isinstance(data, str):
useData = data.encode('utf-8')
useData = b64decode(useData)
puseData = useData # unpad(useData,32)
print("decrypt:In bytes=", puseData)
result = cipher.decrypt(puseData)
print ("decrypt:Out bytes=",result)
结果:
nonce b'1111111111111111'
decrypt:In bytes= b"\xb0Z\x19\xdb\x85Q7Xr37\xa0]?\x86\x170'\xfd\x81\x8f\xbd\xa2\x8e\xf2\x9d^\x0f\xeca\x19\xa5"
decrypt:Out bytes= b'\xc3\x1e"w\x86:~\x86\xd3\xc9H3\xd3\xd3y)|,|\xe02(\xc6\x17\xa3\x1e\xe2\x0f\x1a#\xbbW'
所以,还是不开心...
看起来很像是算法选择的问题,但 iOS 上的选项似乎只有 GCM 或 CBC,GCM 是默认值...大多数测试都是在 GCM 上完成的。我试图在一项测试中使用 CBC(没有 IV,因为它不需要 IV),以防 iOS 实际使用它而不告诉我,但如上所示,也没有成功。
我正在继续测试方法,但确实可以使用已经完成这项工作的人的一些建议 - 我无法找到工作示例。 [作为旁注,RSA 模型工作正常 - 这就是我移动 AES 密钥的方式 - 解决方案的那部分目前是完美无缺的,这是我需要开始操作的最后一点)。
编辑为欧洲央行和加拿大央行在 iOS 和 Python 之间工作的最终答案:
感谢在以下位置构建原始 NSData_AESCrypt 代码的其他人:
// AES Encrypt/Decrypt
// Created by Jim Dovey and 'Jean'
// See http://iphonedevelopment.blogspot.com/2009/02/strong-encryption-for-cocoa-cocoa-touch.html
//
// BASE64 Encoding/Decoding
// Copyright (c) 2001 Kyle Hammond. All rights reserved.
// Original development by Dave Winer.
//
// Put together by Michael Sedlaczek, Gone Coding on 2011-02-22
//
在iOS上,加密逻辑由原来的NSData+AESCrypt修改为:
@implementation NSData (AESCrypt)
- (NSData *)AES256EncryptWithKey:(NSString *)key
{
return [self AES256EncryptWithKey:key ECB:false];
}
- (NSData *)AES256EncryptWithKey:(NSString *)key ECB:(Boolean) ecb
{
// 'key' should be 32 bytes for AES256, will be null-padded otherwise
char keyPtr[kCCKeySizeAES256 + 1]; // room for terminator (unused)
bzero( keyPtr, sizeof( keyPtr ) ); // fill with zeroes (for padding)
// fetch key data
[key getCString:keyPtr maxLength:sizeof( keyPtr ) encoding:NSUTF8StringEncoding];
// create results buffer with extra space for padding
NSUInteger dataLength = [self length];
size_t bufferSize = dataLength + kCCKeySizeAES256;
void *buffer = malloc( bufferSize );
size_t numBytesEncrypted = 0;
NSString *hardCodeIV = @"1111111111111111";
char iv[17];
bzero( iv, sizeof( iv ) );
[hardCodeIV getCString:iv maxLength:sizeof(iv) encoding:NSUTF8StringEncoding];
//CBC
CCCryptorStatus cryptStatus = kCCSuccess;
if (ecb == false)
{
cryptStatus = CCCrypt( kCCEncrypt, kCCAlgorithmAES, kCCOptionPKCS7Padding,
keyPtr, kCCKeySizeAES256,
iv ,
[self bytes], dataLength,
buffer, bufferSize,
&numBytesEncrypted );
} else
{
// ECB
cryptStatus = CCCrypt( kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding | kCCOptionECBMode,
keyPtr, kCCKeySizeAES256,
NULL,
[self bytes], dataLength,
buffer, bufferSize,
&numBytesEncrypted );
}
if( cryptStatus == kCCSuccess )
{
//the returned NSData takes ownership of the buffer and will free it on deallocation
return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
}
free( buffer ); //free the buffer
return nil;
}
- (NSData *)AES256DecryptWithKey:(NSString *)key
{
return [self AES256DecryptWithKey:key ECB:false];
}
- (NSData *)AES256DecryptWithKey:(NSString *)key ECB:(Boolean) ecb
{
// 'key' should be 32 bytes for AES256, will be null-padded otherwise
char keyPtr[kCCKeySizeAES256+1]; // room for terminator (unused)
bzero( keyPtr, sizeof( keyPtr ) ); // fill with zeroes (for padding)
// fetch key data
[key getCString:keyPtr maxLength:sizeof( keyPtr ) encoding:NSUTF8StringEncoding];
NSUInteger dataLength = [self length];
size_t bufferSize = dataLength + kCCKeySizeAES256;
void *buffer = malloc( bufferSize );
size_t numBytesDecrypted = 0;
NSString *hardCodeIV = @"1111111111111111";
char iv[17];
bzero( iv, sizeof( iv ) );
[hardCodeIV getCString:iv maxLength:sizeof(iv) encoding:NSUTF8StringEncoding];
CCCryptorStatus cryptStatus = kCCSuccess;
if (ecb == false)
{
cryptStatus = CCCrypt( kCCDecrypt, kCCAlgorithmAES, kCCOptionPKCS7Padding,
keyPtr, kCCKeySizeAES256,
iv ,
[self bytes], dataLength,
buffer, bufferSize,
&numBytesDecrypted );
} else {
cryptStatus = CCCrypt( kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding | kCCOptionECBMode,
keyPtr, kCCKeySizeAES256,
NULL,
[self bytes], dataLength,
buffer, bufferSize,
&numBytesDecrypted );
}
if( cryptStatus == kCCSuccess )
{
//the returned NSData takes ownership of the buffer and will free it on deallocation
return [NSData dataWithBytesNoCopy:buffer length:numBytesDecrypted];
}
free( buffer ); //free the buffer
return nil;
}
然后使用一些助手 classes(未修改 class)将生成的 NSData 元素进行 base64 编码,如:
static char encodingTable[64] =
{
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
};
+ (NSData *)dataWithBase64EncodedString:(NSString *)string
{
return [[NSData allocWithZone:nil] initWithBase64EncodedString:string];
}
- (id)initWithBase64EncodedString:(NSString *)string
{
NSMutableData *mutableData = nil;
if( string )
{
unsigned long ixtext = 0;
unsigned long lentext = 0;
unsigned char ch = 0;
unsigned char inbuf[4], outbuf[3];
short i = 0, ixinbuf = 0;
BOOL flignore = NO;
BOOL flendtext = NO;
NSData *base64Data = nil;
const unsigned char *base64Bytes = nil;
// Convert the string to ASCII data.
base64Data = [string dataUsingEncoding:NSASCIIStringEncoding];
base64Bytes = [base64Data bytes];
mutableData = [NSMutableData dataWithCapacity:base64Data.length];
lentext = base64Data.length;
while( YES )
{
if( ixtext >= lentext ) break;
ch = base64Bytes[ixtext++];
flignore = NO;
if( ( ch >= 'A' ) && ( ch <= 'Z' ) ) ch = ch - 'A';
else if( ( ch >= 'a' ) && ( ch <= 'z' ) ) ch = ch - 'a' + 26;
else if( ( ch >= '0' ) && ( ch <= '9' ) ) ch = ch - '0' + 52;
else if( ch == '+' ) ch = 62;
else if( ch == '=' ) flendtext = YES;
else if( ch == '/' ) ch = 63;
else flignore = YES;
if( ! flignore )
{
short ctcharsinbuf = 3;
BOOL flbreak = NO;
if( flendtext )
{
if( ! ixinbuf ) break;
if( ( ixinbuf == 1 ) || ( ixinbuf == 2 ) ) ctcharsinbuf = 1;
else ctcharsinbuf = 2;
ixinbuf = 3;
flbreak = YES;
}
inbuf [ixinbuf++] = ch;
if( ixinbuf == 4 )
{
ixinbuf = 0;
outbuf [0] = ( inbuf[0] << 2 ) | ( ( inbuf[1] & 0x30) >> 4 );
outbuf [1] = ( ( inbuf[1] & 0x0F ) << 4 ) | ( ( inbuf[2] & 0x3C ) >> 2 );
outbuf [2] = ( ( inbuf[2] & 0x03 ) << 6 ) | ( inbuf[3] & 0x3F );
for( i = 0; i < ctcharsinbuf; i++ )
[mutableData appendBytes:&outbuf[i] length:1];
}
if( flbreak ) break;
}
}
}
self = [self initWithData:mutableData];
return self;
}
#pragma mark -
- (NSString *)base64Encoding
{
return [self base64EncodingWithLineLength:0];
}
- (NSString *)base64EncodingWithLineLength:(NSUInteger)lineLength
{
const unsigned char *bytes = [self bytes];
NSMutableString *result = [NSMutableString stringWithCapacity:self.length];
unsigned long ixtext = 0;
unsigned long lentext = self.length;
long ctremaining = 0;
unsigned char inbuf[3], outbuf[4];
unsigned short i = 0;
unsigned short charsonline = 0, ctcopy = 0;
unsigned long ix = 0;
while( YES )
{
ctremaining = lentext - ixtext;
if( ctremaining <= 0 ) break;
for( i = 0; i < 3; i++ )
{
ix = ixtext + i;
if( ix < lentext ) inbuf[i] = bytes[ix];
else inbuf [i] = 0;
}
outbuf [0] = (inbuf [0] & 0xFC) >> 2;
outbuf [1] = ((inbuf [0] & 0x03) << 4) | ((inbuf [1] & 0xF0) >> 4);
outbuf [2] = ((inbuf [1] & 0x0F) << 2) | ((inbuf [2] & 0xC0) >> 6);
outbuf [3] = inbuf [2] & 0x3F;
ctcopy = 4;
switch( ctremaining )
{
case 1:
ctcopy = 2;
break;
case 2:
ctcopy = 3;
break;
}
for( i = 0; i < ctcopy; i++ )
[result appendFormat:@"%c", encodingTable[outbuf[i]]];
for( i = ctcopy; i < 4; i++ )
[result appendString:@"="];
ixtext += 3;
charsonline += 4;
if( lineLength > 0 )
{
if( charsonline >= lineLength )
{
charsonline = 0;
[result appendString:@"\n"];
}
}
}
return [NSString stringWithString:result];
}
然后将生成的 base64 编码字符串发送到云端,python pycryptodome 可以将其解密为:
def aesCBCEncrypt(self,key,stri):
if isinstance(key, str):
key = key.encode('utf-8')
cipher = AES.new(key, AES.MODE_CBC, self.nonce) # , nonce=self.nonce)
if isinstance(stri, str):
data = stri.encode('utf-8')
try:
data = pad(data, 16)
ciphertext = cipher.encrypt(data)
ciphertext = b64encode(ciphertext)
ret = ciphertext.decode('utf-8')
except:
print("Some Error")
ret = ""
return ret
def aesCBCDecrypt(self,key,data):
if isinstance(key, str):
key = key.encode('utf-8')
cipher = AES.new(key, AES.MODE_CBC, self.nonce) # , nonce=self.nonce)
if isinstance(data, str):
data = data.encode('utf-8')
data = b64decode(data)
try:
result = cipher.decrypt(data)
result = unpad(result, 16)
ret = result.decode('utf-8')
except:
print("Some Error")
ret = ""
return ret
def aesECBEncrypt(self,key,stri):
if isinstance(key, str):
key = key.encode('utf-8')
cipher = AES.new(key, AES.MODE_ECB) # , nonce=self.nonce)
if isinstance(stri, str):
data = stri.encode('utf-8')
try:
data = pad(data,16)
ciphertext = cipher.encrypt(data)
ciphertext = b64encode(ciphertext)
ret = ciphertext.decode('utf-8')
except:
print("Some Error")
ret = ""
return ret
def aesECBDecrypt(self,key,data):
if isinstance(key, str):
key = key.encode('utf-8')
cipher = AES.new(key, AES.MODE_ECB) #, nonce=self.nonce)
if isinstance(data, str):
data = data.encode('utf-8')
data = b64decode(data)
try:
result = cipher.decrypt(data)
result = unpad(result,16)
ret = result.decode('utf-8')
except:
print("Some Error")
ret = ""
return ret
使用 CBC 时,iOS 和 Python 上的 IV(又名随机数)相同非常重要 - 否则它将无法工作。在这种情况下,这被设置为一个由 16 个“1”字符组成的字符串,以空值结尾。这本身并不是一个真正的秘密密钥,但可能值得更改它并保护它(可能还使用非对称发送它,在我的例子中是 RSA,加密)。然而,AES 是关键密钥,当然应该在设备之间加密发送。
最后,我建议使用 CBC,尽管需要考虑 IV,因为它更安全。当我有时间时,我将研究集成 Swift 只有 Apple Crypto Kit 库以支持其他形式......
我在 iOS (CCCrypt
) 和 python (pycryptdome
) 上都具有使用 AES(128 和 256)encrypt/decrypt 的功能。所有测试用例都在每个平台上工作,但是......当我使用 AES 密钥和从 iOS 到 python 的加密字符串时,解密失败。我已经广泛查看并尝试了各种用例都无济于事。
我在这里创建了一个简单的测试用例,其中包含 iOS 加密和 python 解密,希望有人能告诉我我在平台上所做的不同之处。
iOS代码 测试用例
NSString *test_aes = @"XSmTe1Eyw8JsZkreIFUpNi7BhKEReHTP";
NSString *test_string = @"This is a test string";
NSData *clearPayload = [test_string dataUsingEncoding:NSUTF8StringEncoding];
NSData *encPayload = nil;
char keyPtr[kCCKeySizeAES256 + 1]; // room for terminator (unused)
bzero( keyPtr, sizeof( keyPtr ) ); // fill with zeroes (for padding)
// fetch key data
[test_aes getCString:keyPtr maxLength:sizeof( keyPtr ) encoding:NSUTF8StringEncoding];
NSUInteger dataLength = clearPayload.length;
size_t bufferSize = dataLength + kCCKeySizeAES256;
void *buffer = malloc( bufferSize );
size_t numBytesEncrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt( kCCEncrypt, kCCAlgorithmAES, kCCOptionPKCS7Padding,
keyPtr, kCCKeySizeAES256,
NULL /* initialization vector (optional) */,
[clearPayload bytes], dataLength, /* input */
buffer, bufferSize, /* output */
&numBytesEncrypted );
NSString *encString = @"Error";
if( cryptStatus == kCCSuccess )
{
//the returned NSData takes ownership of the buffer and will free it on deallocation
encPayload = [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
encString = [encPayload base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
}
//free( buffer ); //free the buffer
NSLog(@"Src = %@ AES = %@ String = %@",test_string, test_aes, encString);
encPayload = [[NSData alloc] initWithBase64EncodedString:encString options:NSDataBase64DecodingIgnoreUnknownCharacters];
clearPayload = nil;
char keyPtr2[kCCKeySizeAES256+1]; // room for terminator (unused)
bzero( keyPtr2, sizeof( keyPtr2 ) ); // fill with zeroes (for padding)
// fetch key data
[test_aes getCString:keyPtr2 maxLength:sizeof( keyPtr2 ) encoding:NSUTF8StringEncoding];
NSUInteger dataLength2 = [encPayload length];
//See the doc: For block ciphers, the output size will always be less than or
//equal to the input size plus the size of one block.
//That's why we need to add the size of one block here
size_t bufferSize2 = dataLength2 + kCCKeySizeAES256;
void *buffer2 = malloc( bufferSize2 );
size_t numBytesDecrypted = 0;
CCCryptorStatus cryptStatus2 = CCCrypt( kCCDecrypt, kCCAlgorithmAES, kCCOptionPKCS7Padding,
keyPtr, kCCKeySizeAES256,
NULL /* initialization vector (optional) */,
[encPayload bytes], dataLength2, /* input */
buffer2, bufferSize2, /* output */
&numBytesDecrypted );
NSString *clearString = @"Error";
if( cryptStatus2 == kCCSuccess )
{
//the returned NSData takes ownership of the buffer and will free it on deallocation
clearPayload = [NSData dataWithBytesNoCopy:buffer2 length:numBytesDecrypted];
clearString = [[NSString alloc] initWithData:clearPayload encoding:NSUTF8StringEncoding];
}
NSLog(@"Res = %@",clearString);
这段代码中的加密和解密工作正常,输出为:
Src = This is a test string
AES = XSmTe1Eyw8JsZkreIFUpNi7BhKEReHTP
String = hUbjWyXX4mB01gI0RJhYQRD0iAjQnkGTpsnKcmDpvaQ=
Res = This is a test string
当我将编码字符串和 aes 密钥带到 python 以使用此代码进行测试时:
key = "XSmTe1Eyw8JsZkreIFUpNi7BhKEReHTP"
data = "hUbjWyXX4mB01gI0RJhYQRD0iAjQnkGTpsnKcmDpvaQ="
usekey = key
useData = data
if isinstance(key, str):
usekey = key.encode('utf-8')
cipher = AES.new(usekey, AES.MODE_GCM, nonce=self.nonce)
print("nonce", cipher.nonce)
if isinstance(data, str):
useData = data.encode('utf-8')
useData = b64decode(useData)
puseData = useData # unpad(useData,32)
print("decrypt:In bytes=", puseData)
result = cipher.decrypt(puseData)
print ("decrypt:Out bytes=",result)
解密失败,输出为
nonce b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
decrypt:In bytes= b'\x85F\xe3[%\xd7\xe2`t\xd6\x024D\x98XA\x10\xf4\x88\x08\xd0\x9eA\x93\xa6\xc9\xcar`\xe9\xbd\xa4'
decrypt:Out bytes= b'\x08\xc58\x962q\x94\xff#\xfa\xab\xe2\xc8{b\xed\x0b\xedw\x8f\xe3\xec\x0b\x8e\xfb\xcc\x12\x7f\x9e\xb4\x8f\xd6'
以上两个例程都可以毫无问题地处理本地加密数据,我已经破解了这里的示例(包括不释放 malloc 缓冲区 :) 用于调试目的,所以我为有点脏的代码道歉。
注意:我曾尝试将 python 模式更改为 AES.MODE_CBC(并添加了填充代码),当我看到注释 iOS 可能会使用它而不是 GCM,这也失败了... 现在我将 nonce / iv 保留为 0 的数组,因为我被告知 iOS 将使用它作为默认值,因为没有提供 CCCrypt,当这个例子工作时我将过渡到指定iv.
如有任何指示,我将不胜感激。
编辑: 我继续并在 iOS 侧指定了一个空 IV
char iv[16]; // also tried 17
bzero( iv, sizeof( iv ) );
行为完全没有变化...
编辑: 我在两个系统上将 IV 设置为所有字符“1”并得到相同的结果。 iOS 添加代码:
NSString *hardCodeIV = @"1111111111111111";
char iv[17];
bzero( iv, sizeof( iv ) );
[hardCodeIV getCString:iv maxLength:sizeof(iv) encoding:NSUTF8StringEncoding];
产生了
Src = This is a test string
AES = XSmTe1Eyw8JsZkreIFUpNi7BhKEReHTP
String = sFoZ24VRN1hyMzegXT+GFzAn/YGPvaKO8p1eD+xhGaU=
Res = This is a test string
因此 iOS 它使用字节 0 和字符 1 IV 正确加密和解密....
并且 python 代码在使用 IV 在本地加密和解密时也能正常工作...但是当 iOS 加密的输出用于 python 解密时失败如此处所示。
将密钥和加密消息移动到 python 以解密为:
key = "XSmTe1Eyw8JsZkreIFUpNi7BhKEReHTP"
data = "sFoZ24VRN1hyMzegXT+GFzAn/YGPvaKO8p1eD+xhGaU="
usekey = key
useData = data
if isinstance(key, str):
usekey = key.encode('utf-8')
cipher = AES.new(usekey, AES.MODE_GCM, nonce=self.nonce)
print("nonce", cipher.nonce)
if isinstance(data, str):
useData = data.encode('utf-8')
useData = b64decode(useData)
puseData = useData # unpad(useData,32)
print("decrypt:In bytes=", puseData)
result = cipher.decrypt(puseData)
print ("decrypt:Out bytes=",result)
结果:
nonce b'1111111111111111'
decrypt:In bytes= b"\xb0Z\x19\xdb\x85Q7Xr37\xa0]?\x86\x170'\xfd\x81\x8f\xbd\xa2\x8e\xf2\x9d^\x0f\xeca\x19\xa5"
decrypt:Out bytes= b'\xc3\x1e"w\x86:~\x86\xd3\xc9H3\xd3\xd3y)|,|\xe02(\xc6\x17\xa3\x1e\xe2\x0f\x1a#\xbbW'
所以,还是不开心...
看起来很像是算法选择的问题,但 iOS 上的选项似乎只有 GCM 或 CBC,GCM 是默认值...大多数测试都是在 GCM 上完成的。我试图在一项测试中使用 CBC(没有 IV,因为它不需要 IV),以防 iOS 实际使用它而不告诉我,但如上所示,也没有成功。
我正在继续测试方法,但确实可以使用已经完成这项工作的人的一些建议 - 我无法找到工作示例。 [作为旁注,RSA 模型工作正常 - 这就是我移动 AES 密钥的方式 - 解决方案的那部分目前是完美无缺的,这是我需要开始操作的最后一点)。
编辑为欧洲央行和加拿大央行在 iOS 和 Python 之间工作的最终答案:
感谢在以下位置构建原始 NSData_AESCrypt 代码的其他人:
// AES Encrypt/Decrypt
// Created by Jim Dovey and 'Jean'
// See http://iphonedevelopment.blogspot.com/2009/02/strong-encryption-for-cocoa-cocoa-touch.html
//
// BASE64 Encoding/Decoding
// Copyright (c) 2001 Kyle Hammond. All rights reserved.
// Original development by Dave Winer.
//
// Put together by Michael Sedlaczek, Gone Coding on 2011-02-22
//
在iOS上,加密逻辑由原来的NSData+AESCrypt修改为:
@implementation NSData (AESCrypt)
- (NSData *)AES256EncryptWithKey:(NSString *)key
{
return [self AES256EncryptWithKey:key ECB:false];
}
- (NSData *)AES256EncryptWithKey:(NSString *)key ECB:(Boolean) ecb
{
// 'key' should be 32 bytes for AES256, will be null-padded otherwise
char keyPtr[kCCKeySizeAES256 + 1]; // room for terminator (unused)
bzero( keyPtr, sizeof( keyPtr ) ); // fill with zeroes (for padding)
// fetch key data
[key getCString:keyPtr maxLength:sizeof( keyPtr ) encoding:NSUTF8StringEncoding];
// create results buffer with extra space for padding
NSUInteger dataLength = [self length];
size_t bufferSize = dataLength + kCCKeySizeAES256;
void *buffer = malloc( bufferSize );
size_t numBytesEncrypted = 0;
NSString *hardCodeIV = @"1111111111111111";
char iv[17];
bzero( iv, sizeof( iv ) );
[hardCodeIV getCString:iv maxLength:sizeof(iv) encoding:NSUTF8StringEncoding];
//CBC
CCCryptorStatus cryptStatus = kCCSuccess;
if (ecb == false)
{
cryptStatus = CCCrypt( kCCEncrypt, kCCAlgorithmAES, kCCOptionPKCS7Padding,
keyPtr, kCCKeySizeAES256,
iv ,
[self bytes], dataLength,
buffer, bufferSize,
&numBytesEncrypted );
} else
{
// ECB
cryptStatus = CCCrypt( kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding | kCCOptionECBMode,
keyPtr, kCCKeySizeAES256,
NULL,
[self bytes], dataLength,
buffer, bufferSize,
&numBytesEncrypted );
}
if( cryptStatus == kCCSuccess )
{
//the returned NSData takes ownership of the buffer and will free it on deallocation
return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
}
free( buffer ); //free the buffer
return nil;
}
- (NSData *)AES256DecryptWithKey:(NSString *)key
{
return [self AES256DecryptWithKey:key ECB:false];
}
- (NSData *)AES256DecryptWithKey:(NSString *)key ECB:(Boolean) ecb
{
// 'key' should be 32 bytes for AES256, will be null-padded otherwise
char keyPtr[kCCKeySizeAES256+1]; // room for terminator (unused)
bzero( keyPtr, sizeof( keyPtr ) ); // fill with zeroes (for padding)
// fetch key data
[key getCString:keyPtr maxLength:sizeof( keyPtr ) encoding:NSUTF8StringEncoding];
NSUInteger dataLength = [self length];
size_t bufferSize = dataLength + kCCKeySizeAES256;
void *buffer = malloc( bufferSize );
size_t numBytesDecrypted = 0;
NSString *hardCodeIV = @"1111111111111111";
char iv[17];
bzero( iv, sizeof( iv ) );
[hardCodeIV getCString:iv maxLength:sizeof(iv) encoding:NSUTF8StringEncoding];
CCCryptorStatus cryptStatus = kCCSuccess;
if (ecb == false)
{
cryptStatus = CCCrypt( kCCDecrypt, kCCAlgorithmAES, kCCOptionPKCS7Padding,
keyPtr, kCCKeySizeAES256,
iv ,
[self bytes], dataLength,
buffer, bufferSize,
&numBytesDecrypted );
} else {
cryptStatus = CCCrypt( kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding | kCCOptionECBMode,
keyPtr, kCCKeySizeAES256,
NULL,
[self bytes], dataLength,
buffer, bufferSize,
&numBytesDecrypted );
}
if( cryptStatus == kCCSuccess )
{
//the returned NSData takes ownership of the buffer and will free it on deallocation
return [NSData dataWithBytesNoCopy:buffer length:numBytesDecrypted];
}
free( buffer ); //free the buffer
return nil;
}
然后使用一些助手 classes(未修改 class)将生成的 NSData 元素进行 base64 编码,如:
static char encodingTable[64] =
{
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
};
+ (NSData *)dataWithBase64EncodedString:(NSString *)string
{
return [[NSData allocWithZone:nil] initWithBase64EncodedString:string];
}
- (id)initWithBase64EncodedString:(NSString *)string
{
NSMutableData *mutableData = nil;
if( string )
{
unsigned long ixtext = 0;
unsigned long lentext = 0;
unsigned char ch = 0;
unsigned char inbuf[4], outbuf[3];
short i = 0, ixinbuf = 0;
BOOL flignore = NO;
BOOL flendtext = NO;
NSData *base64Data = nil;
const unsigned char *base64Bytes = nil;
// Convert the string to ASCII data.
base64Data = [string dataUsingEncoding:NSASCIIStringEncoding];
base64Bytes = [base64Data bytes];
mutableData = [NSMutableData dataWithCapacity:base64Data.length];
lentext = base64Data.length;
while( YES )
{
if( ixtext >= lentext ) break;
ch = base64Bytes[ixtext++];
flignore = NO;
if( ( ch >= 'A' ) && ( ch <= 'Z' ) ) ch = ch - 'A';
else if( ( ch >= 'a' ) && ( ch <= 'z' ) ) ch = ch - 'a' + 26;
else if( ( ch >= '0' ) && ( ch <= '9' ) ) ch = ch - '0' + 52;
else if( ch == '+' ) ch = 62;
else if( ch == '=' ) flendtext = YES;
else if( ch == '/' ) ch = 63;
else flignore = YES;
if( ! flignore )
{
short ctcharsinbuf = 3;
BOOL flbreak = NO;
if( flendtext )
{
if( ! ixinbuf ) break;
if( ( ixinbuf == 1 ) || ( ixinbuf == 2 ) ) ctcharsinbuf = 1;
else ctcharsinbuf = 2;
ixinbuf = 3;
flbreak = YES;
}
inbuf [ixinbuf++] = ch;
if( ixinbuf == 4 )
{
ixinbuf = 0;
outbuf [0] = ( inbuf[0] << 2 ) | ( ( inbuf[1] & 0x30) >> 4 );
outbuf [1] = ( ( inbuf[1] & 0x0F ) << 4 ) | ( ( inbuf[2] & 0x3C ) >> 2 );
outbuf [2] = ( ( inbuf[2] & 0x03 ) << 6 ) | ( inbuf[3] & 0x3F );
for( i = 0; i < ctcharsinbuf; i++ )
[mutableData appendBytes:&outbuf[i] length:1];
}
if( flbreak ) break;
}
}
}
self = [self initWithData:mutableData];
return self;
}
#pragma mark -
- (NSString *)base64Encoding
{
return [self base64EncodingWithLineLength:0];
}
- (NSString *)base64EncodingWithLineLength:(NSUInteger)lineLength
{
const unsigned char *bytes = [self bytes];
NSMutableString *result = [NSMutableString stringWithCapacity:self.length];
unsigned long ixtext = 0;
unsigned long lentext = self.length;
long ctremaining = 0;
unsigned char inbuf[3], outbuf[4];
unsigned short i = 0;
unsigned short charsonline = 0, ctcopy = 0;
unsigned long ix = 0;
while( YES )
{
ctremaining = lentext - ixtext;
if( ctremaining <= 0 ) break;
for( i = 0; i < 3; i++ )
{
ix = ixtext + i;
if( ix < lentext ) inbuf[i] = bytes[ix];
else inbuf [i] = 0;
}
outbuf [0] = (inbuf [0] & 0xFC) >> 2;
outbuf [1] = ((inbuf [0] & 0x03) << 4) | ((inbuf [1] & 0xF0) >> 4);
outbuf [2] = ((inbuf [1] & 0x0F) << 2) | ((inbuf [2] & 0xC0) >> 6);
outbuf [3] = inbuf [2] & 0x3F;
ctcopy = 4;
switch( ctremaining )
{
case 1:
ctcopy = 2;
break;
case 2:
ctcopy = 3;
break;
}
for( i = 0; i < ctcopy; i++ )
[result appendFormat:@"%c", encodingTable[outbuf[i]]];
for( i = ctcopy; i < 4; i++ )
[result appendString:@"="];
ixtext += 3;
charsonline += 4;
if( lineLength > 0 )
{
if( charsonline >= lineLength )
{
charsonline = 0;
[result appendString:@"\n"];
}
}
}
return [NSString stringWithString:result];
}
然后将生成的 base64 编码字符串发送到云端,python pycryptodome 可以将其解密为:
def aesCBCEncrypt(self,key,stri):
if isinstance(key, str):
key = key.encode('utf-8')
cipher = AES.new(key, AES.MODE_CBC, self.nonce) # , nonce=self.nonce)
if isinstance(stri, str):
data = stri.encode('utf-8')
try:
data = pad(data, 16)
ciphertext = cipher.encrypt(data)
ciphertext = b64encode(ciphertext)
ret = ciphertext.decode('utf-8')
except:
print("Some Error")
ret = ""
return ret
def aesCBCDecrypt(self,key,data):
if isinstance(key, str):
key = key.encode('utf-8')
cipher = AES.new(key, AES.MODE_CBC, self.nonce) # , nonce=self.nonce)
if isinstance(data, str):
data = data.encode('utf-8')
data = b64decode(data)
try:
result = cipher.decrypt(data)
result = unpad(result, 16)
ret = result.decode('utf-8')
except:
print("Some Error")
ret = ""
return ret
def aesECBEncrypt(self,key,stri):
if isinstance(key, str):
key = key.encode('utf-8')
cipher = AES.new(key, AES.MODE_ECB) # , nonce=self.nonce)
if isinstance(stri, str):
data = stri.encode('utf-8')
try:
data = pad(data,16)
ciphertext = cipher.encrypt(data)
ciphertext = b64encode(ciphertext)
ret = ciphertext.decode('utf-8')
except:
print("Some Error")
ret = ""
return ret
def aesECBDecrypt(self,key,data):
if isinstance(key, str):
key = key.encode('utf-8')
cipher = AES.new(key, AES.MODE_ECB) #, nonce=self.nonce)
if isinstance(data, str):
data = data.encode('utf-8')
data = b64decode(data)
try:
result = cipher.decrypt(data)
result = unpad(result,16)
ret = result.decode('utf-8')
except:
print("Some Error")
ret = ""
return ret
使用 CBC 时,iOS 和 Python 上的 IV(又名随机数)相同非常重要 - 否则它将无法工作。在这种情况下,这被设置为一个由 16 个“1”字符组成的字符串,以空值结尾。这本身并不是一个真正的秘密密钥,但可能值得更改它并保护它(可能还使用非对称发送它,在我的例子中是 RSA,加密)。然而,AES 是关键密钥,当然应该在设备之间加密发送。
最后,我建议使用 CBC,尽管需要考虑 IV,因为它更安全。当我有时间时,我将研究集成 Swift 只有 Apple Crypto Kit 库以支持其他形式......