【问题标题】:How can I do this same encrypt/decrypt PHP function on iOS with Objective-C?如何使用 Objective-C 在 iOS 上执行相同的加密/解密 PHP 函数?
【发布时间】:2014-05-30 03:31:19
【问题描述】:

我在 PHP 中有一个加密和解密字符串的函数:

function encrypt_decrypt($action, $string) 
{
   $output = false;
   $key = 'mykeyhereblah';
   $iv = md5(md5($key));
   if( $action == 'encrypt' ) {
       $output = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, md5($key), $string, MCRYPT_MODE_CBC, $iv);
       $output = base64_encode($output);
   }
   else if( $action == 'decrypt' ){
       $output = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, md5($key), base64_decode($string), MCRYPT_MODE_CBC, $iv);
       $output = rtrim($output, "");
   }
   return $output;
}

我这样称呼它:

echo encrypt_decrypt('decrypt', '2Fa9cICuUFa/UnmAAa5FjXZK4ht9q3cN2qgk1pCvDSs=');

如何在 iOS 上使用 Objective-C 为 NSString 做同样的事情?需要兼容这个PHP函数。

【问题讨论】:

  • 尝试在google中搜索objectiveC的AES加密
  • encrypt_decrypt($action)Oo
  • 为一个简单的问题投入了太多的赏金,不确定 StackOverflow 发生了什么
  • 如果您有机会放弃此代码,请这样做。它是不安全的(对具有相同密钥的每条消息使用相同的 IV,IV 的一半为零,因为默认情况下 mcrypt 零填充太短的 IV,密钥不是使用合理的 PBKDF 导出的,并且零填充到 256 位长度),并使用了目前无人实现的神秘 Rijndael 变体(块大小为 256 位)。

标签: php ios objective-c encryption


【解决方案1】:

为什么不使用 与 PHP 相同的 mcrypt

问题是 Rijndael 不完全是 AES,所以我不确定其他解决方案是否真的可以在这里工作。 Rijndael 允许不同的块大小和密钥对,AES 是 Rijndael 的一个特例,密钥大小为 128、192 和 256,但块大小始终为 128。所以使用与 PHP 相同的 mcrypt 将保证相同的结果。

这个 C++ 示例正是您所需要的,这是输出:

plain text: the book is on the table!
cipher text: dGhlIGJvb2sgaXMgb24gdGhlIHRhYmxlIQ==
back to: the book is on the table!

sample cipher text: 2Fa9cICuUFa/UnmAAa5FjXZK4ht9q3cN2qgk1pCvDSs=
sample plain text: “:F‚m&X”Öwÿ ï@í`D’ühà¢äè"˜‚)

您的示例输出与您的 PHP 代码完全相同(只需对其进行测试!:-))。下面的代码是自己编译的。

  • 需要你编译libmcrypt(我在这个示例中使用的是2.5.8版本)
  • 它还需要 OpenSSL 用于 md5 和 Base64 函数(但您可以跳过...

...请注意,我仅将 OpenSSL 用于 md5() 和我的 Base64 类,这与我用于许多事情的相同,但您可以替换为其他 md5/base64 解决方案,然后您会得到摆脱 OpenSSL。很容易。 Apple 正在转向 CommonCrypto...

/////////////////////////
// Base64.h

#ifndef BASE64_H
#define BASE64_H

#include <string>
#include <vector>

class Base64
{
public:
    static std::string encode( const unsigned char * p_buffer, size_t p_size );

    static std::string encode( const std::string & p_string );

    static std::string encode( const std::vector< unsigned char > & p_buffer );

    static std::string decode( const std::string & p_input );

    static void decode( const std::string & p_input, std::vector< unsigned char > & p_output );
};

#endif // BASE64_H

/////////////////////////
// Base64.cpp

//#include "Base64.h"

#include <openssl/evp.h>

using namespace std;

string Base64::encode( const unsigned char * p_buffer, size_t p_size )
{
    unsigned char * output( new unsigned char[ p_size * 4 / 3 + 4 ] );

    size_t outputLength( EVP_EncodeBlock( output, p_buffer, static_cast< int >( p_size ) ) );

    string ret( reinterpret_cast< char * >( output ), outputLength );

    delete [] output;

    return ret;
}

string Base64::encode( const string & p_string )
{
    return Base64::encode( reinterpret_cast< const unsigned char * >( p_string.c_str() ), p_string.size() );
}

string Base64::encode( const vector< unsigned char > & p_buffer )
{
    return Base64::encode( &p_buffer[ 0 ], p_buffer.size() );
}

void Base64::decode( const string & p_input, vector< unsigned char > & p_output )
{
    p_output.resize( p_input.length() * 3 / 4 );

    size_t outputLength( EVP_DecodeBlock( &p_output[ 0 ], reinterpret_cast< const unsigned char * >( p_input.c_str() ), static_cast< int >( p_input.size() ) ) );

    size_t length( p_input.length() );

    if ( p_input[ length - 2 ] == '=' )
    {
        outputLength -= 2;
    }
    else if ( p_input[ length - 1 ] == '=' )
    {
        outputLength--;
    }

    p_output.resize( outputLength );
}

string Base64::decode( const string & p_input )
{
    vector< unsigned char > output;
    Base64::decode( p_input, output );
    return reinterpret_cast< const char * >( &output[ 0 ] );
}

/////////////////////////
// main.cpp

#include <iostream>
#include <string>
#include <regex>

#include <openssl/evp.h>
#include <mcrypt.h>

#define MCRYPT_MODE_CBC "cbc"

using namespace std;

string md5( const string & p_string )
{
    EVP_MD_CTX mdContext;
    const EVP_MD * md;
    unsigned int outputLength;
    unsigned char output[ 16 ];

    OpenSSL_add_all_digests();

    if ( !( md = EVP_get_digestbyname( "MD5" ) ) )
    {
        throw std::runtime_error( "Unable to init MD5 digest." );
    }

    EVP_MD_CTX_init( &mdContext );
    EVP_DigestInit_ex( &mdContext, md, 0 );
    EVP_DigestUpdate( &mdContext, p_string.c_str(), p_string.length() );
    EVP_DigestFinal_ex( &mdContext, output, &outputLength );
    EVP_MD_CTX_cleanup( &mdContext );

    char outputString[ sizeof( output ) * 2 + 1 ];

    for ( int i( 0 ); i < sizeof( output ); ++i )
    {
        snprintf( outputString + i * 2, 2 + 1, "%02x", output[ i ] );
    }

    return outputString;
}

string trimString( const string & p_string )
{
    string ret( p_string );

    regex functionRegex( "\\s*(.*)\\s*", regex_constants::icase );
    smatch matches;

    if ( regex_search( p_string, matches, functionRegex ) )
    {
        ret = matches[ 1 ].str();
    }

    return ret;
}

void mcrypt_encrypt( vector< unsigned char > & p_output, const char * p_cryptEngine, const string & p_key, const vector< unsigned char > & p_input, const char * p_mode, const string & p_iv )
{
    MCRYPT td = mcrypt_module_open( ( char * )p_cryptEngine, 0, ( char * )p_mode, 0 );

    if ( td == MCRYPT_FAILED )
    {
        throw std::runtime_error( "can't init mcrypt" );
    }

    if ( mcrypt_generic_init( td, ( char * )p_key.c_str(), mcrypt_enc_get_key_size( td ), ( char * )p_iv.c_str() ) < 0 )
    {
        throw std::runtime_error( "can't setup key/iv" );
    }

    p_output.reserve( p_input.size() );
    copy( p_input.begin(), p_input.end(), back_inserter( p_output ) );

    mcrypt_generic( td, ( void * )&p_output[ 0 ], (int)p_output.size() );

    mcrypt_generic_end( td );
}

void mcrypt_decrypt( vector< unsigned char > & p_output, const char * p_cryptEngine, const string & p_key, const vector< unsigned char > & p_input, const char * p_mode, const string & p_iv )
{
    MCRYPT td = mcrypt_module_open( ( char * )p_cryptEngine, 0, ( char * )p_mode, 0 );

    if ( td == MCRYPT_FAILED )
    {
        throw std::runtime_error( "can't init mcrypt" );
    }

    if ( mcrypt_generic_init( td, ( char * )p_key.c_str(), mcrypt_enc_get_key_size( td ), ( char * )p_iv.c_str() ) < 0 )
    {
        throw std::runtime_error( "can't setup key/iv" );
    }

    p_output.reserve( p_input.size() );
    copy( p_input.begin(), p_input.end(), back_inserter( p_output ) );

    mdecrypt_generic( td, ( void * )&p_output[ 0 ], (int)p_output.size() );

    mcrypt_generic_end( td );
}

string encrypt_decrypt( const string & action, const string & p_string )
{
    string output = "";

    string key = "mykeyhereblah";
    string iv = md5( md5( key ) );

    vector< unsigned char > cipherText, plainText;

    if ( action == "encrypt" )
    {
        copy( p_string.begin(), p_string.end(), back_inserter( plainText ) );

        mcrypt_encrypt( cipherText, MCRYPT_RIJNDAEL_256, md5( key ), plainText, MCRYPT_MODE_CBC, iv );

        output = Base64::encode( cipherText );
    }
    else if ( action == "decrypt" )
    {
        Base64::decode( p_string, cipherText );
        mcrypt_decrypt( plainText, MCRYPT_RIJNDAEL_256, md5( key ), cipherText, MCRYPT_MODE_CBC, iv );

        output = string( ( char* )&plainText[ 0 ], plainText.size() );
        output = trimString( output );
    }

    return output;
}

int main( int argc, char * argv[] )
{
    string plainText = "the book is on the table!";
    string cipherText = encrypt_decrypt( "encrypt", plainText );

    cout << "plain text: " << plainText << endl;
    cout << "cipher text: " << cipherText << endl;
    cout << "back to: " << encrypt_decrypt( "decrypt", cipherText ) << endl;
    cout << endl;
    cout << "your sample: " << encrypt_decrypt( "decrypt", "2Fa9cICuUFa/UnmAAa5FjXZK4ht9q3cN2qgk1pCvDSs=" ) << endl;

    return 0;
}

【讨论】:

  • 瓦格纳是对的。问题是块大小。 CCCrypt 通过使用 256 位密钥实现 AES256,但 iv 和块大小仍然是 128 位。另一方面,mcrypt_encrypt:MCRYPT_RIJNDAEL_256 使用 256 位密钥、iv 和块大小。
  • OP 要求提供一个客观的 C 解决方案,您在这里提供的是一个 C++ 解决方案,它使用 iOS 中开箱即用的 openssl 和 mcrypt 库。尽管 openssl 也可以编译为在 iOS 上工作(但这不是初学者),但苹果不鼓励这样做,并建议使用安全框架中包含的捆绑 openssl 的包装器。
  • 我没有告诉他在 iOS 上使用这个代码。他必须移植它。我什至没有提到 NString。这段代码足以学习如何在 C/C++ 中使用 mcrypt,这是 Objective-C 的一个子集,所以展示 C/C++ 代码是正确的。我在回答中“告诉”了一个应该更改为 md5 的 CommonCrypto 和 Base64 的任何其他内容......这非常明显。您不需要仅仅因为我对您的问题投反对票而对我的问题投反对票。你的回答与他的问题没有任何关系。 ;-)
  • @AntonioE。 mcrypt 实际上是一个 C 库,所以你至少理论上应该能够将它包含在你的项目中。我既不喜欢瓦格纳提出的解决方案,也不喜欢答案主要是概念证明,但似乎没有“好的”解决方案可用,因为基本上没有任何地方可用的“Rijndael-256/256”实现.关于更新:mcrypt 的更新日志中的最后一次更改是 10 多年前的,所以应该没问题;)
  • @AntonioE,所以我不想在这里成为敌人。正如您所说,StackOverflow 是为了让我们互相帮助!但是我真的建议您检查一下您的 cmets 是否说“我的答案是错误的”。这只会对你不利。您可能出于任何原因投反对票/或做任何您想做的事,但不是因为我的回答是错误的。我曾经使用 iOS 工作了几年(在 CommonCrypto 之前),现在我与 OpenSSL/Cryptography 密切合作,我知道这是解决他确切问题的最佳解决方案。希望你明白... ;-)
【解决方案2】:

好的,这里有几件事要指出...首先,MD5 不会给您足够的熵来认为该密钥是安全的。虽然 IV 甚至可以是公开的,但它无论如何都应该是随机的,因此 md5 也不能在那里工作。请注意,拥有固定的 IV 或多或少根本没有它。

如果您想真正使用密码来生成加密密钥,最好的方法是使用PBKDF2

现在是代码:

这是我目前在我的一个项目中使用的代码

- (NSData *)AES256EncryptWithKey:(NSString *)key andIV:(const void*)iv
{
    // '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];

    //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 bufferSize = dataLength + kCCBlockSizeAES128;
    void *buffer = malloc( bufferSize );

    size_t numBytesEncrypted = 0;
    CCCryptorStatus cryptStatus = CCCrypt( kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
                                          keyPtr, kCCKeySizeAES256,
                                          iv /* initialization vector (optional) */,
                                          [self bytes], dataLength, /* input */
                                          buffer, bufferSize, /* output */
                                          &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 andIV:(const void*)iv
{
    // '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];

    //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 bufferSize = dataLength + kCCBlockSizeAES128;
    void *buffer = malloc( bufferSize );

    size_t numBytesDecrypted = 0;
    CCCryptorStatus cryptStatus = CCCrypt( kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
                                          keyPtr, kCCKeySizeAES256,
                                          iv /* initialization vector (optional) */,
                                          [self bytes], dataLength, /* input */
                                          buffer, bufferSize, /* output */
                                          &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;
}

以上代码借自this SO's answer

下面是我在一个项目中使用的部分代码。请注意,函数是对象的一部分,我没有发布所有代码,仅发布相关代码。

/**
 * Pads the data using PKCS7 padding scheme, as described in RFC 5652.
 * 
 * We do not want to rely on Mcrypt's zero-padding, because it differs from
 * OpenSSL's PKCS7 padding.
 * 
 * Note: $data is passed by reference.
 * 
 * @param string &$data 
 */
static public function pkcs7Pad(&$data)
{

    $blockSize = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
    $padding = $blockSize - (strlen($data) % $blockSize);

    $data .= str_repeat(chr($padding), $padding);
}

/**
 * Removes the (PKCS7) padding bytes from $data.
 * 
 * Note: $data is passed by reference.
 * 
 * @param string &$data 
 */
static public function pkcs7Strip(&$data)
{
    $paddingByte = substr($data, -1);
    $paddingLen = ord($paddingByte);
    $dataLen = strlen($data) - $paddingLen;

    // Simple sanity check to make sure we have correct padding bytes. If padding
    // is not correct, we simply set $data to false. At this point, there
    // should be no need to worry about leaking side-channels.
    if (!isset($data[15]) || $paddingLen < 1 || $paddingLen > mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC) )
    {
        //$data = false;
    }
    else if (substr($data, $dataLen) === str_repeat($paddingByte, $paddingLen))
    {
        // Padding is correct, strip it off.
        $data = substr($data, 0, $dataLen);
    }
    else
    {
        //$data = false;
    }
}

public static function encrypt($dataString, $aesCipherKey, $iv = null, $returnBase64Encoded = false){

    // ensure source file exist
    if (!$dataString || empty($dataString))
        return null;

    try{

            // ===========
            // Ciphering
            $ciphered_data = null;

            //Make sure padding is pkcs7 based
            self::pkcs7Pad($dataString);                    

            //Encrypt data with AES
            $ciphered_data = @mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $aesCipherKey, $dataString, MCRYPT_MODE_CBC, $iv);

            return ( $returnBase64Encoded ? base64_encode( $ciphered_data ) : $ciphered_data );


        }
    catch(Exception $ex){

        return null;
    }


}

public static function decrypt($dataString, $aesCipherKey, $iv = null, $returnBase64Encoded = false){

    // ensure source file exist
    if (!$dataString || empty($dataString))
        return null;

    try{

            // ===========
            // Ciphering
            $ciphered_data = null;

            //Decrypt data with AES
            $ciphered_data = @mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $aesCipherKey, $dataString, MCRYPT_MODE_CBC, $iv);

            //Ensure no pkcs7 padding is left overs
            self::pkcs7Strip($ciphered_data);   

            return ( $returnBase64Encoded ? base64_encode( $ciphered_data ) : $ciphered_data );


        }
    catch(Exception $ex){

        return null;
    }

}

编辑:请记住,对于包含加密的软件,您需要遵守美国出口法律。

【讨论】:

  • 关键点是:不想依赖 Mcrypt 的零填充,因为它与 PKCS7 填充不同。
  • 他想要的是:“在 iOS 上使用 Objective-C 的 NSString 完全相同的东西”“它需要与他的 PHP 函数兼容。”...
  • @WagnerPatriota 当然问题是这样,但 StackOverflow 的重点不仅仅是给出一个问题的答案(因此盲目地接受一切),而是就如何做得更好恕我直言给出建议。这就是为什么我包含我的 PHP 代码并通过解释他的服务器端代码中的不好之处来开始我的回答。现在,如果您认为这是对我的答案投反对票的好理由,那很好。
  • 我强烈反对。如果我只是想展示“更好的东西”,我将不得不重写每个问题的代码。人们会到这里来寻找 256 位块大小问题的解决方案。如果他们找到一些通用的加密,这将不是很有帮助——这并不是说没有可用于通用 AES 加密的样本。我的意思是,在这里,您正在推广零 IV 和加密模式,如果应用填充预言,它将在几秒钟内被破坏 - 而且很可能会这样做。回答问题,改进 cmets 的方法(或作为最后一段)。
【解决方案3】:

所以您想在 CBC 模式下使用 AES256 进行加密。 您正在寻找的库是 CommonCrypto,您可以在这里找到一篇关于它的好文章:http://robnapier.net/aes-commoncrypto

您还需要一个 MD5 函数,可以在此处找到:http://www.makebetterthings.com/iphone/how-to-get-md5-and-sha1-in-objective-c-ios-sdk/

您的代码应如下所示:

NSString *originalString,*keyString;
NSData *key = [[self md5:keyString] dataUsingEncoding:NSUTF8StringEncoding];
NSData *iv = [[self md5:[self md5:key]] dataUsingEncoding:NSUTF8StringEncoding];
NSData *data = [originalString dataUsingEncoding:NSUTF8StringEncoding];
NSMutableData *cipherData = [NSMutableData dataWithLength:data.length + kCCBlockSizeAES128]; //The block size of MCRYPT_RIJNDAEL_256 is just like AES128
size_t outLength;

CCCryptorStatus result
       = CCCrypt(kCCEncrypt, // operation, replace with kCCDecrypt to decrypt
                 kCCAlgorithmAES, // Same as MCRYPT_RIJNDAEL_256
                 nil, // CBC mode
                 key.bytes, // key
                 32, // Since you are using AES256
                 iv.bytes,// iv
                 data.bytes, // dataIn
                 data.length, // dataInLength,
                 cipherData.mutableBytes, // dataOut
                 cipherData.length, // dataOutAvailable
                 &outLength); // dataOutMoved
NSString resultString = [cipherData base64Encoding];

并确保在两种情况下都使用相同的 UTF8 编码,并使用此导入:

#import <CommonCrypto/CommonCryptor.h>

我很确定这应该可行。

编辑:密钥长度应为 32,因为您使用的是 AES256 256bit=32bytes。我认为默认情况下 MD5 输出与此长度不匹配。

【讨论】:

  • @WagnerPatriota 实际上 AES 256 Rijndael。至少或多或少。 AES256 实际上基于 Rijndael 算法,但仅限于 128 位的块大小,而 Rijndael 支持从 128 到 256 位的任何 32 位的块大小倍数。
  • @AntonioE。 AES256 是“a” Rijndael,正确,但并非每个 Rijndael 都是 AES。正如 Wagner 在他的回答中指出的那样,mcrypt 中的 RIJNDAEL_256 算法是具有 256 位块大小的 Rijndael(您可以在 mcrypt source./modules/algorithms/rijndael-256.c 中验证这一点),但 AES256 具有 128 位块大小。 (两者都有 256 位密钥。)
  • 要明确一点:这段代码不能工作有几个原因: 1. 它不使用 256 位块大小的 Rijndael; 2.密钥不够大,只有128bit(预计256bit),mcrypt zero默认填充为256bit; 3. IV 对于 256 位块大小来说不够大(尽管这与 CommonCrypto 无关,因为它使用 - 在这种情况下 - 无论如何 - 不正确的 128 位块大小),再次 mcrypt 零填充 IV。
  • @Perseids 从来没有说过每个 Rijndael 都是 AES,而是说 AES 是 Rijndael 的一个特例。 Wagner 的答案的问题在于,即使它展示了如何在 C++ 中使用 AES256 进行加密/解密,它也没有回答这个问题,因为它使用了 iOS 平台上不直接可用的框架。即使你可以让它们在 iOS 上工作,苹果也不鼓励使用它们,因为你应该使用已经捆绑在平台中的苹果包装器。
  • @AntonioE.:我想说的是,当 PHP 中实际使用的 Rijndael 不是那种特殊情况时,AES 是 Rijndael 的一种特殊情况是不切实际的。
【解决方案4】:

我使用http://searchcode.com/codesearch/view/14846108 进行 MD5 加密......证明我错了,但它假设 MD5 只是一种加密方式,不能像那样解密。它可以通过暴力破解或使用大型加密字符串数据库进行解密。这就是为什么它被认为是安全的。

【讨论】:

  • MD5 不是加密算法,而是哈希算法函数:en.wikipedia.org/wiki/Hash_function。他正在使用它来散列他的密码(并且还创建一个 IV,这可能是个坏主意..)
【解决方案5】:

我认为这个类别可能会对你有所帮助

NSString+hashes

别忘了导入

#import <CommonCrypto/CommonCryptor.h>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-12
    • 2014-01-13
    • 2021-02-17
    • 1970-01-01
    相关资源
    最近更新 更多