Java:通过套接字发送后公钥不同

时间:2011-10-11 22:26:05

标签: java sockets cryptography

我正在尝试通过Java中的套接字连接发送公钥。虽然我非常清楚Java为这种活动提供SSL功能,但这是一项单一的任务;我无法使用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的回复。修改后的(工作)代码作为响应发布。

4 个答案:

答案 0 :(得分:3)

在实际代码中,我强烈建议不要以这种方式直接使用加密类。如果可能,请使用Java Secure Socket Extension

也就是说,我看到的错误是您将InputStreamReader访问权限与下面的原始InputStream混合在一起。 InputStreamReader可能会读取比readLine中要求的字节更多的字节 - 它几乎可以假设它拥有底层InputStream,因此可以在缓冲块中预读。

引用javadoc

  

每次调用一个InputStreamReader的read()方法都可以   导致从底层字节输入中读取一个或多个字节   流。要实现将字节有效转换为字符,   可以从底层流中读取更多字节   必须满足当前的读操作。

答案 1 :(得分:2)

可以通过套接字上的对象发送公钥 例如,我们可以像下面一样编写一个类:

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

}
客户端

只需定义Frame和socket,然后写入:

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

答案 2 :(得分: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);
        }   
    }   
}

答案 3 :(得分:0)

除了Jeffrey所解释的Writers和OutputStream的混合之外,以下内容也可能存在问题:

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

InputDream.read {4}的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;
}