PHP 使用trim() 对某些字符串解密失败

标签 php encryption

我在解密以 %3D%3D 结尾的值时遇到问题。解密后,我得到一个完全难以辨认的返回值。加密值是通过查询字符串传递的,但我已经运行了一个循环测试值 0 到 200,以排除 url 编码的问题。

加密解密函数:

function encryptValue($encrypt) {
    $key = variable_get_local("privateKey", $default = "");
    $iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB),   MCRYPT_RAND);
    $passcrypt = trim(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, trim($encrypt), MCRYPT_MODE_ECB, $iv));
    $encode = urlencode(base64_encode($passcrypt));
    return $encode;
}


function decryptValue($decrypt) {
    $key = variable_get_local("privateKey", $default = "");
    $decoded = base64_decode(urldecode($decrypt));
    $iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND);
    $decrypted = trim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, trim($decoded), MCRYPT_MODE_ECB, $iv));
    return $decrypted;
}

我尝试在加密和解密过程中保持 iv 值相同,但这不会改变输出。我还尝试删除 trim($decoded) 周围的 trim() 但这也没有改变任何东西。

下面是我用来识别问题的方法。 0 到 200 之间的加密将产生 9 次以 %3D%3D 结尾的值,并导致解密失败。

for($i=0;$i<200;$i++) {
    echo encryptValue($i) . "<br/>";
    echo decryptValue(encryptValue($i)) . "<br/><hr/>";
}

感谢您的阅读。

最佳答案

以下是当前脚本中的一些观察结果:

  • ECB 模式不安全,请使用 CBC 代替
  • MCRYPT_RANDrand 相同,请参阅 str_shuffle and randomness使用 MCRYPT_DEV_URANDOM 代替
  • 为了更好的安全性,使用加密+使用HMAC进行身份验证来防止oracle padding攻击
  • 使用经过适当测试的库 Zend\Crypt 而不是创建您自己的库,因为您不是安全专家

示例:

// Encription Key
$encryptionKey = mcrypt_create_iv(16, MCRYPT_DEV_URANDOM); // Stored securely

// Signature Key
$signatureKey = mcrypt_create_iv(16, MCRYPT_DEV_URANDOM); // Stored securely

// Start DataEncryption Object
$obj = new DataEncryption($encryptionKey, $signatureKey);
$obj->setEncoding(DataEncryption::ENCODE_BASE64);

// Test
for($i = 0; $i < 200; $i ++) {
    printf("%s = %s\n", $encode = $obj->encrypt($i), $obj->decrypt($encode));
}

输出

eSCknmsoMHY2oo5lpW3NpQhDigs4+Fw8aObeIhK+wPyUImQbvlh/aUrW = 0 
qFswb3VO5+Foi4kjVn6s5lpZbiWgdKmfObh37/xjPyqB4ZFfAXNUeYYX = 1 
WKG0BCKUxOXWU6S3YJ/dNL46Lcn7lt+ihG4tEoZuORDoJXSjz6Vrcepn = 2 
K24QqkGYC86btzGQ5HKKMewVhiEIdKOajpgLx8SMKVKfCwlOJlbRwpaz = 3 
0DbJycPZ24FOAhQrhQJmgMsP0p2nFzYUlFVOFlbQ8zhLTXkcdnNOhVfi = 4 
l7saQG2BTPAZR2EnYjxfNmTxEBBaAh+n9+8eOCITDGzEVShw9wOxP7Pt = 5 
eUhvvHJOFsy6ZaBu40XgU+N5VtuFBesRVx0ryfManIXva5y7J0ShiKcE = 6 
TaX+172N60X1UTVmMWYcdcn7YzN7xoAOVEPpaD7r1pE3OtX5Erg4nja8 = 7 
0LM3W0pkQ73IsmqAgRiQvqL0/rdkk7YvuwcVwoe1NI+qZo7Jq8gyFIvn = 8 
....
38m8fEoUhoTyPPBukg3KVhrmwVDyVCcnWx/5erAslUDzEP7Bddzj5y8Y = 196 
Dwi6t7sX30bxjbVXMKCWEZs0FxTUZM4IPHKR3VD6kygi7op0Q6ARCZJW = 197 
TJ/faDaIuE0mDPHmGar1BeIyAnfVD0Z47ZtCcHjz5AZzaQ1YWH8kF1bU = 198 
FYh+8Kts4ubVvTT5o0vZYfKC+8ExhpD5pWgHK3EhvGWkcPwKerSIvkK0 = 199

使用的类

class DataEncryption {
    private $keyEncryption;
    private $keySignature;
    private $ivSize;
    private $cipher = MCRYPT_RIJNDAEL_128;
    private $mode = MCRYPT_MODE_CBC;
    private $signatureLength = 10;
    private $encoding = 2; // I prefer hex
    const ENCODE_BASE64 = 1;
    const ENCODE_HEX = 2;

    function __construct($encryptionKey = null, $signatureKey = null) {
        // Set Keys
        $this->keyEncryption = empty($encryptionKey) ? mcrypt_create_iv(32, MCRYPT_DEV_URANDOM) : $encryptionKey;
        $this->keySignature = empty($signatureKey) ? mcrypt_create_iv(32, MCRYPT_DEV_URANDOM) : $signatureKey;

        // Get IV Size
        $this->ivSize = mcrypt_get_iv_size($this->cipher, $this->mode);
    }

    public function getKeys() {
        return array(
                "encryption" => $this->keyEncryption,
                "signature" => $this->keySignature
        );
    }

    public function setMode($mode) {
        $this->mode = $mode;
    }

    public function setCipher($cipher) {
        $this->cipher = $cipher;
    }

    public function setEncoding($encode) {
        $this->encoding = $encode;
    }

    public function encrypt($data) {

        // add PKCS7 padding to data
        $block = mcrypt_get_block_size($this->cipher, $this->mode);
        $pad = $block - (strlen($data) % $block);
        $data .= str_repeat(chr($pad), $pad);

        $iv = $this->rand($this->ivSize);
        $cipherData = mcrypt_encrypt($this->cipher, $this->keyEncryption, $data, $this->mode, $iv);
        $finalData = $iv . $cipherData;

        // protected against padding oracle attacks
        $finalData = $this->sign($finalData) . $finalData;
        return $this->encode($finalData);
    }

    public function decrypt($data) {
        $data = $this->decode($data);
        // Check Integrity
        if (! $this->check($data)) {
            return false;
        }
        $data = substr($data, $this->signatureLength);

        // Break Data
        $iv = substr($data, 0, $this->ivSize);
        $cipherData = substr($data, $this->ivSize);
        $data = mcrypt_decrypt($this->cipher, $this->keyEncryption, $cipherData, $this->mode, $iv);

        // Remove PKCS7 padding
        $block = mcrypt_get_block_size($this->cipher, $this->mode);
        $pad = ord($data[($len = strlen($data)) - 1]);

        // $data = rtrim($data, "\0..\32");
        return substr($data, 0, $len - $pad);
    }

    public function encode($data) {
        return $this->encoding === self::ENCODE_BASE64 ? base64_encode($data) : bin2hex($data);
    }

    public function decode($data) {
        return $this->encoding === self::ENCODE_BASE64 ? base64_decode($data) : pack("H*", $data);
    }

    public function sign($data) {
        $hash = hash_hmac('sha256', $data, $this->keySignature, true);
        return substr($hash, 0, $this->signatureLength);
    }

    public function check($data) {
        $signature = substr($data, 0, $this->signatureLength);
        $data = substr($data, $this->signatureLength);
        $hash = hash_hmac('sha256', $data, $this->keySignature, true);
        // return $signature === substr($hash, 0, $this->signatureLength);

        return $this->compare($signature, substr($hash, 0, $this->signatureLength));
    }

    public function rand($no) {
        return mcrypt_create_iv($no, MCRYPT_DEV_URANDOM);
    }

    /**
     * Prevent Timing Attacks
     * @param string $a
     * @param string $b
     * @return boolean
     */
    public function compare($a, $b) {
        if (strlen($a) !== strlen($b)) {
            return false;
        }
        $result = 0;
        for($i = 0; $i < strlen($a); $i ++) {
            $result |= ord($a[$i]) ^ ord($b[$i]);
        }
        return $result == 0;
    }
}

关于PHP 使用trim() 对某些字符串解密失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16518795/

相关文章:

PHP:没有 php.ini 文件

php - 拉维尔/Symfony : unable to load the "app" configuration file

javascript - 当php获取字符串在当前url中时如何重定向URL

asp.net - 保护 SQL Server 2008R2 数据库的安全

php - 如何加密字符串数据,使其只能由其所在的计算机解码?

security - 对CBC和ECB使用相同的AES key

php - 使用 ACF 从分类术语中获取图像

php - 在 Symfony 2 Controller 中抽象通用功能的正确方法是什么

java - Char 到 Int,将 int 值更改回 char,然后添加到字符串

java - 如何创建自定义 JDBCrealm?