如何使用Objective-C在iOS上执行相同的加密/解密PHP功能


How can I do this same encrypt/decrypt PHP function on iOS with Objective-C?

我在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函数兼容。

因此,您希望在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>

我确信这应该行得通。

EDIT:密钥长度应该是32,因为您使用的是AES256 256bit=32字节。默认情况下,MD5输出与这个长度不匹配。

为什么不使用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函数(但您可以跳过…

。。。注意,我只为md5()和我的Base64类使用了OpenSSL,这与我在许多事情上使用的相同,但你可以替换为其他md5/Base64解决方案,然后你就可以去掉OpenSSL了。很容易苹果现在正在转向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;
}

我认为这个类别可能会帮助您

NSString+散列

也不要忘记导入

#import <CommonCrypto/CommonCryptor.h>

我使用http://searchcode.com/codesearch/view/14846108用于MD5加密。。。证明我错了,但它认为MD5只是单向加密,不能像那样解密。它可以用蛮力解密,也可以用加密字符串的大型数据库解密。这就是为什么它被认为是安全的。

好的,这里有几点需要指出。。。首先,MD5不会给你足够的熵来考虑该密钥的安全性。虽然IV甚至可以是公开的,但它无论如何都应该是随机的,因此md5也不起作用。请注意,有固定的静脉注射或多或少就是根本没有。

如果你真的想使用密码来生成加密密钥,最好的方法是使用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;
}

上面的代码是从这个SO的答案中借来的

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

/**
 * 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;
    }
}

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