JavaScript DES加密/PHP解密

3

我有一个PHP端点,被多个网站使用。在向端点POST数据时,网站使用加密头来识别各种信息。这些网站使用标准的PHP mcrypt_ecb函数,而端点会像这样解密变量:

$var = trim(mcrypt_ecb(MCRYPT_DES, $key, base64_decode($input), MCRYPT_DECRYPT)));

虽然我无法改变PHP端点,但如果mcrypt_ecb不能很好地使用,我可以构建另一个端点。

现在我有一个PhoneGap应用程序,还需要发送加密标头。Phonegap只能使用JavaScript来POST到远程服务器。我查看了Google CryptoJS,并使用了base64库尝试实现此目的。请注意,密钥与上面的$key变量相同。

var encrypted = CryptoJS.DES.encrypt(user.email, key).toString();
var base64 = base64.encode(encrypted);

问题在于加密字符串(我已尝试过不使用base64编码/解码)通过mcrypt_ecb时会变成垃圾。显然,有不同的三重DES加密方法?如何在JavaScript中创建与mcrypt_ecb兼容的加密字符串?

1
请将您的解决方案发布为答案,然后接受它。谢谢。 - deceze
未来的读者:1. DES已经被弃用且不安全,已被AES取代,请勿在新工作中使用。2.请勿在新工作中使用ECB模式,并尽快更新旧有工作,因为它不安全,请参见ECB模式,向下滚动到企鹅处。相反,请使用带有随机IV的CBC模式,只需将加密数据的前缀与IV一起用于解密即可,无需保密。 - zaph
未来的读者:最好不要使用PHP mcrypt,它已被弃用,多年未更新,也不支持标准的PKCS#7(原名PKCS#5)填充,只支持非标准的空填充,甚至不能用于二进制数据。mcrypt存在许多未解决的错误,可以追溯到2003年。mcrypt扩展已被弃用,将在PHP 7.2中被移除。 相反,请考虑使用defuseRNCryptor,它们提供了完整的解决方案,并正在得到维护和正确性保证。 - zaph
3个回答

1
根据: http://php.net/manual/en/function.mcrypt-decrypt.php

警告自PHP 7.1.0起,此函数(mcrypt-decrypt)已被弃用。 强烈不建议依赖此函数。

然后我不得不进行大量调查,直到发现一种使用JavaScript通过openssl加密并使用php解密的方法。
我遇到的唯一问题是当我发送长文本时。那是因为RSA在定义上支持有限长度的字符串。

https://security.stackexchange.com/questions/33434/rsa-maximum-bytes-to-encrypt-comparison-to-aes-in-terms-of-security/33445#33445

根据PKCS#1的定义,RSA加密有大小限制。使用通常的“v1.5填充”和2048位的RSA密钥,可加密的数据最大为245字节。不能再多。
例如,如果我使用1024位的私钥,则可以发送:
"José compró en Perú una vieja zampoña. Excusándose, Sofía tiró su whisky al desagüe de la banqueta."

没有更长的内容。如果我使用512位的私钥,就可以发送。

"José compró en Perú una vieja zampoña. Excusánd"

没有更长的内容。

当字符串过长时,JavaScript控制台会报告:“消息对于RSA过长”

因此,如果您想加密长字符串,则必须在javascript加密之前压缩和分割它们,在解密后在php上连接和解压缩它们,我认为zlib是一个很好的分割/连接解决方案,因为它在javascript和php上都受支持。

也许最好使用AES库而不是像https://github.com/brainfoolong/cryptojs-aes-php这样的库。

我的工作代码如下:

<?php
    //------------------------------------------------------------
    // Global Settings.
    //------------------------------------------------------------
    ini_set('display_errors', 1);
    error_reporting(E_ALL);
    $directorio = "/path/to/key/directory/apache/writable/";
    $nombre_base = "llaves_php";

    //------------------------------------------------------------
    // Initialization.
    //------------------------------------------------------------
    $encabezado_html = "";
    $cuerpo_html = "";

    //------------------------------------------------------------
    // Loading keys
    //------------------------------------------------------------
    list($privateKey, $pubKey) =
        cargar_llaves_RSA($directorio, $nombre_base);

    //------------------------------------------------------------
    // Form that uses javascript to encrypt data.
    // (it uses only the public key)
    //------------------------------------------------------------
    $librerias_html = "
        <script type='text/javascript'
                src='https://ajax.googleapis.com/ajax/libs/".
                    "jquery/3.2.1/jquery.min.js'></script>
        <script type='text/javascript'
                src='lib/jsencrypt.js'></script>
        ";

    $pubKey_html = htmlentities($pubKey);
    $datos_html = "
        <h2>Cifrando con Javascript</h2>
        <input type='text' id='mensaje' />
        <br />
        <button id='ENVIAR'>Enviar</button>
        <br />
        <textarea id='pubkey' style='display: none;'>".
        $pubKey_html.
        "</textarea>
        <script type='text/javascript'>
            $('#ENVIAR').click(function () {
                var codificador = new JSEncrypt();
                codificador.setKey($('#pubkey').val());
                var cifrado = codificador.encrypt($('#mensaje').val());
                window.open('?mensaje=' + encodeURIComponent(cifrado)
                           , '_top');
            });
        </script>
        ";

    //------------------------------------------------------------
    // Decrypting using php (it uses only the privateKey)
    //------------------------------------------------------------
    if (isset($_REQUEST['mensaje'])) {
        openssl_private_decrypt( base64_decode($_REQUEST['mensaje'])
                               , $descifrado
                               , $privateKey);
        $datos_html.= "
            <h2>Descifrando con PHP</h2>
            ".$descifrado."
            ";
    }

    //------------------------------------------------------------
    // HTML DISPLAY
    //------------------------------------------------------------
    $encabezado_html.= "<title>Receptor de mensaje cifrado</title>"
                     . $librerias_html;

    $cuerpo_html.= $datos_html;

    $contenido = "<head>$encabezado_html</head><body>$cuerpo_html</body>";
    $contenido = "<html>$contenido</html>";
    print $contenido;

//============================================================
//============================================================
// Functions
//============================================================
//============================================================

    //------------------------------------------------------------
    function cargar_llaves_RSA($directorio, $nombre_base) {
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // PROPÓSITO: Genera o carga desde archivos las llaves RSA
    // ENTRADAS:
    // $directorio: Directorio donde se encuentran los archivos.
    // $nombre_base: Nombre, sin extensión, de los archivos con
    //               las llaves.
    // SALIDAS:
    //------------------------------------------------------------
        if (  !file_exists($directorio.$nombre_base.".crt")
           || !file_exists($directorio.$nombre_base.".pub")) {
            list($privateKey, $pubKey) = crear_llaves_RSA($directorio.$nombre_base);
        } else {
            //------------------------------------------------------------
            // CARGA DE LLAVES RSA ARCHIVADAS
            //------------------------------------------------------------
            $privateKey = file_get_contents($directorio.$nombre_base.".crt");
        if (!$privKey = openssl_pkey_get_private($privateKey))
            die('Loading Private Key failed');
            $pubKey  = file_get_contents($directorio.$nombre_base.".pub");
        }

    return array($privateKey, $pubKey);
    }

    //------------------------------------------------------------
    function crear_llaves_RSA($ruta_base) {
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // PROPÓSITO:
    // generacion de llaves RSA en php
    // ENTRADAS:
    // $ruta_base: Ruta de los archivos a generar sin extensión.
    // SALIDAS:
    // Se generarán dos archivos, uno con la llave privada con
    // extensión .crt, el otro con llave pública con extensión
    // .pub; la función retorna tanto la llave pública como la
    // privada en un arreglo.
    //------------------------------------------------------------
        $config = array(
            "private_key_bits" => 1024,
            "private_key_type" => OPENSSL_KEYTYPE_RSA,
        );

        $llavePrivadaCruda = openssl_pkey_new($config);
        openssl_pkey_export_to_file($llavePrivadaCruda, $ruta_base.".crt");
        $privateKey = file_get_contents($ruta_base.".crt");
        openssl_pkey_export($llavePrivadaCruda, $privKey);

        $pubKeyData = openssl_pkey_get_details($llavePrivadaCruda);
        $pubKey = $pubKeyData["key"];
        file_put_contents($ruta_base.".pub", $pubKey);
        openssl_free_key($llavePrivadaCruda);

    return array($privateKey, $pubKey);
    }

    //------------------------------------------------------------
    function Mostrar($valor) {
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // PROPÓSITO: Genera el código HTML para presentar una
    // variable embebida en la página.
    // ENTRADAS:
    // $valor: el valor a presentar.
    // SALIDAS: código html que permite visualizar la variable.
    //------------------------------------------------------------
        $retorno = htmlentities(stripslashes(var_export($valor, true)));
        $retorno = "<pre>$retorno</pre>";
        return $retorno;
    }

?>

目录树必须长成这样:

├── script.php
└── lib
    └── jsencrypt.js

需要一个目录,由php在公共区域之外进行写入,并命名为

/path/to/key/directory/apache/writable/

0

我使用了相同的库。必须确保des()结果被修剪,因为在其后有一些奇怪的字符,导致匹配失败。

$password = trim(des("12345678", hexToString("1212121121"), 0, 1, null));

希望能对某些人有所帮助。


0

这个答案是由 Op 作为 问题的编辑 发布的。虽然要求以 答案的形式发布,但它被留作原始问题的编辑。


我已通过使用不同的javascript加密库解决了这个问题。具体做法如下:
我在javascript中使用了Paul Tero的简单DES库,并使用了标准的PHP解密函数。
Javascript代码:
var key = '12345678';
var encrypted = stringToHex(des(key, 'This is something private', 1));

注意事项:
  • 密钥必须(显然)具有偶数个字符。我使用了8个。我想16或更多可能会更好,但正如你可能已经注意到的那样,我对密码学一无所知。
  • stringToHex函数包含在DES JavaScript代码中。

PHP

$key = '12345678'
$unencrypted = trim(mcrypt_decrypt(MCRYPT_DES
                                  , $key
                                  , safeHexToString($encrypted)
                                  , MCRYPT_MODE_ECB))

使用这个辅助函数:
/**
 *
 * Convert input HEX encoded string to a suitable format for decode
 * // Note: JS generates strings with a leading 0x
 *
 * @return string
 */
function safeHexToString($input)
{
    if(strpos($input, '0x') === 0)
    {
        $input = substr($input, 2);
    }
    return hex2bin($input);
}

这对我有用。在我进行一种特别愚蠢的调试时,JavaScript 十六进制编码字符串带有前导 '0x' 导致了一些问题。

所以我希望这可以帮到你。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接