-1

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. ;)

clasik
  • 3
  • 2

1 Answers1

0

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.

Community
  • 1
  • 1
zaph
  • 111,848
  • 21
  • 189
  • 228
  • The PHP 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. – clasik Nov 19 '15 at 14:47
  • In that case you need to remove the PHP null padding after decryption with CCCrypt and remove the `kCCOptionPKCS7Padding` option. As long as the data being encrypted by mcrypt does not have the last data byte of 0x00 just remove trailing 0x00 characters. As mentioned you also need to remove the `kCCOptionECBMode` option. It unfortunately is not uncommon to have insecure encryption initially used and not corrected. – zaph Nov 19 '15 at 15:35
  • Note: CCCrypt does not report incorrect padding, doing so is insecure. – zaph Nov 19 '15 at 15:38
  • It appears that there is no padding being used which is OK prpoviding the imput is *always* a multiple of the block size (8-bytes fpor DES/3DES). But in this case the data is 10 bytes. DES is a block cipher and must have complete blocks. When the data input is not an exact multiple of the block size the data must be padded to a blocl size. mcrypt uses non-standard nul padding. Nulls must be added to the data input to CCCrypt. – zaph Nov 19 '15 at 21:37
  • Could you share the final code? I'm struggling with this too but I'm not able to apply this changes correctly. I've asked about this question here in SO: https://stackoverflow.com/questions/44936947/ios-3des-enccryption-for-tpv-payments – Wonton Jul 05 '17 at 22:27