9

I have a web-service in php that generates a keypair to encrypt a message, and one application in java that retrives the privatekey and decrypt the message.

For php I'm using http://phpseclib.sourceforge.net/ and have this two files:

keypair.php

<?php

set_time_limit(0);
if( file_exists('private.key') )
{
    echo file_get_contents('private.key');
}
else
{
    include('Crypt/RSA.php');
    $rsa = new Crypt_RSA();
    $rsa->createKey();
    $res = $rsa->createKey();

    $privateKey = $res['privatekey'];
    $publicKey  = $res['publickey'];

    file_put_contents('public.key', $publicKey);
    file_put_contents('private.key', $privateKey);
}

?>

encrypt.php

<?php

include('Crypt/RSA.php');

//header("Content-type: text/plain");

set_time_limit(0);
$rsa = new Crypt_RSA();
$rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_OAEP);
$rsa->loadKey(file_get_contents('public.key')); // public key

$plaintext = 'Hello World!';
$ciphertext = $rsa->encrypt($plaintext);

echo base64_encode($ciphertext);

?>

and in java I have this code:

package com.example.app;

import java.io.DataInputStream;
import java.net.URL;
import java.security.Security;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

import sun.misc.BASE64Decoder;

public class MainClass {

    /**
     * @param args
     */
    public static void main(String[] args)
    {
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

        try {
            BASE64Decoder decoder   = new BASE64Decoder();
            String b64PrivateKey    = getContents("http://localhost/api/keypair.php").trim();
            String b64EncryptedStr  = getContents("http://localhost/api/encrypt.php").trim();

            System.out.println("PrivateKey (b64): " + b64PrivateKey);
            System.out.println(" Encrypted (b64): " + b64EncryptedStr);

            SecretKeySpec privateKey    = new SecretKeySpec( decoder.decodeBuffer(b64PrivateKey) , "AES");
            Cipher cipher               = Cipher.getInstance("RSA/None/OAEPWithSHA1AndMGF1Padding", "BC");
            cipher.init(Cipher.DECRYPT_MODE, privateKey);

            byte[] plainText            = decoder.decodeBuffer(b64EncryptedStr);

            System.out.println("         Message: " + plainText);
        }
        catch( Exception e )
        {
            System.out.println("           Error: " + e.getMessage());
        }

    }

    public static String getContents(String url)
    {
        try {
            String result = "";
            String line;
            URL u = new URL(url);
            DataInputStream theHTML = new DataInputStream(u.openStream());
            while ((line = theHTML.readLine()) != null)
                result = result + "\n" + line;

            return result;
        }
        catch(Exception e){}

        return "";
    }
}

My questions are:

  1. Why I'm having a exception saying "not an RSA key!"?
  2. How can I improve this code? I have used base64 to avoid encoding and comunication errors between Java and PHP.
  3. This concept is correct? I mean, I'm using it correctly?
Alexandre Leites
  • 278
  • 1
  • 4
  • 15

4 Answers4

4

With the help of above answer, I got that SecretKeySpec is used wrong, and I found that PEM file from OpenSSL isn't a 'standart format', so I need to use the PEMReader to convert it to PrivateKey class.

Here is my working class:

package com.example.app;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.StringReader;
import java.net.URL;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.Security;

import javax.crypto.Cipher;

import org.bouncycastle.openssl.PEMReader;

import sun.misc.BASE64Decoder;

public class MainClass {

    /**
     * @param args
     */
    public static void main(String[] args)
    {
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

        try {
            BASE64Decoder decoder   = new BASE64Decoder();
            String b64PrivateKey    = getContents("http://localhost/api/keypair.php").trim();
            String b64EncryptedStr  = getContents("http://localhost/api/encrypt.php").trim();

            System.out.println("PrivateKey (b64): " + b64PrivateKey);
            System.out.println(" Encrypted (b64): " + b64EncryptedStr);

            byte[] decodedKey           = decoder.decodeBuffer(b64PrivateKey);
            byte[] decodedStr           = decoder.decodeBuffer(b64EncryptedStr);
            PrivateKey privateKey       = strToPrivateKey(new String(decodedKey));

            Cipher cipher               = Cipher.getInstance("RSA/None/OAEPWithSHA1AndMGF1Padding", "BC");
            cipher.init(Cipher.DECRYPT_MODE, privateKey);


            byte[] plainText            = cipher.doFinal(decodedStr);

            System.out.println("         Message: " + new String(plainText));
        }
        catch( Exception e )
        {
            System.out.println("           Error: " + e.getMessage());
        }

    }

    public static String getContents(String url)
    {
        try {
            String result = "";
            String line;
            URL u = new URL(url);
            DataInputStream theHTML = new DataInputStream(u.openStream());
            while ((line = theHTML.readLine()) != null)
                result = result + "\n" + line;

            return result;
        }
        catch(Exception e){}

        return "";
    }

    public static PrivateKey strToPrivateKey(String s)
    {
        try {
            BufferedReader br   = new BufferedReader( new StringReader(s) );
            PEMReader pr        = new PEMReader(br);
            KeyPair kp          = (KeyPair)pr.readObject();
            pr.close();
            return kp.getPrivate();
        }
        catch( Exception e )
        {

        }

        return null;
    }
}

Here is my keypair.php

<?php

set_time_limit(0);
if( file_exists('private.key') )
{
    echo base64_encode(file_get_contents('private.key'));
}
else
{
    include('Crypt/RSA.php');

    $rsa = new Crypt_RSA();
    $rsa->setHash('sha1');
    $rsa->setMGFHash('sha1');
    $rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_OAEP);
    $rsa->setPrivateKeyFormat(CRYPT_RSA_PRIVATE_FORMAT_PKCS1);
    $rsa->setPublicKeyFormat(CRYPT_RSA_PUBLIC_FORMAT_PKCS1);

    $res = $rsa->createKey(1024);

    $privateKey = $res['privatekey'];
    $publicKey  = $res['publickey'];

    file_put_contents('public.key', $publicKey);
    file_put_contents('private.key', $privateKey);

    echo base64_encode($privateKey);
}

?>

and my encrypt.php

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

    $rsa = new Crypt_RSA();
    $rsa->setHash('sha1');
    $rsa->setMGFHash('sha1');
    $rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_OAEP);

    $rsa->loadKey(file_get_contents('public.key')); // public key

    $plaintext  = 'Hello World!';
    $ciphertext = $rsa->encrypt($plaintext);
    $md5        = md5($ciphertext);
    file_put_contents('md5.txt', $md5);
    file_put_contents('encrypted.txt', base64_encode($ciphertext));

    echo base64_encode($ciphertext);

?>

I hope it helps anyone and thanks.

Alexandre Leites
  • 278
  • 1
  • 4
  • 15
  • Your code is the best I could find. The problem is, I can't register BouncyCastleProvider. I get the following error "Error: JCE cannot authenticate the provider BC" – MrD Oct 06 '16 at 12:25
3

A few thoughts.

  1. Shouldn't you be echo'ing out $privatekey in the else as well?

  2. Are you using the latest Git version of phpseclib? I ask because a while ago there was this commit:

    https://github.com/phpseclib/phpseclib/commit/e4ccaef7bf74833891386232946d2168a9e2fce2#phpseclib/Crypt/RSA.php

    The commit was inspired by https://stackoverflow.com/a/13908986/569976

  3. Might be worthwhile if you change your tags up a bit to include bouncycastle and phpseclib. I'd add those tags but some tags will have to be removed since you're already at the limit of 5. I'll let you decide which ones to remove (if you even want to do that).

Community
  • 1
  • 1
neubert
  • 14,208
  • 21
  • 90
  • 172
2
SecretKeySpec privateKey    = new SecretKeySpec( decoder.decodeBuffer(b64PrivateKey) , "AES");

b64PrivateKey is supposed to contain the private key right? 'cause looking it up in the docs it looks like SecretKeySpec is only intended for symmetric algorithms (like AES) - not asymmetric ones like RSA.

1

I did some digging into the classes you're using and it seems like what you've posted has most of the default parameters matching your explicit parameters. However, this doesn't ensure that your configuration has those all set to match the current documentation if you're using older implementations.

Also, a tip from a senior Facebook security engineer who discussed a similar issue in a lecture recently; different libraries implementing the same security protocols will oftentimes be incompatible and even the same libraries in different environments or languages will oftentimes fail to work together. With that in mind, a few things to try given that working examples similar to your setup exist online:

Make sure you're using the latest versions of libraries. Also note that some javax functions and classes are deprecated and suggest using java.security now (didn't check if this applies to your case).

Force the key size to be consistent (1024 or 2048). The java implementation can do either and I didn't find consistent documentation for both libraries saying what your configuration default would be used (could be causing problem #2, though you might get a different exception for invalid key size).

Ensure that your privatekey matches expectation (length/reads the same between java and php).

Force default values to be explicitly defined (set CryptRSA hash to sha1, key lengths, anything else you can explicitly set).

Try encrypting the same message with both java and php to see if you can get the same outputs. Do this after ensuring your keys read the same and don't throw exceptions while being used in both applications. Encrypting a single character can tell you if the same padding scheme is actually being used (it appears from the source code that both use MGF1, but it never hurts to check outputs).

Finally try taking an example for php to java encryption which is said to already work and do one change at a time until you get back to your current encryption scheme. I saw a few examples googling quickly which used different parameters and settings with CryptRSA and java security that stated they were working together. Take a working example and try swapping the hash function and then the encryption, etc.

Pyrce
  • 7,448
  • 2
  • 25
  • 44