简体   繁体   中英

Send RSA public key over network from C to Java

I am trying to send a RSA public key over the.network, from a server written in c to a client written in Java. When the client receives the modulus, it should recreate the RSA key using the modulus and the public exponent F4, which the server is also using.

Here is how the server is generating and sending the key:

//Generate RSA key in C
RSA          *rsa;
int           bits = 1024;  
unsigned long exp  = RSA_F4;
BIGNUM       *bn;
BIGNUM       *MyModPubKey;

bn = BN_new();
BN_set_word(bn, exp);
rsa = RSA_new();
RSA_generate_key_ex(rsa, bits, bn, NULL);
MyModPubKey = rsa->n;

//Send RSA modulus in C
unsigned char public_key_Mod[128];
unsigned char *PubMod;
int PubModLen;


PubMod    = (unsigned char *)&public_key_Mod;
PubModLen = BN_bn2bin(MyModPubKey, PubMod);
assert(PubModLen == 128);
send(sd, public_key_Mod, 128, 0);

Here is how the key is recreated in Java. Exception handling omitted.

//Read the modulus
byte[] public_key_mod = new byte[128];
is.read(public_key_mod, 0, 128);


//Create BigInteger modulus and exponent
BigInteger RxPubKeyMod = new BigInteger(1, public_key_mod);             
BigInteger RxPubKeyExp = RSAKeyGenParameterSpec.F4;
PublicKey RxRsaPubKey = KeyFactory.getInstance("RSA").generatePublic(
                    new RSAPublicKeySpec(RxPubKeyMod, RxPubKeyExp));

However, the generated public RSA key in Java is not the same as the one in C. Verified by printing base64 encoded version of the keys. I also print both the modulus and exponent on both ends and verify they are the same. This how that is done:

//Java
System.out.println("RxPubKeyExp: " + RxPubKeyExp.toString(16));
System.out.println("RxPubKeyMod: " + RxPubKeyMod.toString(16));

//C
printf("PubKeyExp %s \n", BN_bn2hex(MyModPubKey->e));
printf("PubKeyMod %s \n", BN_bn2hex(MyModPubKey->n));

They are the same.

I can not figure out why this doesn't work. Am I missing something important here. Complicating factors is that I have a C version of the client, and it can correctly recreate the information it receives from the server. It may be something with BigInteger's being signed in Java, but I also tried inserting an extra leading 0x00 and expanding the bytearray to 129 bytes, with identical result (same generated key).

Please, I am completely stuck. Thanks

UPDATE:

Here is a sample output from the C server:

PubKeyExp 010001 
PubKeyMod A8CB09C2B84762A8C822F18C9CA48036E0D9988C9D8625BF5F2DF16FDEEC92D018863E129C0AE89EB0C344FD32DFF419548C39BB41A867293BC4BD84A1ECAEB0D723EEA97BD2651AF8BD56B2C97D38A39111A0C48894BC034371C2EDB96F1E2E9CDDA9B16DFBE80580ADFDA3853445120F7429AD60300E31254864041210B0BB 

PublicKey -----BEGIN RSA PUBLIC KEY-----
MIGJAoGBAKjLCcK4R2KoyCLxjJykgDbg2ZiMnYYlv18t8W/e7JLQGIY+EpwK6J6w
w0T9Mt/0GVSMObtBqGcpO8S9hKHsrrDXI+6pe9JlGvi9VrLJfTijkRGgxIiUvAND
ccLtuW8eLpzdqbFt++gFgK39o4U0RRIPdCmtYDAOMSVIZAQSELC7AgMBAAE=
-----END RSA PUBLIC KEY-----

It is printed in this way:

printf("PubKeyExp %s \n", BN_bn2hex(MyModPubKey->e));
printf("PubKeyMod %s \n", BN_bn2hex(MyModPubKey->n));
FILE *fp;
char *pubKeyString;
pubKeyString = calloc(1, 512);
if(!(fp = fmemopen(pubKeyString, 512, "w"))) {
   exit(1);
}
PEM_write_RSAPublicKey(fp, pubKey);
fflush(fp);
fclose(fp);
printf("PublicKey %s \n",pubKeyString);

where pubKey=rsa as created above

Here is a sample output from the Java server:

RxPubKeyExp: 10001
RxPubKeyMod: a8cb09c2b84762a8c822f18c9ca48036e0d9988c9d8625bf5f2df16fdeec92d018863e129c0ae89eb0c344fd32dff419548c39bb41a867293bc4bd84a1ecaeb0d723eea97bd2651af8bd56b2c97d38a39111a0c48894bc034371c2edb96f1e2e9cdda9b16dfbe80580adfda3853445120f7429ad60300e31254864041210b0bb
RxRsaPubKey: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCoywnCuEdiqMgi8YycpIA24NmYjJ2GJb9fLfFv3uyS0BiGPhKcCuiesMNE/TLf9BlUjDm7QahnKTvEvYSh7K6w1yPuqXvSZRr4vVayyX04o5ERoMSIlLwDQ3HC7blvHi6c3amxbfvoBYCt/aOFNEUSD3QprWAwDjElSGQEEhCwuwIDAQAB

It is printed in this way:

System.out.println("RxPubKeyExp: " + RxPubKeyExp.toString(16));
System.out.println("RxPubKeyMod: " + RxPubKeyMod.toString(16));
String RxRsaPubKeyChar = Base64.encodeToString(RxRsaPubKey.getEncoded(), Base64.DEFAULT);
System.out.println("RxRsaPubKey: " + RxRsaPubKeyChar);

The output is not the same because they use different encodings. Java's outputs the RSA public in SubjectPublicKeyInfo (or SPKI) format, whereas your C code is getting the RSA public key in PKCS#1 format.

I haven't used openssl in many years but looking through the documentation for openssl 3.0 it seems that the functions that use the EVP_PKEY type are the way to go and the i2d_PUBKEY function will return an SPKI-encoded public key. This can then be read in by Java and converted to an RSA public key by something like

// The C server writes the length L of the encoded public key data as a
// big-endian 4 byte int followed by the L bytes of spki

DataInputStream dis = new DataInputStream(sock.getInputStream());
int spkiLength = dis.readInt();
byte [] spkiBytes = new byte[spkiLength];
dis.readFully(spkiBytes);
KeyFactory rsaKf = KeyFactory.getInstance("RSA");
PublicKey pubKey = rsaKf.generatePublic(new X509EncodedKeySpec(spkiBytes));

For sending socket over network, Java is using Big-Endian, whereas C is using little-Endian. After Java client received data, try to convert it from little-endian to big-endian. You can use ByteBuffer at Client side.

So try this:

ByteBuffer bbf = ByteBuffer.allocate(Income_Data.length);
bbf.put(Income_Data);
bbf.order(ByteOrder.BIG_ENDIAN);
byte[] goodData = bbf.array();

you might also try:

ByteBuffer bbf = ByteBuffer.warp(Income_Data);

Please refer to: C++ Client to a Java Server(TCP/IP)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM