2

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. :)

Community
  • 1
  • 1
JVE999
  • 3,327
  • 10
  • 54
  • 89

3 Answers3

0

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.

Jim Flood
  • 8,144
  • 3
  • 36
  • 48
  • Here `ord($bin[0]) == 0 ? substr($bin, 1) : $bin` is not removing any leading 0's. In the function `asn_integer_to_hex`, `ord($bin[0]) == 0 ? substr($bin, 1) : $bin; returns 010001, $bin; returns 010001, and bin2hex(ord($bin[0]) == 0 ? substr($bin, 1) : $bin); returns 303130303031` The proper value in this case would be `10001`, correct? This is after the `bin2hex` in the echo. – JVE999 Jun 22 '14 at 13:07
  • 1
    @JVE999 I only meant to remove a full zero byte 0x00, which can appear at the start of the modulus in the ASN.1. In the case of 010001 that leading zero is only the first four bits (nibble) of the leading byte 0x01 so I personally would not remove it because I read the value as 0x010001 but if the client parses both 010001 or 10001 as decimal 65,537 then it's fine to remove. I think of hex strings as pairs of hex digits, always an even number of chars. – Jim Flood Jun 22 '14 at 17:48
0

Even though the exponent looks empty in print_r(), it definitely isn't; taking the public key you've generated in the question:

$key = openssl_pkey_get_public('-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt+S0ZxgyQ7BPcmz/JEa7
yEhcKDZTE9TgOF/9cW1w+quFvC43daYmyRpT3asYOm7YPGCmaQ7hUx9XKUUlEdXz
Zr1uvvDyFZdtS45+4nQ5DOI20mZoUHGV82rAMmvf5vote/JJu8Gt01ZUARfsMl+K
DtwpVDHN6LGPBOW8l8abktk1tL/oiwLSVrO2cM/IgBZETDkQpUaZxZx3yUcueEQ+
BFrtS3IYaEny938daQzElNdCaip0f68Ig0gOTPzwkzDOgyOhyjFRxx4aisGzIlXu
TFkqzIz7oC3JysgS5EhlwmsEIAelbZWpgc17HK2aWIzqlT99hB+kKv2fauxH/fgT
nQIDAQAB
-----END PUBLIC KEY-----');
$details = openssl_pkey_get_details($key);

echo bin2hex($details['rsa']['e']); // "010001"
Ja͢ck
  • 170,779
  • 38
  • 263
  • 309
  • Thus, something similar to`preg_match('/Exponent:\s([^\s]+)/',exec('openssl rsa -pubin -inform PEM -text -noout'),$matches); $exponent = $matches[1];`, except with a public key input? – JVE999 Jun 22 '14 at 13:12
  • @JVE999 Actually, I wasn't thinking straight; the exponent is returned in `$details['rsa']['e']`, you just can't use `print_r()` to debug it. – Ja͢ck Jun 22 '14 at 23:58
  • Even on client side `console.log(data)` after the response lists a value for the modulus, but not the exponent (the exponent is blank). – JVE999 Jun 23 '14 at 02:19
  • @JVE999 How do you populate `data`? Your question doesn't show that. – Ja͢ck Jun 23 '14 at 03:00
  • I simplified the question. What I'm doing is echoing a JSON array of `$details['rsa']['n']` and `$details['rsa']['e']` using `json_encode(array('n'=>to_hex($keys[1]['n']),'e'=>to_hex($keys[1]['e'])))` after getting the keys from `genKeys()`, then the data is received via jQuery's `$.post(page,sendData,function(data){console.log(data);});` it outputs when I use Jim Flood's answer, but not with just php's openssl functions. – JVE999 Jun 24 '14 at 18:15
  • @JVE999 Shouldn't that be `json_encode(['n' => to_hex($keys[1]['rsa'][n'], 'e' => to_hex($keys[1]['rsa']['e']])` instead? Also, I didn't see an update to the question. – Ja͢ck Jun 24 '14 at 18:20
  • @JimFlood Yeah, not sure what's going on here; must be something we're missing. – Ja͢ck Jun 24 '14 at 19:20
  • Oops, I deleted my comment by mistake. I wrote that I also see $details['rsa']['e'] on my system -- echo bin2hex'd just fine. – Jim Flood Jun 24 '14 at 19:24
  • I agree, it does look like something's wrong with the openssl or php installation here. – JVE999 Jun 24 '14 at 19:26
  • @JVE999 Just realised that the code in my earlier answer missed a call to `openssl_pkey_get_public()`; updated now ... my apologies. – Ja͢ck Jun 24 '14 at 19:33
0

Here's how I'd do it (far simpler than the proposed methods);

<?php
include('Crypt/RSA.php');

$rsa = new Crypt_RSA();
$rsa->loadKey('-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt+S0ZxgyQ7BPcmz/JEa7
yEhcKDZTE9TgOF/9cW1w+quFvC43daYmyRpT3asYOm7YPGCmaQ7hUx9XKUUlEdXz
Zr1uvvDyFZdtS45+4nQ5DOI20mZoUHGV82rAMmvf5vote/JJu8Gt01ZUARfsMl+K
DtwpVDHN6LGPBOW8l8abktk1tL/oiwLSVrO2cM/IgBZETDkQpUaZxZx3yUcueEQ+
BFrtS3IYaEny938daQzElNdCaip0f68Ig0gOTPzwkzDOgyOhyjFRxx4aisGzIlXu
TFkqzIz7oC3JysgS5EhlwmsEIAelbZWpgc17HK2aWIzqlT99hB+kKv2fauxH/fgT
nQIDAQAB
-----END PUBLIC KEY-----');

$publickey = $rsa->getPublicKey(CRYPT_RSA_PUBLIC_FORMAT_RAW);

echo $publickey['e'];

You'll need phpseclib, a pure PHP RSA implementation to do it.

neubert
  • 15,947
  • 24
  • 120
  • 212
  • How is downloading a library and writing five lines of code "far simpler" than just two lines with the standard openssl route? – Ja͢ck Jun 24 '14 at 19:19