【问题标题】:AES encryption between iOS and PythoniOS 和 Python 之间的 AES 加密
【发布时间】:2021-07-07 21:09:38
【问题描述】:

我有在 iOS (CCCrypt) 和 python (pycryptdome) 上使用 AES (128 和 256) 加密/解密的功能。所有测试用例都在每个平台上工作,但是......当我从 iOS 获取 AES 密钥和加密字符串到 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 的缓冲区:) 用于调试目的,所以我为有点脏的代码道歉。

注意:当我看到注释 iOS 可能使用它而不是 GCM 时,我尝试将 python 模式更改为 AES.MODE_CBC(并添加了填充代码),这也失败了......现在我保留了 nonce / iv作为 0 的数组,据我所知,iOS 将使用它作为默认值,因为没有提供 CCCrypt,当此示例有效时,我将转换到指定的 iv。

我会很感激任何方向。

编辑: 我继续并在 iOS 端指定了一个空 IV

    char iv[16]; // also tried 17
    bzero( iv, sizeof( iv ) );

行为完全没有变化...

编辑: 我在两个系统上将 IV 设置为所有 char '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 正确加密和解​​密......

当使用任何一种 IV 在本地加密和解密时,python 代码也能正常工作......但是当在 python 上使用 iOS 加密的输出来解密时,它会失败,如下所示。

将密钥和加密消息移动到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,因为它不需要),以防 iOS 实际使用它并且没有告诉我,但如上所示,这也没有成功。

我正在继续测试方法,但确实可以使用完成这项工作的人的一些建议 - 我无法找到有效的示例。 [附带说明,RSA 模型工作正常 - 这就是我移动 AES 密钥的方式 - 解决方案的那部分目前完美无缺,这是我需要投入使用的最后一点)。

【问题讨论】:

    标签: python ios cryptography aes


    【解决方案1】:

    在 ECB 和 CBC 在 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;
    }
    

    生成的 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 时,IV(又名 nonce)在 iOS 和 Python 上是相同的,这一点非常重要 - 否则它将无法工作。在这种情况下,这被设置为以空值结尾的 16 个“1”字符的字符串。这本身并不是真正的密钥,但可能值得对其进行更改和保护(可能也使用非对称发送它,在我的情况下是 RSA,加密)。然而,AES 是关键密钥,当然应该在设备之间加密发送。

    最后,即使需要考虑 IV,我还是建议使用 CBC,因为它更安全。当我有时间时,我会考虑集成 Swift 唯一的 Apple Crypto Kit 库以支持其他形式......

    【讨论】:

      猜你喜欢
      • 2017-05-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-23
      • 2012-05-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多