Search code examples
javascriptphpopensslcryptographycryptojs

Using CryptoJS to decrypt a ciphertext from openssl_seal


I need to encode message with PHP using public key and decode this message with javascript using private key.

This is test code:

<?php
$message = 'sevenflash';

$public_key = openssl_get_publickey(file_get_contents('pubkey.pem'));

$encrypted = $e = NULL;
openssl_seal($message, $encrypted, $e, array($public_key));

$sealed_data = base64_encode($encrypted);
$envelope = base64_encode($e[0]);

$unsealed = null;
openssl_open(base64_decode($sealed_data), $unsealed, base64_decode($envelope), file_get_contents('privatekey.pem'));
echo $unsealed; // This is sevenflash

$embed = "<script>var encrypted = '$sealed_data'; var envelope = '$envelope'; </script>";
?>
<html>
<head>
    <script type="text/javascript" src="rc4.js"></script>
    <script type="text/javascript" src="enc-base64.js"></script>
    <script type="text/javascript" src="jsencrypt/bin/jsencrypt.js"></script>
    <?php echo $embed; ?>
    <script>
        var private_key = `...`;

        var crypt = new JSEncrypt();
        crypt.setPrivateKey(private_key);
        var theKey = crypt.decrypt(envelope);

        var decrypted = CryptoJS.RC4.decrypt(encrypted, theKey);
        console.log(decrypted.toString()); // This must be sevenflash, but this is 696a2b0440220d69e8c3
    </script>
</head>
<body>

</body>
</html>

But this doesn't work. Why?


Solution

  • The CryptoJS decryption expects a CipherParams object in place of the ciphertext and a WordArray in place of the key. If you pass in the key as a string, then it will assume that it is a password and try to derive a key from that with MD5 and a non-existent salt.

    Try to parse everything:

    var decrypted = CryptoJS.RC4.decrypt({
        ciphertext: CryptoJS.enc.Base64.parse(encrypted)
    }, CryptoJS.enc.Latin1.parse(theKey));
    

    JSEncrypt returns a "plain string" from the crypt.decrypt() function, which means that it has to be either parsed with Latin1 or UTF-8 encoding in mind.
    But I couldn't make it work with JSEncrypt, so I used forge instead:

    var privateKey = forge.pki.privateKeyFromPem(private_key);
    var theKey = privateKey.decrypt(forge.util.decode64(envelope), 'RSAES-PKCS1-V1_5');
    

    CrypoJS' .toString() returns a Hex-encoded version by default. You can override that to get textual data:

    console.log(decrypted.toString(CryptoJS.enc.Utf8));
    

    var private_key = `-----BEGIN RSA PRIVATE KEY-----
    MIIEpAIBAAKCAQEAx1u1OPc+KT1vmLea4ro0VtdiQa9Pj7oFMF04uIPKb5BEuN6w
    rHj3dQkdoeT3vWZaR/eAOnxSIgl4PwNm9C6AeW5vHAq/4XFp4FZbtbHfqCm3h6tT
    m8FxmuTZQVr+Kgrkgiob/C5+bWHhD+rfUnaW8CGnvmhHD4AtxCVimMUhYLCSklkX
    YOWng9aRO5kAvDx0KXyZ60YsJJwUouPat/vBdoHljRZhzOb7KHSek4yd//0zBmBW
    3y9z+KG8WrpGEoCutGE14QBGh/EghRP9QAhfGV4a9kfD77S0Spu7uzmfzBuyyit5
    Dhtcact4SJuJz2617I1gujGe51j5Oyex70hoEQIDAQABAoIBACJl5IMLxiH/g73q
    EEOGtXk2eLG0uhmmkzahHyRXORq+3dy4L+vSg4AiLoa8pzJ68qDwR6PPx6wvzhux
    TpZbX7eUIn/IKwuPMiGPRQu4wUqWyCAUo7yASEXdrs7kEG7Yqkz7TxDYROBCvz6r
    3BSyeaC9beAgxgIar8rkWHrbqMKx5koX+3nopn2vrEQWTsP5PebMnC/2u3k3BfIW
    5ucgaYlbrzfYULx2DZdBEuBtourE0yQePxNvTDd6tjcSnH/G/pZdy9GXamvgbIRH
    5YCzO9gaiEzRgbddX96AHNXK7LmmSOvX4mdzjmkNb4yKud8+/fpX9fa4sIzKBMuB
    15rdfzECgYEA/58+QBu2rjmE5qA6uMQYtzF7AvLK2ZfhfBs6lt/ItnlQ283sKaHS
    vkzjmvVVq7hjbvjvFJ6aUAwzZ9XhOjbdpvkwVhGZfNdx7ia6ctH9q0Cq8ksmhVbD
    SSEklWK5k5OcXbd8DiVClX8hRR13WbhK+L9Ct4/4+/2kA9bnsJKi92cCgYEAx6cr
    A8faHUdIHSo01S8bRijCOVLKNXuOsgqVxe05RrcqJI/QpaHTFPvVm1rUb97sq9pW
    eQANrWoONmoGVWzmKzK3Hk6mLSKfbPEhclQgVTsrlp4Mil9e06pqqw6FmuxFrk/5
    FCws84vyijzt4/qvh1tXJoX7+xSSa82v86wJ0ccCgYEA0FhSvJ3Z/5cSI4dlVUM/
    gdDqmjSZ8VBBkI4A6wfq6A9pQD5OxoAG7ORuftaHxTa8lK6A7yV2UNgg7fj9Yh1l
    +7oPE5e0qnRPf/RA82UEhJhovKNT+koFUY2Z68hTyVz+jcQHcOAdrQu5akPiC1aS
    hLwe/M5z+a96B0caAGkEOvECgYEAkOM3zB6Uovq/0qp2fN8ao+egS4lRRU7NV1dn
    KaPmFPOHmdDkUc8GKAw6D+vyjtzwVCa26kinpFl6xvJadlP5FRBQ46Wf4tFlcOHs
    q9NxQ2kVUNCRuTlMz760ej6npq4gQ0e2MnV8iaHd+YQN/4xFJa2bqVFCdAuR1roG
    HoeS+LECgYAsJhCTNQUCeqvXO2cdJTQ1da3ug/P3c0Eg5DyrB7w7Fwtq/eeSsZOf
    R1et6S+aRaEfclx2vEF9Ck4hcTESRNZrZJ4i5hB4y//B1og777H/jrT7H1NZfU11
    Iwly+S/SlgTR9BrXCJsfenslolp/rMgiD2G8sky5yFRmrDB7qj8/Nw==
    -----END RSA PRIVATE KEY-----`;
    
    var encrypted = 'E+moZJOeVuxgsg=='; var envelope = 'LMJqdAu+FVPxVpVkLgBOrqLWkY/S/56KHETnt528k2D0lUDzl7nymsgycrto5BD4w17oH6THyC+0yg/‌​Ws+gqpIIWimcQze1KKQy+1+uvH4gR1Olt22cgEUDid09dDqvSXi0VcYYYQXyxWzIQVd2Jun1A0h1qhWc4‌​LinzkJSP6HuhH+D52eEcXep5H4cjjy62rgGIR5YyQO/RBXuYYBBRSgmeniFjY9FbR8AaBkhcCVYeBH28u‌​Bi0dx27hPQ0yVlVWrG3KuR2i441Ruhb4J+AOnr+K1uIMT+rnk8Th8eTTc/E1CSrZo9GASfEyYK8mJdwfq‌​2/FJ5Qnp9uHl1aN/1RSg==';
    
    var privateKey = forge.pki.privateKeyFromPem(private_key);
    var theKey = privateKey.decrypt(forge.util.decode64(envelope), 'RSAES-PKCS1-V1_5');
    
    var decrypted = CryptoJS.RC4.decrypt({
        ciphertext: CryptoJS.enc.Base64.parse(encrypted)
    }, CryptoJS.enc.Latin1.parse(theKey));
    console.log(decrypted.toString(CryptoJS.enc.Utf8));
    <script type="text/javascript" src="https://cdn.rawgit.com/CryptoStore/crypto-js/3.1.2/build/rollups/rc4.js"></script>
    <script type="text/javascript" src="https://cdn.rawgit.com/CryptoStore/crypto-js/3.1.2/build/components/enc-base64-min.js"></script>
    <script type="text/javascript" src="https://rawgit.com/artjomb/96b970358e20410fa64daa2e844aeb0f/raw/5375e7171ef297d436d65b962149dcc0e1960b2b/forge_v0.6.39.min.js"></script>