Шифрование PHP, Расшифровка Java


У меня есть веб-сервис на php, который генерирует пару ключей для шифрования сообщения, и одно приложение на java, которое повторно использует приватный ключ и расшифровывает сообщение.

Для php я использую http://phpseclib.sourceforge.net / и иметь эти два файла:

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);

?>

И в java у меня есть такой код:

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 "";
    }
}

Мои вопросы таковы:

  1. Почему у меня есть исключение, говорящее "не ключ RSA!"?
  2. Как можно Я улучшаю этот код? Я использовал base64, чтобы избежать ошибок кодирования и связи между Java и PHP.
  3. Эта концепция верна? Я имею в виду, я правильно его использую?
Author: Alexandre Leites, 2013-03-26

4 answers

С помощью приведенного выше ответа я понял, что SecretKeySpec используется неправильно, и я обнаружил, что файл PEM из OpenSSL не является "стандартным форматом", поэтому мне нужно использовать PEMReader для преобразования его в класс PrivateKey.

Вот мой рабочий класс:

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;
    }
}

Вот мой 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);
}

?>

И мой 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);

?>

Я надеюсь, что это кому-нибудь поможет, и спасибо.

 4
Author: Alexandre Leites, 2013-03-27 13:34:14

Несколько мыслей.

  1. Разве вы не должны повторять $privatekey и в другом месте?

  2. Используете ли вы последнюю версию Git phpseclib? Я спрашиваю, потому что некоторое время назад была такая фиксация:

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

    Фиксация была вдохновлена https://stackoverflow.com/a/13908986/569976

  3. Может быть стоит, если вы немного измените свои теги, чтобы включить bouncycastle и phpseclib. Я бы добавил эти теги, но некоторые теги придется удалить, так как вы уже достигли предела в 5. Я позволю вам решить, какие из них удалить (если вы вообще захотите это сделать).

 3
Author: neubert, 2017-05-23 12:33:14
SecretKeySpec privateKey    = new SecretKeySpec( decoder.decodeBuffer(b64PrivateKey) , "AES");

B64privatekey должен содержать закрытый ключ, верно? потому что, глядя на это в документах, похоже, что SecretKeySpec предназначен только для симметричных алгоритмов (таких как AES), а не для асимметричных, таких как RSA.

 2
Author: , 2013-03-27 05:03:18

Я немного покопался в классах, которые вы используете, и похоже, что в том, что вы опубликовали, большинство параметров по умолчанию соответствуют вашим явным параметрам. Однако это не гарантирует, что в вашей конфигурации все они настроены в соответствии с текущей документацией, если вы используете более старые реализации.

Кроме того, совет старшего инженера по безопасности Facebook, который недавно обсуждал аналогичную проблему на лекции; разные библиотеки, реализующие одни и те же протоколы безопасности, будут часто они несовместимы, и даже одни и те же библиотеки в разных средах или языках часто не могут работать вместе. Имея это в виду, несколько вещей, которые нужно попробовать, учитывая, что рабочие примеры, подобные вашей настройке, существуют в Интернете:

Убедитесь, что вы используете последние версии библиотек. Также обратите внимание, что некоторые функции и классы javax устарели и предлагают использовать java.security сейчас (не проверял, применимо ли это к вашему случаю).

Принудительно установите согласованный размер ключа (1024 или 2048). Реализация java может сделать и то, и другое, и я не нашел согласованной документации для обеих библиотек, в которой говорилось бы, что будет использоваться ваша конфигурация по умолчанию (может вызвать проблему № 2, хотя вы можете получить другое исключение для недопустимого размера ключа).

Убедитесь, что ваш приватный ключ соответствует ожиданиям (длина/читается одинаково между java и php).

Принудительно укажите значения по умолчанию, которые должны быть явно определены (установите хэш CryptRSA в sha1, длины ключей, все остальное, что вы можете явно заданный).

Попробуйте зашифровать одно и то же сообщение с помощью java и php, чтобы посмотреть, сможете ли вы получить одинаковые выходные данные. Сделайте это после того, как убедитесь, что ваши ключи читаются одинаково, и не создавайте исключений при использовании в обоих приложениях. Шифрование одного символа может сказать вам, действительно ли используется одна и та же схема заполнения (из исходного кода следует, что оба используют MGF1, но никогда не помешает проверить выходные данные).

Наконец, попробуйте взять пример шифрования php на java, который сказано, что вы уже работаете и вносите по одному изменению за раз, пока не вернетесь к своей текущей схеме шифрования. Я видел несколько примеров быстрого поиска в Google, в которых использовались разные параметры и настройки с помощью CryptRSA и java security, в которых говорилось, что они работают вместе. Возьмите рабочий пример и попробуйте поменять местами хэш-функцию, а затем шифрование и т. Д.

 1
Author: Pyrce, 2013-03-26 17:58:03