2

I have written a simple XOR function in PHP. It works great standalone, but it always throw an exception of "Length Not Match" when I implement it into a class.

The XOR Function:

private static function strxor($dataA, $dataB) {
    if (($dataLen = strlen($dataA)) != strlen($dataB)) {
        throw new Exception("Length Not Match in strxor");
    }
    $result = '';
    for ($i = 0; $i < $dataLen; $i++) {
        $result .= $dataA[$i] ^ $dataB[$i];
    }
    return $result;
}

I even tried another version that copied from Encrypt/decrypt with XOR in PHP, with added Length Check and variable name changed.

private static function xor_this($dataA, $dataB) {
    if (($dataLen = strlen($dataA)) !== strlen($dataB)) {
        die("Length Not Match in xor_this");
    }
    $result = '';
    for($i=0;$i<$dataLen;) {
        for($j=0;($j<$dataLen && $i<$dataLen);$j++,$i++) {
            $result .= $dataA{$i} ^ $dataB{$j};
        }
    }
    return $result;
}

The class I'm going to implement:

public static function encrypt($key, $data) {
    $iv = parent::genSafeRandomBytes(16);
    $nonce = parent::genSafeRandomBytes(16);
    $firstBlock = self::xor_this($nonce, $iv);
    $salt = parent::genSafeRandomBytes(16);
    $hmac = parent::signText($data, $key);
    $subkey = parent::genSubKey($key, $salt);
    $data = self::pkcs7pad($data);
    $data = str_split($data, 16);
    $tmp_r = openssl_encrypt($firstBlock, self::CIPHER, $subkey, OPENSSL_RAW_DATA);
    $result = '';
    for ($i = 0; $i < count($data); $i++) {
        $tmp_n = parent::ivAdd($nonce, $i+1);
        $tmp_n = self::xor_this($tmp_n, $tmp_r);
        $tmp_x = openssl_encrypt($tmp_n, self::CIPHER, $subkey, OPENSSL_RAW_DATA);
        $result .= $tmp_r = self::xor_this($tmp_x, $data[$i]);
    }
    return Base62::encode($iv.$nonce.$salt.$hmac.$result);
}

I'm sure that the two values have the same length. How can I fix it? I really don't know why and how the error occur.

Full source: https://gist.github.com/hartmantam/39857700831591775b1c (not working)

Community
  • 1
  • 1
Hartman
  • 295
  • 4
  • 14
  • Strange problem, couldn't duplicate it... Could you create a fiddle at http://phpfiddle.org/ ? – Jason Fetterly Mar 22 '16 at 05:49
  • You know you can XOR strings against each other directly, right? You don't have to go character by character. `function strxor($a, $b) { return $a ^ $b; }` –  Mar 22 '16 at 06:13
  • @JasonFetterly I really don't know how to use phpfiddle.org . So I post all source [here](https://gist.github.com/hartmantam/39857700831591775b1c), please take a look. – Hartman Mar 22 '16 at 06:24
  • @duskwuff Thanks you advise, but it did not solve the length not match problem, although it increase the performance. – Hartman Mar 22 '16 at 06:24

1 Answers1

0

By adding a debug function, I finally found that the problem was not in the XOR function, it is in the openssl_encrypt function. As AES block size is 16 and I input it just right, the function will pad one more block to the plaintext, causing the output double, so this is why this error occurs.

The solution is pretty simple: Change OPENSSL_RAW_DATA to OPENSSL_ZERO_PADDING, and then the function will not do padding. Although this problem solved, it now requires us to pad the message manually, and it will output a Base64 encoded string.

Change all openssl_encrypt as below, and replace the first variable if needed.

openssl_encrypt($firstBlock, self::CIPHER, $subkey, OPENSSL_ZERO_PADDING);

All I want to say: strange design

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Hartman
  • 295
  • 4
  • 14
  • Hi, I suspected the string size was changing after encrypt, but my local test server has an old SSL lib and lacked the 'pkcs7pad' function so I couldn't test last night. Glad you nailed it, sorry I wasn't any help. – Jason Fetterly Mar 22 '16 at 15:13