2011-10-11 64 views
2

我試圖通過Java中的套接字連接發送公鑰。雖然我非常清楚Java爲這類活動提供了SSL功能,但這是一項單獨的任務;我無法使用Java實現。Java:通過套接字發送後的公鑰不同

服務器對其公鑰進行編碼並通過套接字連接將其發送給客戶端。當客戶端收到密鑰並對其進行解碼時,它看起來不同。不僅如此,客戶端接收到的數據與服務器發送的數據不同。我相信當我嘗試使用此密鑰對用戶名和密碼進行加密時,會給我帶來問題。

客戶:

public class TestClient { 

    /** 
    * @param args 
    */ 
    public static void main(String[] args) { 

     final int sPort = 4321; 

     Socket sock = null; 
     Key serverPubKey = null; 
     BufferedReader clientIn = null; 

     // Initialise server connection 
     try{ 
      sock = new Socket(InetAddress.getLocalHost(), sPort); 
      clientIn = new BufferedReader(new InputStreamReader(sock.getInputStream())); 
     } catch (UnknownHostException e) { 
      System.out.println("Unknown host."); 
      System.exit(1); 
     } catch (IOException e) { 
      System.out.println("No I/O"); 
      System.exit(1); 
     } 

     // Get server pub key 
     try{ 
      int len = Integer.parseInt(clientIn.readLine()); 
      byte[] servPubKeyBytes = new byte[len]; 
      sock.getInputStream().read(servPubKeyBytes,0,len); 
      System.out.println(servPubKeyBytes); 
      X509EncodedKeySpec ks = new X509EncodedKeySpec(servPubKeyBytes); 
      KeyFactory kf = KeyFactory.getInstance("RSA"); 
      serverPubKey = kf.generatePublic(ks); 
      System.out.println(serverPubKey.getEncoded()); 
     } catch (IOException e) { 
      System.out.println("Error obtaining server public key 1."); 
      System.exit(0); 
     } catch (NoSuchAlgorithmException e) { 
      System.out.println("Error obtaining server public key 2."); 
      System.exit(0); 
     } catch (InvalidKeySpecException e) { 
      System.out.println("Error obtaining server public key 3."); 
      System.exit(0); 
     } 

    } 

} 

服務器:

public class TestServer { 

    public static void main(String[] args) { 
     final int servPort = 4321; 
     final int RSAKeySize = 1024; 
     final String newline = "\n"; 

     Key pubKey = null; 
     ServerSocket cServer = null; 
     Socket cClient = null; 
     PrintWriter cOut = null; 

     // Initialise RSA 
     try{ 
      KeyPairGenerator RSAKeyGen = KeyPairGenerator.getInstance("RSA"); 
      RSAKeyGen.initialize(RSAKeySize); 
      KeyPair pair = RSAKeyGen.generateKeyPair(); 
      pubKey = pair.getPublic(); 
     } catch (GeneralSecurityException e) { 
      System.out.println(e.getLocalizedMessage() + newline); 
      System.out.println("Error initialising encryption. Exiting.\n"); 
      System.exit(0); 
     } 

     // Initialise socket connection 
     try{ 
      cServer = new ServerSocket(servPort); 
      cClient = cServer.accept(); 
      cOut = new PrintWriter(cClient.getOutputStream(), true); 
     } catch (IOException e) { 
      System.out.println("Error initialising I/O.\n"); 
      System.exit(0); 
     } 

     // Send public key 
     try { 
      cOut.println(pubKey.getEncoded().length); 
      System.out.println(pubKey.getEncoded()); 
      cClient.getOutputStream().write(pubKey.getEncoded()); 
      cClient.getOutputStream().flush(); 
     } catch (IOException e) { 
      System.out.println("I/O Error"); 
      System.exit(0); 
     } 

    } 

} 

這可能是由於通知我我的鑰匙一樣簡單不X509編碼

這個問題可以用下面的代碼進行復制,但是這似乎是從文件中恢復密鑰(也讀爲字節)的方式,所以我不明白爲什麼它不起作用?

非常感謝您的幫助/建議。

編輯:問題已解決,請參閱Jeffrey的回覆。修改後的(工作)代碼作爲響應發佈。

+1

爲什麼你的代碼有像「系統語句。通過out.println(pubKey.getEncoded())「? PrintStream.println對byte []沒有做任何特殊的處理,所以只會打印出類似於「[B @ 10b62c9」(意思是在內存位置0x010B62C9處的一個字節[])。我問,因爲你說「客戶收到的數據看起來與服務器傳輸的數據不同」,我希望上面的陳述不是你如何確定的。 – ruakh

+0

@ruakh「,我希望上面的陳述不是你如何確定的」不幸的是,這正是我所做的。我幾乎是一個java處女,實際上,我認爲它正在做一些不可思議的類型轉換爲char數組或字符串,「new String(pubKey.getEncoded())」會幫助我確定發送/接收的數據是否一樣?據我所知,字節數組可能無法正確編碼,但如果它們相等,我至少可以大致假設是這種情況,並嘗試再次加密我的用戶/密碼。 – mkingston

+1

我建議使用「System.out.println(javax.xml.bind.DatatypeConverter.printHexBinary(pubKey.getEncoded()))」,它使用JDK 1.6 DatatypeConverter類(hat-tip來Sii)轉換每個字節分成兩個十六進制數字。否則,您將打印字節到您的控制檯,而不是ASCII打印字符 - 難以比較,並且如果警報/響鈴/嘟嘟聲字符在您的耳朵上,您的耳朵會很難受! – ruakh

回答

3

在現實世界的代碼中,我強烈建議不要以這種方式直接使用加密類。如果可能,請使用Java Secure Socket Extension

也就是說,我看到的錯誤是,你正在混合InputStreamReader訪問與下面的原始InputStreamInputStreamReader可能會讀取比您要求的更多的字節數readLine —它幾乎被認爲是擁有基本的InputStream,所以可以在緩衝塊中讀取。

引述javadoc

的InputStreamReader的read()的一箇中的每一個調用可能 原因被從底層字節輸入 流中讀取一個或多個字節的方法。爲了能夠有效地將字節轉換爲字符,可以從底層流中讀取更多的字節,而不是滿足當前讀操作所需的 。

+0

「我強烈建議不要以這種方式直接使用加密類」我明白這一點,除非我認爲我的任務的目的更多是爲了展示理解而不是寫一個安全的程序。非常感謝你的迴應,我會有一個戲劇,讓你知道我的去向。 – mkingston

+0

對於它的價值,編寫HTTP處理器(服務器或客戶端)時出現同樣的問題。在讀取頭文件之前,我必須編寫一個特殊的無緩衝的InputStreamReader變體,而不消耗任何實體主體字節。 –

+1

如果您可以使用其他標準JDK類,則可以將密鑰作爲單行文本傳輸。 ['DatatypeConverter'](http://download.oracle.com/javase/6/docs/api/javax/xml/bind/DatatypeConverter.html)具有實用功能,可以在字節數組和字符串之間進行base64或「hex二進制「格式。這應該比自定義混合文本/二進制輸入流更容易實現。 – millimoose

0

除了傑弗裏解釋作家和OutputStreams的混合,下面也可能會出現問題:

sock.getInputStream().read(servPubKeyBytes,0,len); 

的InputStream.read writes的JavaDoc中:

讀取的一些數來自輸入流的字節並將它們存儲到緩衝器陣列b中。實際讀取的字節數作爲整數返回。此方法阻塞,直到輸入數據可用,檢測到文件結尾或引發異常。 如果b的長度爲零,則不讀取字節並返回0;否則,嘗試讀取至少一個字節。如果由於流位於文件末尾而沒有可用字節,則返回值-1;否則,至少讀取一個字節並存儲到b中。

讀取的第一個字節存儲在元素b [0]中,下一個讀入b [1],依此類推。讀取的字節數最多等於b的長度。令k爲實際讀取的字節數;這些字節將被存儲在元素b [0]到b [k-1]中,使元素b [k]到b [b.length-1]不受影響。

也就是說,read()可能讀取比請求更少的字節。如果你知道有更多的字節,你應該反覆調用read,直到所有數據已被讀取,這樣的:

for (int p = 0; p < len;) { 
    int read = in.read(servPubKeyBytes, p, len - p); 
    if (read == -1) { 
     throw new RuntimeException("Premature end of stream"); 
    } 
    p += read; 
} 
+0

字節數組是用outputstream.write(byte [])方法編寫的,我不完全確定這是否是作者?正如Jeffrey指出的那樣,我使用緩衝方法讀取數組長度,並提前讀取幾個額外的字節。在將長度轉換爲字節數組後,我用輸出流替換了長度爲writer的長度,類似地,另一端的長度緩衝讀取器現在用inputstream.read()替換。感謝您的答覆。 – mkingston

+0

我錯過了你在該方法中使用Writer和OutputStream。我相應地編輯了我的答案。 – meriton

1

首先,感謝大家對你的幫助,這是非常非常感謝!在這之前要等待12個小時,我開始擔心,從這裏順利航行,我想:)。

無論如何,修改後的代碼:

服務器:

public class TestServer { 

    public static void main(String[] args) { 
     final int servPort = 4321; 
     final int RSAKeySize = 1024; 
     final String newline = "\n"; 

     Key pubKey = null; 
     ServerSocket cServer = null; 
     Socket cClient = null; 

     // Initialise RSA 
     try{ 
      KeyPairGenerator RSAKeyGen = KeyPairGenerator.getInstance("RSA"); 
      RSAKeyGen.initialize(RSAKeySize); 
      KeyPair pair = RSAKeyGen.generateKeyPair(); 
      pubKey = pair.getPublic(); 
     } catch (GeneralSecurityException e) { 
      System.out.println(e.getLocalizedMessage() + newline); 
      System.out.println("Error initialising encryption. Exiting.\n"); 
      System.exit(0); 
     } 

     // Initialise socket connection 
     try{ 
      cServer = new ServerSocket(servPort); 
      cClient = cServer.accept(); 
     } catch (IOException e) { 
      System.out.println("Error initialising I/O.\n"); 
      System.exit(0); 
     } 

     // Send public key 
     try { 
     System.out.println(DatatypeConverter.printHexBinary(pubKey.getEncoded())); 
      ByteBuffer bb = ByteBuffer.allocate(4); 
      bb.putInt(pubKey.getEncoded().length); 
      cClient.getOutputStream().write(bb.array()); 
      cClient.getOutputStream().write(pubKey.getEncoded()); 
      cClient.getOutputStream().flush(); 
     } catch (IOException e) { 
      System.out.println("I/O Error"); 
      System.exit(0); 
     } 

    } 

} 

客戶:

public class TestClient { 

    /** 
    * @param args 
    */ 
    public static void main(String[] args) { 

     final int sPort = 4321; 

     Socket sock = null; 
     Key serverPubKey = null; 

     // Initialise server connection 
     try{ 
      sock = new Socket(InetAddress.getLocalHost(), sPort); 
     } catch (UnknownHostException e) { 
      System.out.println("Unknown host."); 
      System.exit(1); 
     } catch (IOException e) { 
      System.out.println("No I/O"); 
      System.exit(1); 
     } 

     // Get server pub key 
     try{ 
      byte[] lenb = new byte[4]; 
      sock.getInputStream().read(lenb,0,4); 
      ByteBuffer bb = ByteBuffer.wrap(lenb); 
      int len = bb.getInt(); 
      System.out.println(len); 
      byte[] servPubKeyBytes = new byte[len]; 
      sock.getInputStream().read(servPubKeyBytes); 
      System.out.println(DatatypeConverter.printHexBinary(servPubKeyBytes)); 
      X509EncodedKeySpec ks = new X509EncodedKeySpec(servPubKeyBytes); 
      KeyFactory kf = KeyFactory.getInstance("RSA"); 
      serverPubKey = kf.generatePublic(ks); 
      System.out.println(DatatypeConverter.printHexBinary(serverPubKey.getEncoded())); 
     } catch (IOException e) { 
      System.out.println("Error obtaining server public key 1."); 
      System.exit(0); 
     } catch (NoSuchAlgorithmException e) { 
      System.out.println("Error obtaining server public key 2."); 
      System.exit(0); 
     } catch (InvalidKeySpecException e) { 
      System.out.println("Error obtaining server public key 3."); 
      System.exit(0); 
     } 
    } 
} 
2

有可能通過物體在插座 發送公鑰例如,我們可以寫一類如下框架:

import java.io.Serializable; 
public class Frame implements Serializable { 
    byte[] data; 

} 

在客戶端只定義框架和插座,只寫了進去:

Frame frame = new Frame(); 
frame.data = thePublicKey.getEncoded(); 
toServer.writeObject(frame); 
在服務器端

解碼公鑰:

Frame frame = fromClient.readObject(); 
byte[] pubKey = frame.data;     
X509EncodedKeySpec ks = new X509EncodedKeySpec(pubKey); 
KeyFactory = KeyFactory.getInstance("RSA"); 
thepublicKey = kf.generatePublic(ks);