【问题标题】:How to decrypt a string encrypted by SQL Server's EncryptByPassPhrase() in PHP?如何在 PHP 中解密由 SQL Server 的 EncryptByPassPhrase() 加密的字符串?
【发布时间】:2019-09-11 14:45:14
【问题描述】:

我有一个加密字符串及其密钥,它是使用 SQL Server 使用“EncryptByPassPhrase”创建的,我如何在 PHP 中解密它?

我已阅读“EncryptByPassPhrase”的文档,其中指出这是 128 长度的三重 DES 加密。我尝试了 PHP 的 3DES 解密,但它没有返回预期的输出。

MS SQL 中的加密是通过

declare @encrypt varbinary(200) 
select @encrypt = EncryptByPassPhrase('key', 'taskseq=10000&amt=200.5' )
select @encrypt 

我在 PHP 中解密如下:

    function decryptECB($encrypted, $key) {
       $iv_size = mcrypt_get_iv_size(MCRYPT_3DES, MCRYPT_MODE_ECB);
       $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
       // decrypting
       $stringText = mcrypt_decrypt(MCRYPT_3DES, $key, $encrypted, 
       MCRYPT_MODE_ECB, $iv);

       return $stringText;
    }

【问题讨论】:

  • 我假设你也会在 SQL 中使用 docs.microsoft.com/en-us/sql/t-sql/functions/…
  • 我这边用的是MYSQL,找不到这个TSql解密函数。
  • @user3783243 我没有从 mssql 同步,我的 PHP 脚本中有一些硬编码的加密字符串。
  • 好的,mysql 没有被使用。将数据库值静态存储在 PHP 中似乎是个坏主意。这违背了数据库的目的(而且使用反函数解密会容易得多)。
  • @user3783243 实际上加密的字符串将包含一些数据,这些数据将用于生成特定的 url,在访问该 url 时,需要在 PHP 应用程序中解密加密的字符串,数据将显示在这页纸。我们别无他法,只能用上面提到的TSQL函数使用sql server中生成的加密字符串。

标签: php sql sql-server tsql encryption


【解决方案1】:

我冒昧地将this Stack Overflow answer 翻译成 PHP。

这是结果:

<?php

// SQL Server's DecryptByPassphrase translated into PHP. 
function decrypt(string $data, string $password): ?string {
    // SQL Server <2017 uses SHA1 for the key and the DES-EDE-CBC crypto algorithm
    // whereas SQL Server >= 2017 uses SHA256 and AES-256-CBC. 
    // Version 1 is the SHA1 + DES-EDE-CBC version, Version 2 is the AES-256-CBC version.
    // Version is stored in the first four bytes as a little endian int32.
    $version_bytes = substr($data, 0, 4);
    $version = unpack('V', $version_bytes)[1];

    // Password must be converted to the UTF-16LE encoding.
    $passwordUtf16 = mb_convert_encoding($password, 'UTF-16LE');

    if ($version === 1) {
        // Key is hashed using SHA1, The first 16 bytes of the hash are used.
        $key = substr(hash('sha1', $passwordUtf16, true), 0, 16);
        $method = 'des-ede-cbc';
        $options = OPENSSL_RAW_DATA;
        $iv = substr($data, 4, 8); // initialization vector of 8 bytes
        $encrypted_data = substr($data, 12); // actual encrypted data
    } else if ($version === 2) {
        // Key is hashed using sha256. Key length is always 32 bytes.
        $key = hash('sha256', $passwordUtf16, true);
        $method = 'aes-256-cbc';
        $options = OPENSSL_RAW_DATA;
        $iv = substr($data, 4, 16); // iv of 16 bytes
        $encrypted_data = substr($data, 20);
    } else {
        throw new \InvalidArgumentException('Invalid version');
    }

    $decrypted = openssl_decrypt($encrypted_data, $method, $key, $options, $iv);

    if ($decrypted === false) {
        return null;
    }

    // First 8 bytes contain the magic number 0xbaadf00d and the length
    // of the decrypted data
    $decrypted = substr($decrypted, 8);

    // UTF-16 encoding should be converted to UTF-8. Note that
    // there might be a better way to accomplish this.
    $isUtf16 = strpos($decrypted, 0) !== false;

    if ($isUtf16) {
        return mb_convert_encoding($decrypted, 'UTF-8', 'UTF-16LE');
    }

    return $decrypted;
}

// A version 1 encrypted string. Taken directly from the Stack Overflow answer linked above
$s = '010000007854E155CEE338D5E34808BA95367D506B97C63FB5114DD4CE687FE457C1B5D5';
$password = 'banana';
$bin = hex2bin($s);
$d = decrypt($bin, $password);
var_dump($d); // string(6) "turkey"

// A version 2 encrypted string. Taken directly from the Stack Overflow answer linked above
$s = '02000000266AD4F387FA9474E825B013B0232E73A398A5F72B79BC90D63BD1E45AE3AA5518828D187125BECC285D55FA7CAFED61';
$password = 'Radames';
$bin = hex2bin($s);
$d = decrypt($bin, $password);
var_dump($d); // string(16) "LetTheSunShining"

旁注:mcrypt is deprecated as it has been abandoned for over a decade.

【讨论】:

    【解决方案2】:

    EncryptByPassPhrase() 使用似乎没有现成文档的专有格式。解密的最佳选择是DecryptByPassPhrase()

    这种专有格式的目的是在您的应用程序的数据库层中使用——而不是跨应用程序/网络/语言。

    如果您不喜欢使用这种格式(我建议不要这样做),您需要获得这种格式的规范,包括使用什么样的密钥偏差函数将密码转换为实际的加密密钥等。

    当您有了这个规范后,您就必须自己实现它。

    【讨论】:

    • 现在看来唯一的解决方案是将 PHP 应用程序连接到 MS SQL 服务器,使用相同的 TSQL 解密函数“DecryptByPassPhrase()”从 PHP 运行解密查询。我正在避免这种情况,因为 MS SQL 服务器上的连接和查询将只是用于字符串的解密。我正在尝试用 PHP 完成解密工作,但似乎别无选择。
    【解决方案3】:

    在查询中使用以下内容

    DECRYPTBYPASSPHRASE('key', [field] )
    

    Reference

    【讨论】:

    • 再次阅读问题,我不是要求在 MS SQL 中解密。我想用 PHP 解密。
    猜你喜欢
    • 2014-03-08
    • 1970-01-01
    • 1970-01-01
    • 2020-05-13
    • 2010-09-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多