Search code examples
javascriptphpencryptionopensslrsa

openssl_pkey_get_details($res) returns no public exponent


I'm using this example for javascript encryption using a key generated with the php openssl library, but $details = openssl_pkey_get_details($resource) is returning no public exponent ($details['rsa']['e']).

This is how I'm generating it:

function genKeys() {
    // Create the keypair
    $res=openssl_pkey_new();
    // Get private key
    $pass = bin2hex(mcrypt_create_iv(100, MCRYPT_DEV_URANDOM));
    openssl_pkey_export($res, $pk, $pass);

    $details = openssl_pkey_get_details($res);
    print_r($details);
    $details = array('n'=>$details['rsa']['n'],'e'=>$details['rsa']['e']);

    return array($pk,$details,$pass);
}
function to_hex($data)
{
    return strtoupper(bin2hex($data));
}
$details = genKeys()[1];

When I use print_r to print out the $details array, I get:

Array
(
    [bits] => 2048
    [key] => -----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt+S0ZxgyQ7BPcmz/JEa7
yEhcKDZTE9TgOF/9cW1w+quFvC43daYmyRpT3asYOm7YPGCmaQ7hUx9XKUUlEdXz
Zr1uvvDyFZdtS45+4nQ5DOI20mZoUHGV82rAMmvf5vote/JJu8Gt01ZUARfsMl+K
DtwpVDHN6LGPBOW8l8abktk1tL/oiwLSVrO2cM/IgBZETDkQpUaZxZx3yUcueEQ+
BFrtS3IYaEny938daQzElNdCaip0f68Ig0gOTPzwkzDOgyOhyjFRxx4aisGzIlXu
TFkqzIz7oC3JysgS5EhlwmsEIAelbZWpgc17HK2aWIzqlT99hB+kKv2fauxH/fgT
nQIDAQAB
-----END PUBLIC KEY-----

    [rsa] => Array
        (
            [n] => ���g2C�Orl�$F��H\(6S��8_�qmp����.7u�&�Sݫ:n�<`�i�SW)E%��f�n����mK�~�t9�6�fhPq��j�2k���-{�I����VT�2_��)T1�豏弗ƛ��5�����V��p�ȀDL9�F�Ŝw�G.xD>Z�KrhI��iĔ�Bj*t��HL���0΃#��1Q����"U�LY*̌��-����He�k �m����{��X���?}��*��j�G���
            [e] => 
            [d] => ~����G�P�t���@��5��z�nEk�m���    qИ���i�k�%�ĨS���{/�:(��0�И<MS��ʓ�r�kڷ��lRu}q��?���V���g|�i��H��]2-X%U��R�\|9h�Xs��&g���܉9S8�\����bL�_`[.w}6��d�Ù
IroD�N�*��\�Q��3|���X�k7�mYs����.�m���Ã�#��~�ǀ�8{�L�s`�O���]�T��
��
            [p] => ���ɺ;�n%\,b4�]7��)��Z���е삽�66i8a�`��P#�?.�ޙ,���sq��L�HF����{8��C ���"�
>H,���A������������H�g��̓3G�mBrY`�S�
            [q] => ���.VӦ�(����hZ�jTY���3���B��ք9SuMw&.^�Ƹ�d�T!9i�u�K�#�*Fc�FY��*\�iO0b���Б]iei���  �OMDӒw,V�wӾK��r�%X��[��˓4=-�h�2
            [dmp1] => �ី���X��U�ܵ���}�-#́�|~�.�=�0���SjN@����V+A�<e!$3��~�"��g�������~s��   y
x5�i��(�Y�X�;X�Tn���<w�$#�#��P�3�d�Uk�
            [dmq1] => �$�!Q3��Zk�{ӗ�\����I2[*V5���&kے��yr�����b�[1gpc�y?�0Gf3��i���=א�!ܜ�7�a^܉I��a$����v�x����˲�[=��ʹW�'���%�"�B
            [iqmp] => &���jx�� ������&��'��Ya�B�����)��H-�<�uĮ1��H���Fwy����Xbt[;����I�2*�6���������i�ډ���3@�;�Lt.�׽��`h�qb�N�2�"����
        )

    [type] => 0
)

So, in javascript, when I use:

var rsa = new RSAKey();
rsa.setPublic('<?php echo to_hex($details['rsa']['n']) ?>', '<?php echo to_hex($details['rsa']['e']) ?>');

I am inputting nothing for the public exponent, and when I try to decrypt it on the server, it returns nothing.

Which, because that's the only possibility I have found, I think it may be the reason for the error.

I'm decrypting it with:

function prKeyDecrypt($data,$prKey,$passKey){
    $data = pack('H*', $data);
    $pkres = openssl_pkey_get_private($prKey,$passKey);
    if (openssl_private_decrypt($data, $r, $pkres)) {
       return $r;
    } else {
        return "error";
    }
}
if(isset($_POST['data'])echo prKeyDecrypt($_POST['data'],$prKey,$passKey);

Where $prKey and $passKey are both gotten from the previous genKeys() statement.

Is there a different way to do this, or perhaps a different way to use the public key information generated in php on the server to encrypt data in javascript on the client? This is set up nicely, so I'd like to use what I have, but if there's another way that it'll work (such as a different javascript library), well, working is better than not working. :)


Solution

  • This is an interesting puzzle. Here is a round-about way to get the modulus and public exponent out.

    You can find a simple ASN.1 parser written in PHP here. After you produce the RSA key pair:

    // Create the keypair
    $res = openssl_pkey_new();
    $details = openssl_pkey_get_details($res);
    

    You can convert the public key from PEM to DER format for the ASN.1 parser, and then feed it to the parser:

    function pem2der($pem)
    {
        $matches = array();
        preg_match('~^-----BEGIN ([A-Z ]+)-----\s*?([A-Za-z0-9+=/\r\n]+)\s*?-----END \1-----\s*$~D', $pem, $matches);
        return base64_decode(str_replace(array("\r", "\n"), array('', ''), $matches[2]));
    }
    
    $der = pem2der($details['key']);
    $asn = ASN_BASE::parseASNstring($der);
    

    Then you can reach into the ASN.1 format of the public key, and pull out the modulus and exponent -- we know directly where to find them.

    This particular ASN.1 parser collects the values in a modified-Base64 format which can be reversed and then the value converted to hex for transfer to the client:

    function asn_integer_to_hex($value)
    {
        // The ASN.1 parser strtr'd these -- strtr them back
        $bin = base64_decode(strtr($value, '-_', '+/'));
        // Remove any leading 0x00 byte, too, and return hex
        return bin2hex(ord($bin[0]) == 0 ? substr($bin, 1) : $bin);
    }
    
    $arr = $asn[0]->data[1]->data[0]->data;
    $n = asn_integer_to_hex($arr[0]->value);
    $e = asn_integer_to_hex($arr[1]->value);
    

    These should match the details found in the private key:

    echo "$details n: ".bin2hex($details['rsa']['n'])."\n";
    echo "$details e: ".bin2hex($details['rsa']['e'])."\n";
    
    echo "n: ".$n."\n";
    echo "e: ".$e."\n";
    

    That is, if $details['rsa'] has anything to show. I can't explain why ['e'] is empty in your case, but you should be able to pull the exponent out of the public key by parsing the ASN.1.