Decrypt mcrypt with openssl

前端 未结 3 1941
感情败类
感情败类 2020-12-11 10:02

Since mcrypt is considered obsolete, my task is upgrading the current code to use openssl. Sounds simple, but ... after a few days of try and failure I feel like going insan

相关标签:
3条回答
  • 2020-12-11 10:21

    If you encrypt in mcrypt without adding PKCS7 manually, mcrypt will happily pad your plaintext with NUL bytes.

    OpenSSL will do PKCS7 padding for you whenever using aes-X-cbc. The unfortunate consequence of this is that if you have AES-CBC(NULL_PADDED(plaintext)) and try to decrypt it, openssl_decrypt will attempt to remove the padding and fail.

    Compare http://3v4l.org/bdQe9 vs http://3v4l.org/jr68f and http://3v4l.org/K6ZEU

    The OpenSSL extension does not currently offer you a way to say "This string is not padded, please don't strip the padding for me" and then remove the NUL bytes on your own. You must encrypt with PKCS7 padding in order for decryption to succeed.

    Although this is a limitation of OpenSSL, it bears emphasizing that the only reason you're running into it is because mcrypt is terrible.

    0 讨论(0)
  • 2020-12-11 10:29

    There shouldn't be any major differences except for the padding. You should be able to call EVP_CIPHER_CTX_set_padding if you use the higher level OpenSSL (EVP) constructs directly. I presume that the padding argument should be zero, although it is not documented. You need a preconfigured encryption/decryption context for this.

    Afterwards you will have your plaintext of the same length as the ciphertext. Zero to fifteen bytes at the end will be set to zero. You need to remove these bytes manually. If the plaintext happens to end with zero bytes then those will also be removed; that's however never the case if the plaintext is a printable string (that uses 8 bit encoding). You may want to ensure that you don't remove more than 15 bytes.

    If you get completely random plaintext then your key or ciphertext is incorrect. If you get readable plaintext but for the first 16 bytes then your IV handling is incorrect.

    0 讨论(0)
  • 2020-12-11 10:45

    Slightly old, but you can solve this with a bit of work. You can tell PHP's OpenSSL that the encrypted string is not padded, and tell it to give you the raw output (So you don't have to base64 decode it, either). You can then strip nulls from the end of the resulting string if the length of the string happens to be perfectly divisible by the IV (This is a sanity check, as if the resulting string isn't divisible by the IV then it wasn't padded at all).

    Be aware, this code has two major limitations:

    1. If, at any point, you encrypted a legitimate string that ended in two or more NULL bytes then this code will not give you the same output.

    2. If the padding of the string needed only one null byte, then this code won't strip it.

    You can solve both of these if you know for a FACT that you didn't encrypt anything that ends in null bytes, you can alter the code that strips the nulls to just do a preg_replace; just make sure you anchor the regex to the end of the string so it only strips from the end.

    <?php
    $message = 'test';
    $key = openssl_random_pseudo_bytes(16);
    $iv = openssl_random_pseudo_bytes(16);
    
    $cipher = mcrypt_encrypt(
        MCRYPT_RIJNDAEL_128,
        $key,
        $message,
        MCRYPT_MODE_CBC,
        $iv
    );
    
    $plain = openssl_decrypt(
        $cipher,
        'aes-128-cbc',
        $key,
        OPENSSL_RAW_DATA | OPENSSL_NO_PADDING,
        $iv
    );
    
    //try to detect null padding
    if (mb_strlen($iv, '8bit') % mb_strlen($plain, '8bit') == 0) {
            preg_match_all('#([\0]+)$#', $plain, $matches);
            if (mb_strlen($matches[1][0], '8bit') > 1) {
                    $plain = rtrim($plain, "\0");
                    trigger_error('Detected and stripped null padding. Please double-check results!');
            }
    }
    
    
    
    var_dump(
        $message,
        bin2hex($cipher),
        $plain,
        mb_strlen($message, '8bit'),
        mb_strlen($plain, '8bit'),
        $message === $plain
    );
    

    http://3v4l.org/kYAXn

    Obviously this code comes with no major disclaimer and please test it in your use case, but someone might hopefully find this useful.

    0 讨论(0)
提交回复
热议问题