Search code examples
phpiosencryption3des

3DES encrypt result in PHP, JAVA and .NET produces different result from 3DES iOS


I'm trying to upgrade my current app in iOS 8, to adapt to the new encryption SHA256 of Redsys / Sermpa.

But I have problems in data encryption. In PHP, Java and .NET I get a result, completely different from iOS.

I think the problem must be in the 3DES IOS CCCrypt.

The PHP, JAVA and .NET code is a library, I can not alter that library.

I have to make the result of encryption in iOS, is identical to right result encrypt in PHP, JAVA and .NET.

Library Java Code:

    String secretCodeString = "Mk9m98IfEblmPfrpsawt7BmxObt98Jev";
    String Ds_Merchant_Order = "1442772645";
    String Ds_MerchantParameters = "eyJEU19NRVJDSEFOVF9BTU9VTlQiOiIxNDUiLCJEU19NRVJDSEFOVF9PUkRFUiI6IjE0NDI3NzI2NDUiLCJEU19NRVJDSEFOVF9NRVJDSEFOVENPREUiOiI5OTkwMDg4ODEiLCJEU19NRVJDSEFOVF9DVVJSRU5DWSI6Ijk3OCIsIkRTX01FUkNIQU5UX1RSQU5TQUNUSU9OVFlQRSI6IjAiLCJEU19NRVJDSEFOVF9URVJNSU5BTCI6Ijg3MSIsIkRTX01FUkNIQU5UX01FUkNIQU5UVVJMIjoiaHR0cHM6XC9cL2VqZW1wbG9cL2VqZW1wbG9fVVJMX05vdGlmLnBocCIsIkRTX01FUkNIQU5UX1VSTE9LIjoiaHR0cHM6XC9cL2VqZW1wbG9cL2VqZW1wbG9fVVJMX09LX0tPLnBocCIsIkRTX01FUkNIQU5UX1VSTEtPIjoiaHR0cHM6XC9cL2VqZW1wbG9cL2VqZW1wbG9fVVJMX09LX0tPLnBocCJ9";

    byte [] secretCode = decodeB64(secretCodeString.getBytes("UTF-8"));
    String secretKc = toHexadecimal(secretCode, secretCode.length);
    byte [] Ds_Merchant_Order_encrypt3DES = encrypt_3DES(secretKc, Ds_Merchant_Order);
    byte [] hash = mac256(Ds_MerchantParameters, Ds_Merchant_Order_encrypt3DES);
    byte [] res = encodeB64UrlSafe(hash);
    String Ds_Signature = new String(res, "UTF-8");
    //Ds_Signature: hueCwD/cbvrCi+9IDY86WteMpXulIl0IDNXNlYgcZHM=


public byte [] encrypt_3DES(final String claveHex, final String datos) {
        byte [] ciphertext = null;
        try {
            DESedeKeySpec desKeySpec = new DESedeKeySpec(toByteArray(claveHex));
            SecretKey desKey = new SecretKeySpec(desKeySpec.getKey(), "DESede");
            Cipher desCipher = Cipher.getInstance("DESede/CBC/NoPadding");
            byte [] IV = {0, 0, 0, 0, 0, 0, 0, 0};

            desCipher.init(Cipher.ENCRYPT_MODE, desKey, new IvParameterSpec(IV));

            int numeroCerosNecesarios = 8 - (datos.length() % 8);
            if (numeroCerosNecesarios == 8) {
                numeroCerosNecesarios = 0;
            }
            ByteArrayOutputStream array = new ByteArrayOutputStream();
            array.write(datos.getBytes("UTF-8"), 0, datos.length());
            for (int i = 0; i < numeroCerosNecesarios; i++) {
                array.write(0);
            }
            byte [] cleartext = array.toByteArray();

            ciphertext = desCipher.doFinal(cleartext);
        } catch (Exception e) {
            e.printStackTrace(System.err);
        }
        return ciphertext;
    }

Library PHP Code:

    $Ds_Merchant_Order = "1442772645";
    $Ds_MerchantParameters = "eyJEU19NRVJDSEFOVF9BTU9VTlQiOiIxNDUiLCJEU19NRVJDSEFOVF9PUkRFUiI6IjE0NDI3NzI2NDUiLCJEU19NRVJDSEFOVF9NRVJDSEFOVENPREUiOiI5OTkwMDg4ODEiLCJEU19NRVJDSEFOVF9DVVJSRU5DWSI6Ijk3OCIsIkRTX01FUkNIQU5UX1RSQU5TQUNUSU9OVFlQRSI6IjAiLCJEU19NRVJDSEFOVF9URVJNSU5BTCI6Ijg3MSIsIkRTX01FUkNIQU5UX01FUkNIQU5UVVJMIjoiaHR0cHM6XC9cL2VqZW1wbG9cL2VqZW1wbG9fVVJMX05vdGlmLnBocCIsIkRTX01FUkNIQU5UX1VSTE9LIjoiaHR0cHM6XC9cL2VqZW1wbG9cL2VqZW1wbG9fVVJMX09LX0tPLnBocCIsIkRTX01FUkNIQU5UX1VSTEtPIjoiaHR0cHM6XC9cL2VqZW1wbG9cL2VqZW1wbG9fVVJMX09LX0tPLnBocCJ9";
    $secretCode = "Mk9m98IfEblmPfrpsawt7BmxObt98Jev";

    $secretCode = base64_decode($secretCode);
    $bytes = array(0,0,0,0,0,0,0,0);
    $iv = implode(array_map("chr", $bytes)); //PHP 4 >= 4.0.2
    $Ds_Merchant_Order_encrypt3DES = mcrypt_encrypt(MCRYPT_3DES, $secretCode, $Ds_Merchant_Order, MCRYPT_MODE_CBC, $iv);
    $hash = hash_hmac('sha256', $Ds_MerchantParameters, $Ds_Merchant_Order_encrypt3DES, true);
    $Ds_Signature = $this->encodeBase64($hash);
    //Ds_Signature: hueCwD/cbvrCi+9IDY86WteMpXulIl0IDNXNlYgcZHM=

Library .NET Code:

byte[] secretCode = Base64Decode("Mk9m98IfEblmPfrpsawt7BmxObt98Jev");

    string Ds_MerchantParameters = "eyJEU19NRVJDSEFOVF9BTU9VTlQiOiIxNDUiLCJEU19NRVJDSEFOVF9PUkRFUiI6IjE0NDI3NzI2NDUiLCJEU19NRVJDSEFOVF9NRVJDSEFOVENPREUiOiI5OTkwMDg4ODEiLCJEU19NRVJDSEFOVF9DVVJSRU5DWSI6Ijk3OCIsIkRTX01FUkNIQU5UX1RSQU5TQUNUSU9OVFlQRSI6IjAiLCJEU19NRVJDSEFOVF9URVJNSU5BTCI6Ijg3MSIsIkRTX01FUkNIQU5UX01FUkNIQU5UVVJMIjoiaHR0cHM6XC9cL2VqZW1wbG9cL2VqZW1wbG9fVVJMX05vdGlmLnBocCIsIkRTX01FUkNIQU5UX1VSTE9LIjoiaHR0cHM6XC9cL2VqZW1wbG9cL2VqZW1wbG9fVVJMX09LX0tPLnBocCIsIkRTX01FUkNIQU5UX1VSTEtPIjoiaHR0cHM6XC9cL2VqZW1wbG9cL2VqZW1wbG9fVVJMX09LX0tPLnBocCJ9"
    string Ds_Merchant_Order = "1442772645";

    // Calculate derivated key by encrypting with 3DES the "DS_MERCHANT_ORDER" with decoded key 
    byte[] Ds_Merchant_Order_encrypt3DES = cryp.Encrypt3DES(Ds_Merchant_Order, secretCode);

    // Calculate HMAC SHA256 with Encoded base64 JSON string using derivated key calculated previously
    byte[] hash = cryp.GetHMACSHA256(Ds_MerchantParameters, Ds_Merchant_Order_encrypt3DES);

    // Encode byte[] res to Base64 String
    string Ds_Signature = Base64Encode2(hash);
    //Ds_Signature: hueCwD/cbvrCi+9IDY86WteMpXulIl0IDNXNlYgcZHM=

    public byte[] Encrypt3DES(string plainText, byte[] key)  {
            byte[] toEncryptArray = Encoding.UTF8.GetBytes(plainText);
            TripleDESCryptoServiceProvider tdes = new TripleDESCryptoServiceProvider();

            try  {
                /// SALT used in 3DES encryptation process.
                byte[] SALT = new byte[8] {0,0,0,0,0,0,0,0};

                // Block size 64 bit (8 bytes)
                tdes.BlockSize = 64;

                // Key Size 192 bit (24 bytes)
                tdes.KeySize = 192;
                tdes.Mode = CipherMode.CBC;
                tdes.Padding = PaddingMode.Zeros;

                tdes.IV = SALT; 
                tdes.Key = key;  

                var cTransform = tdes.CreateEncryptor();

                //transform the specified region of bytes array to resultArray
                byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);

                //Release resources held by TripleDes Encryptor
                tdes.Clear();

                return resultArray;

            } // Error in Cryptographic method
            catch (CryptographicException ex) {
                throw new CryptographicException(ex.Message);
            }
        }

**********************------------**********************------------**********************

My Objective-C Code:

NSString *Ds_Merchant_Order = @"1442772645";
NSString *Ds_MerchantParameters = @"eyJEU19NRVJDSEFOVF9BTU9VTlQiOiIxNDUiLCJEU19NRVJDSEFOVF9PUkRFUiI6IjE0NDI3NzI2NDUiLCJEU19NRVJDSEFOVF9NRVJDSEFOVENPREUiOiI5OTkwMDg4ODEiLCJEU19NRVJDSEFOVF9DVVJSRU5DWSI6Ijk3OCIsIkRTX01FUkNIQU5UX1RSQU5TQUNUSU9OVFlQRSI6IjAiLCJEU19NRVJDSEFOVF9URVJNSU5BTCI6Ijg3MSIsIkRTX01FUkNIQU5UX01FUkNIQU5UVVJMIjoiaHR0cHM6XC9cL2VqZW1wbG9cL2VqZW1wbG9fVVJMX05vdGlmLnBocCIsIkRTX01FUkNIQU5UX1VSTE9LIjoiaHR0cHM6XC9cL2VqZW1wbG9cL2VqZW1wbG9fVVJMX09LX0tPLnBocCIsIkRTX01FUkNIQU5UX1VSTEtPIjoiaHR0cHM6XC9cL2VqZW1wbG9cL2VqZW1wbG9fVVJMX09LX0tPLnBocCJ9";

NSString *clave = @"Mk9m98IfEblmPfrpsawt7BmxObt98Jev";
NSData *decodedData = [[NSData alloc] initWithBase64EncodedString:clave options:0];
NSString *secretCode = [self hexadecimalString:decodedData];

NSData *Ds_Merchant_Order_encrypt3DES = [self encrypt3DES:Ds_Merchant_Order key:secretCode];

NSData *hash = [self hmac256ForKeyAndData:Ds_MerchantParameters withKey:Ds_Merchant_Order_encrypt3DES];

NSString *Ds_Signature = [hash base64EncodedStringWithOptions:0];
//Ds_Signature:  kUVwanKNIlrvw3t56HUAYXSBmE/u6ruTj1r/FGOIiUg=

My Functions:

- (NSString *)hexadecimalString:(NSData*)data{
    const unsigned char *dataBuffer = (const unsigned char *)[data bytes];
    if (!dataBuffer){
        return [NSString string];
    }
    NSUInteger          dataLength  = [data length];
    NSMutableString     *hexString  = [NSMutableString stringWithCapacity:(dataLength * 2)];
    for (int i = 0; i < dataLength; ++i){
        [hexString appendFormat:@"%02x", (unsigned int)dataBuffer[i]];
    }
    return [NSString stringWithString:hexString];
}


- (NSData*)encrypt3DES:(NSString*)data key:(NSString*)key{
    NSData *plainData = [data dataUsingEncoding:NSUTF8StringEncoding];
    const void *vplainText = (const void *)[plainData bytes];
    size_t plainTextBufferSize = [plainData length];
    size_t movedBytes = 0;
    size_t bufferPtrSize = (plainTextBufferSize + kCCBlockSize3DES) & ~(kCCBlockSize3DES - 1);
    uint8_t * bufferPtr = malloc( bufferPtrSize * sizeof(uint8_t));
    memset((void *)bufferPtr, 0x0, bufferPtrSize);

    NSString *initVec = @"\0\0\0\0\0\0\0\0";
    const void *vkey = (const void *) [key UTF8String];
    const void *vinitVec = (const void *) [initVec UTF8String];

    CCCryptorStatus ccStatus = CCCrypt(kCCEncrypt,
                                       kCCAlgorithm3DES,
                                       kCCOptionPKCS7Padding | kCCOptionECBMode,
                                       vkey,
                                       kCCKeySize3DES,
                                       vinitVec,
                                       vplainText,
                                       plainTextBufferSize,
                                       (void *)bufferPtr,
                                       bufferPtrSize,
                                       &movedBytes);
    if (ccStatus == kCCSuccess) NSLog(@"SUCCESS");
    else if (ccStatus == kCCParamError) NSLog( @"PARAM ERROR");
    else if (ccStatus == kCCBufferTooSmall) NSLog( @"BUFFER TOO SMALL");
    else if (ccStatus == kCCMemoryFailure) NSLog( @"MEMORY FAILURE");
    else if (ccStatus == kCCAlignmentError) NSLog( @"ALIGNMENT");
    else if (ccStatus == kCCDecodeError) NSLog( @"DECODE ERROR");
    else if (ccStatus == kCCUnimplemented) NSLog( @"UNIMPLEMENTED");

    return [NSData dataWithBytes:(const void *)bufferPtr length:(NSUInteger)movedBytes];
}


-(NSData *)hmac256ForKeyAndData:(NSString *)data withKey:(NSData *)keyData{
    NSData *dataData=[data dataUsingEncoding:NSUTF8StringEncoding];
    NSMutableData* hash = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH];
    CCHmac(kCCHmacAlgSHA256, keyData.bytes, keyData.length, dataData.bytes, dataData.length, hash.mutableBytes);
    return hash;
}

P.S.: Keys and passwords are fake, are just for testing. ;)


Solution

  • There are a couple of errors:

    First: the PHP version uses CBC mode and the iOS version uses ECB mode. The default for CCCrypt is CBC mode, just remove kCCOptionECBMode. Using a null iv will make the first block insecure, generally a random iv is used and prepended to the encrypted data.

    Second: mcrypt does not support PKCS#7 padding, it only supports non-standard insecure null padding. Therefore it is necessary to add the padding to the data prior to encryption.

    From this SO Answer:

    Add PKCS#7 padding (php):
    where $block is the block size in bytes and $str is the data to be encrypted

     $pad = $block - (strlen($str) % $block);
     $str .= str_repeat(chr($pad), $pad);
    

    Remove PKCS#7 padding (php):
    where $str is the decrypted data

    $len = strlen($str);
    $pad = ord($str[$len-1]);
    $str = $strsubstr($str, 0, $len - $pad);
    

    Note: if the data is exactly a multiple of the block size an entire block of padding will be added, this is necessary.

    See PKCS#7 for more information on padding.

    For further debugging provide the hex dumps of all parameters and data in and out of the encryption: secretCode, Ds_Merchant_Order, iv and encrypted output.

    Finally: For better security consider using RNCryptor which is available for several platforms and languages. It is well vetted, supports the current best practices and is currently supported.