I wanted to implement my own encryption for my application. I did a major overhaul of this post. I really didn't have much time until today to address it. Hopefully this being the more useful than my original. Spent a ridiculous amount of time on this issue. Hopefully can save others that time.
I encountered several issues while doing this. I did not realize what was happening until the very end. I was getting different shared secrets and later some exceptions.
This is what I tried:
java.security.spec.InvalidKeySpecException: Inappropriate key specification
. Think statistics didn't work in my favor here either. The secrets still did not match.BigInteger
.This is where I began to see things. This all was manually entered over many iterations to make sure I found the correct pattern of events. In Erlang I see my public key beginning with <<215, 101, 208, 153,
. The first element of the BigInteger
on the Java-side is 681193318
. The buffer the byte data was read into reads: [-41, 101, -48, -103
. (Same as Erlang's). However taking the time convert the 1st four elements of the binary string to an integer...
<<I:32/signed-integer>> = <<215,101,208,153>>.
That yields -681193319
versus the big integer's 681193318
The code I was using was some what simple:
Erlang "Server":
-module(echo).
-export([start/0]).
start() ->
crypto:start(),
spawn(fun () -> {ok, Sock} = gen_tcp:listen(12321, [binary, {packet, raw}]),
echo_loop(Sock)
end).
echo_loop(Sock) ->
{ok, Conn} = gen_tcp:accept(Sock),
io:format("Got connection: ~p~n", [Conn]),
Handler = spawn(fun () -> handle(Conn) end),
gen_tcp:controlling_process(Conn, Handler),
echo_loop(Sock).
p() ->
16#ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece65381ffffffffffffffff.
g() ->
2.
handle(Conn) ->
receive
{tcp, Conn, Yc} ->
Xs = crypto:strong_rand_bytes(64),
Ys = crypto:mod_pow(g(),Xs,p()),
S = crypto:mod_pow(Yc, Xs, p()),
AESKey = crypto:hash(sha256, S),
gen_tcp:send(Conn, Ys),%KeyCert),
handle(Conn);
{tcp_closed, Conn} ->
io:format("Connection closed: ~p~n", [Conn])
end.
Java "Client":
public class MyProgram {
private static Socket s;
private static OutputStream out;
private static InputStream in;
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
MessageDigest hash;
byte buffer[] = new byte[1024];
byte buf2[];
int len = 0;
byte[] aeskey;
try {
hash = MessageDigest.getInstance("SHA-256");
byte keybuffer[] = new byte[64];
SecureRandom srnd = SecureRandom.getInstance("SHA1PRNG");
BigInteger Xc, Yc, Sc, Ys;
srnd.nextBytes(keybuffer);
Xc = new BigInteger(keybuffer);
Yc = new BigInteger("2").modPow(Xc, DiffieHellman.Group2.P);
s = new Socket("localhost",12321);
out = s.getOutputStream();
in = s.getInputStream();
out.write(Yc.toByteArray());
out.flush();
len = in.read(buffer);
buf2 = new byte[len];
System.arraycopy(buffer, 0, buf2, 0, len);
Ys = new BigInteger(buf2);
Sc = Ys.modPow(Xc, DiffieHellman.Group2.P);
aeskey = hash.digest(Sc.toByteArray());
out.close();
in.close();
s.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
What was wrong?
The issue is reading but not understanding the documentation. I spend a lot of time in reference pages because I don't code very often. I did not think much of this particular detail in the documentation for BigInteger
:
All operations behave as if Bigintegers were represented in two's-complement notation...
There are two spots in my original code where this presents an issue:
Ys = new BigInteger(buf2);
Sc = Ys.modPow(Xc, DiffieHellman.Group2.P);
The issue with the first line is that if bit 8 is set in the first byte the entire buf2
array needs to be prepended with a 0x00
byte. There is an issue with the second line as well... it does not become evident until the following line is executed: aeskey = hash.digest(Sc.toByteArray());
The issue here is if bit 8 is set in the first byte of the result... 0x00
is prepended to it. This is forwarded to the digest()
function but needs to be omitted.
My code changed to what is below and works: :)
len = in.read(buffer);
buf2 = new byte[len+1];
System.arraycopy(buffer, 0, buf2, 1, len);
buf2[0] = 0;
if(buf2[1] < 0)
Ys = new BigInteger(buf2);
else
Ys = new BigInteger(Arrays.copyOfRange(buf2, 1, buf2.length));
Sc = Ys.modPow(Xc, DiffieHellman.Group2.P);
buffer = Sc.toByteArray();
if(buffer[0] == 0)
aeskey = hash.digest(Arrays.copyOfRange(buffer, 1, buffer.length));
else
aeskey = hash.digest(buffer);
These two lines were left as is:
Xc = new BigInteger(keybuffer);
Yc = new BigInteger("2").modPow(Xc, DiffieHellman.Group2.P);
This is because the private key can be "any random number." The 0x00
byte is prepended if necessary to the public key of the client in the second line. However Erlang interprets integers as big-endian and any leading 0x00
bytes end up being irrelevant as it does not affect the numerical value and hence the result when conducting a crypto:mod_pow()
.
Comments on how to improve code very welcome.