9

我有一个 php 中的 web 服务,它生成一个密钥对来加密消息,还有一个 java 中的应用程序可以检索私钥并解密消息。

对于 php,我使用http://phpseclib.sourceforge.net/并拥有这两个文件:

密钥对.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);
}

?>

加密.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. 这个概念正确吗?我的意思是,我使用正确吗?
4

4 回答 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);

?>

我希望它对任何人都有帮助,谢谢。

于 2013-03-27T13:34:14.077 回答
3

一些想法。

  1. 你不应该在 else 中回显 $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 个的限制。我会让你决定要删除哪些标签(如果你甚至想这样做的话)。

于 2013-03-26T17:38:16.613 回答
2
SecretKeySpec privateKey    = new SecretKeySpec( decoder.decodeBuffer(b64PrivateKey) , "AES");

b64PrivateKey 应该包含私钥对吗?因为在文档中查找它看起来 SecretKeySpec 仅适用于对称算法(如 AES) - 而不是像 RSA 这样的非对称算法。

于 2013-03-27T05:03:18.230 回答
1

我对您正在使用的类进行了一些研究,您发布的内容似乎具有与您的显式参数匹配的大多数默认参数。但是,如果您使用的是较旧的实现,这并不能确保您的配置已全部设置为与当前文档相匹配。

此外,一位 Facebook 高级安全工程师最近在一次讲座中讨论了类似问题的提示;实现相同安全协议的不同库通常不兼容,甚至不同环境或语言中的相同库也经常无法协同工作。考虑到这一点,考虑到与您的设置类似的工作示例在线存在,可以尝试一些事情:

确保您使用的是最新版本的库。另请注意,不推荐使用某些 javax 函数和类,并建议现在使用 java.security (没有检查这是否适用于您的情况)。

强制密钥大小保持一致(1024 或 2048)。java实现可以做任何一个,我没有找到两个库的一致文档说明您的配置默认值将被使用(可能导致问题#2,尽管您可能会因无效密钥大小而得到不同的异常)。

确保您的私钥符合预期(java 和 php 之间的长度/读取相同)。

强制显式定义默认值(将 CryptRSA 哈希设置为 sha1、密钥长度、您可以显式设置的任何其他值)。

尝试使用 java 和 php 加密相同的消息,看看是否可以获得相同的输出。在确保您的密钥读取相同并且在两个应用程序中使用时不要抛出异常之后执行此操作。加密单个字符可以告诉您是否实际使用了相同的填充方案(从源代码中可以看出两者都使用 MGF1,但检查输出永远不会受到伤害)。

最后尝试举一个 php 到 java 加密的例子,据说它已经工作并且一次做一个改变,直到你回到你当前的加密方案。我看到一些快速搜索的示例,它们使用不同的参数和设置与 CryptRSA 和 java 安全性,表明它们正在协同工作。举一个工作示例,尝试交换散列函数,然后交换加密等。

于 2013-03-26T17:58:03.073 回答