加密 PHP,解密 Java

9
我是一名有用的助手,可以为您翻译文本。

我有一个使用php编写的Web服务,它生成密钥对以加密消息,还有一个使用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. 这个概念是正确的吗?我的意思是,我正在正确地使用它吗?

预先编码的数据是否与解码后的base64数据匹配? - Diego Agulló
是的,我现在已经测试过了,PHP和Java字符串解码后的md5校验和是相同的。 - Alexandre Leites
1
也许我的SecretKeySpec是错误的?我尝试更改算法的值,但没有成功。 - Alexandre Leites
4个回答

4

在上面的答案帮助下,我知道 SecretKeySpec 的使用是错误的,我发现 OpenSSL 的 PEM 文件不是一种“标准格式”,因此我需要使用 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);

?>

希望这能帮助到任何人,谢谢。


你的代码是我能找到的最好的。问题是,我无法注册BouncyCastleProvider。我收到以下错误:“错误:JCE无法验证提供程序BC”。 - MrD

3

一些想法。

  1. 在else中,你不应该也输出$privatekey吗?

  2. 你是否正在使用最新的Git版本phpseclib?我问这个是因为有一段时间以前有一个提交:

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

    这个提交受到了https://dev59.com/pGYr5IYBdhLWcg3wKHEp#13908986的启发。

  3. 如果你改变一下标签,加上bouncycastle和phpseclib,可能会更有价值。我可以添加这些标签,但由于已经达到5个标签的限制,所以必须删除一些标签。我会让你决定要删除哪些标签(如果你想这样做的话)。


我正在使用phpseclib的git版本,并且已经更改了我的问题标签。我已经更改了我的主要内容,使其更加清晰: - Alexandre Leites

2
SecretKeySpec privateKey    = new SecretKeySpec( decoder.decodeBuffer(b64PrivateKey) , "AES");
b64PrivateKey 应该包含私钥,对吗?因为在文档中查找它时,似乎 SecretKeySpec 仅用于对称算法(如 AES),而不是非对称算法,例如 RSA。

1
我对你使用的类进行了一些调查,看起来你发布的大多数默认参数都与你明确指定的参数匹配。然而,这并不能保证你的配置已经设置为与当前文档匹配,如果你正在使用旧版本的实现。
此外,最近一位Facebook高级安全工程师在讲座中讨论了类似问题时给出了一个提示:不同的库实现相同的安全协议通常是不兼容的,甚至不同环境或语言中的同一库也通常无法协同工作。鉴于这一点,考虑到与你设置类似的工作示例在线上存在,有几件事情可以尝试:
确保你正在使用最新版本的库。此外,请注意,一些javax函数和类已被弃用,并建议现在使用java.security(我没有检查是否适用于你的情况)。
强制密钥大小保持一致(1024或2048)。Java实现可以执行任何一个操作,我没有找到两个库的一致文档说你的配置默认值将被使用(可能会导致问题#2,但你可能会得到不同的异常,表示无效的密钥大小)。
确保你的私钥符合预期(长度/在Java和PHP之间读取相同)。
强制默认值被明确定义(将CryptRSA哈希设置为sha1,密钥长度,任何其他你可以明确设置的内容)。
尝试使用Java和PHP加密相同的消息,以查看是否可以获得相同的输出。在两个应用程序中使用密钥时,请确保它们读取相同并且不会在使用过程中抛出异常。加密单个字符可以告诉您是否实际上正在使用相同的填充方案(从源代码中看来,两者都使用MGF1,但检查输出永远不会有坏处)。
最后,尝试使用已经工作的PHP到Java加密示例,并逐个更改,直到回到当前的加密方案。我快速谷歌了一些例子,它们使用CryptRSA和Java安全性的不同参数和设置,声明它们可以一起工作。找一个可行的示例,然后尝试交换哈希函数,然后是加密等等。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接