• The Problem

    • I was using protobufs to transfer a ecdsa public key over a tcp connection.
    • The server was written in go, the client was an Android app (Google Play services)
    • The public key consists of two large numbers (X and Y).
    • The X and Y values generated are always positive(not sure why yet, need to see if this is as per design)
    • Client also performs a check using signum to see if the values are negative and throws and exception. The following message is from the adb logs
    • ehou: Cannot parse public key: Point encoding must use only non-negative integers
      
    • The numbers that were positive on the server somehow turned negative on the client.

    • This behaviour was intermittent. The transfer was successful sometimes.
  • Experiments and Observations

    • Tried checking if endianness is the problem. But the client continued to throw the same exception.
    • The values X and Y are 32 bytes long.
    • Since I cannot really go and debug what’s going on in the client, since its a system app on android and my phone is not rooted at the moment. I have tried to mimic the conditions using the following snippet.
    • package javaapp;
       
      import java.net.*;
      import java.util.Arrays;
      import java.io.*;
      import java.math.BigInteger;
       
      public class App {
          public static void main(String[] args) {
              String serverHost = "localhost"; // Change to the actual server host
              int serverPort = 3000; // Change to the actual server port
       
              try {
                  Socket socket = new Socket(serverHost, serverPort);
                  InputStream inputStream = socket.getInputStream();
       
                  byte[] buffer = new byte[1024];
                  int bytesRead = inputStream.read(buffer);
       
                  for(int i=0; i< bytesRead ; i++) {
                      System.out.println(buffer[i]);
                  }
                  Message.EcP256PublicKey message = Message.EcP256PublicKey.parseFrom(Arrays.copyOf(buffer, bytesRead));
                  
       
                  // Now you can work with the unmarshalled protobuf message
                  // System.out.println((new BigInteger("2").pow(Integer.MAX_VALUE)).subtract(new BigInteger("1")));
       
                  System.out.println("Received message:");
                  System.out.println("X as byte array: " + Arrays.toString(message.getX().toByteArray()));
                  System.out.println("Y as byte array: " + Arrays.toString(message.getY().toByteArray()));
                  System.out.println("X as big int: "+new BigInteger(message.getX().toByteArray()).toString(10));
                  System.out.println("Y as big int: "+new BigInteger(message.getY().toByteArray()).toString(10));
       
                  socket.close();
              } catch (Exception e) {
                  e.printStackTrace();
              }
       
          }
      }
       
    • The Golang server is as below
    • package main
       
      import (
      	"crypto/ecdsa"
      	"crypto/elliptic"
      	"crypto/rand"
      	"fmt"
      	"math/big"
      	"net"
       
      	"google.golang.org/protobuf/proto"
      )
       
      func main() {
      	port := 3000
      	fmt.Println("Starting the server on port:", port)
      	l, _ := net.Listen("tcp4", "127.0.0.1:3000")
      	for {
      		conn, _ := l.Accept()
      		// buf := make([]byte, 1024)
      		privateKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
      		publicKey := privateKey.PublicKey
      		
      		message, _ := proto.Marshal(&EcP256PublicKey{X: publicKey.X.Bytes(), Y: publicKey.Y.Bytes()})
      		fmt.Println("X: ", publicKey.X, "\nY: ", publicKey.Y, "\n")
      		fmt.Println("X: ", publicKey.X.Bytes(), "\nY: ", publicKey.Y.Bytes(), "\n")
      		
      		conn.Write(message)
      	}
      }
       
    • To my surprise I found that byte array implementation was different in golang and java.
    • In golang each byte in the byte array is unsigned.
    • In java each byte is a signed byte.
    • Though both are same in memory, we see the difference in interpretation of the byte value.
    • And the byte array in java considers the msb as a signed bit.
    • So whenever there is a 1 in the msb the biginteger in java interpreted is as a negative number, this explains the intermittent behaviour.
    • To fix this issue I had to prefix a 0 byte to the beginning of the byte array that was sent from golang. This would make java BigInteger to parse the number correctly since the msb is not 1 anymore.
    • The change in golang code is as below
    • // using FillBytes with size 33 will add a leading 0 byte to the byte array
      message, _ := proto.Marshal(&EcP256PublicKey{X: publicKey.X.FillBytes(make([]byte,33)), Y: publicKey.Y.FillBytes(make([]byte,33))})