2013-03-26 153 views
9

我有一个PHP的Web服务,生成一个密钥对加密消息,并在Java中的一个应用程序回收的私钥和解密消息。加密PHP,解密Java

对于我使用http://phpseclib.sourceforge.net/和这个两个文件的PHP:

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. 这个概念是正确的?我的意思是,我正确使用它?
+0

pre-base64数据是否与解码后的base64数据相匹配? –

+0

是的,我现在已经测试过了,用base64解码后PHP和Java字符串的md5校验和是一样的。 –

+1

也许我的SecretKeySpec是错的?我试图改变算法的价值,但没有成功。 –

回答

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

?> 

我希望它能帮助任何人,谢谢。

+0

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

3

一些想法。

  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的限制。我会让您决定删除哪些标签(如果您甚至想这样做)。

+0

我使用git版本的phpseclib,并且更改了我的问题的标签。我已经改变了我的主要有点清楚: –

1

我对你正在使用的类进行了一些挖掘,看起来你已经发布的大部分默认参数都与你的显式参数匹配。但是,如果您使用较早的实现,这并不能确保您的配置具有与当前文档匹配的所有设置。

另外,来自Facebook安全工程师的一个小贴士在最近一次讲座中讨论了类似的问题;实现相同安全协议的不同库往往不兼容,甚至不同环境或语言中的相同库经常无法一起工作。考虑到这一点,在网上可以找到与您的设置类似的工作示例的几件事情:

确保您使用的是最新版本的库。另请注意,一些javax函数和类已被弃用,并建议现在使用java.security(并未检查这是否适用于您的案例)。

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

确保您的私钥匹配期望值(长度/在java和php之间读取相同)。

强制显式定义默认值(将CryptRSA散列设置为sha1,密钥长度,您可以明确设置的任何其他值)。

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

最后尝试以php为例来说明java加密,据说这个加密已经可以工作,并且一次只做一次更改,直到回到当前的加密方案。我看到几个快速搜索的例子,它们使用CryptRSA和java安全性的不同参数和设置,声明它们一起工作。以一个工作示例,并尝试交换哈希函数,然后加密等

2
SecretKeySpec privateKey = new SecretKeySpec(decoder.decodeBuffer(b64PrivateKey) , "AES"); 

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