Java / Erlang:Diffie Hellman 密钥交换不工作
Java / Erlang: Diffie Hellman Key Exchange not Working
我想为我的应用程序实施我自己的加密。我对这个 post 进行了大修。直到今天我真的没有太多时间来解决这个问题。希望这比我原来的更有用。在这个问题上花了很多时间。希望能节省其他人的时间。
我在执行此操作时遇到了几个问题。直到最后我才意识到发生了什么。我得到了不同的共享秘密,后来又出现了一些例外情况。
这是我试过的:
- 使用两种语言提供的内置工具。无法弄清楚如何将原始 public 密钥转换为 Java 可以使用的形式。
- 从头开始,使用简单的公式计算每一方的 public 和私钥。 (从统计数据来看,这可能在大约 25% 的时间内奏效……对我来说幸运的是它没有。)
- 深入研究了 ITU 的 ASN.1 文档,并发送了以与 Java 的密钥类似的方式编码的 Erlang public 密钥。通过将 Java 键保存到文件并使用十六进制编辑器来确定这一点。我没有回去进行长时间的测试。它确实摆脱了
java.security.spec.InvalidKeySpecException: Inappropriate key specification
。认为统计数据在这里也对我不利。秘密仍然不匹配。
- 将 Java 中的所有数字发送到 Erlang 端以计算密钥,使用 Java 个数字共享秘密...相同的数字。有希望!!!
- 开始仔细检查他们正在通信的数据。这有点耗时,因为 Erlang 以无符号字节组织数据。 Eclipse IDE(也许某处有一个设置需要更改)在字节数组中使用带符号的字节,在
BigInteger
. 中使用带符号的整数数组
这是我开始看东西的地方。这一切都是在多次迭代中手动输入的,以确保我找到了正确的事件模式。在 Erlang 中,我看到我的 public 键以 <<215, 101, 208, 153,
开头。 Java 侧 BigInteger
的第一个元素是 681193318
。字节数据读入的缓冲区读取:[-41, 101, -48, -103
。 (与 Erlang 的相同)。但是花时间将二进制字符串的第一个四个元素转换为整数...
<<I:32/signed-integer>> = <<215,101,208,153>>.
相对于大整数的 681193318
产生 -681193319
我使用的代码非常简单:
二郎"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();
}
}
}
怎么了?
问题是阅读但不理解文档。我在参考页面上花费了大量时间,因为我不经常编写代码。我在 BigInteger
的文档中没有考虑太多这个特定细节:
All operations behave as if Bigintegers were represented in
two's-complement notation...
我的原始代码中有两个地方存在问题:
Ys = new BigInteger(buf2);
Sc = Ys.modPow(Xc, DiffieHellman.Group2.P);
第一行的问题在于,如果在第一个字节中设置了第 8 位,则整个 buf2
数组需要添加一个 0x00
字节作为前缀。第二行也有问题......直到执行以下行才变得明显:aeskey = hash.digest(Sc.toByteArray());
这里的问题是如果在结果的第一个字节中设置了第 8 位... 0x00
被添加到它的前面。这将转发给 digest()
函数,但需要省略。
我的代码更改为以下内容并且有效::)
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);
这两行原样保留:
Xc = new BigInteger(keybuffer);
Yc = new BigInteger("2").modPow(Xc, DiffieHellman.Group2.P);
这是因为私钥可以是 "any random number." 如果需要的话,在第二行中客户端的 public 密钥前面加上 0x00
字节。然而,Erlang 将整数解释为大端,任何前导 0x00
字节最终都是无关紧要的,因为它不会影响数值,因此不会影响执行 crypto:mod_pow()
.
时的结果。
非常欢迎关于如何改进代码的评论。
我想为我的应用程序实施我自己的加密。我对这个 post 进行了大修。直到今天我真的没有太多时间来解决这个问题。希望这比我原来的更有用。在这个问题上花了很多时间。希望能节省其他人的时间。
我在执行此操作时遇到了几个问题。直到最后我才意识到发生了什么。我得到了不同的共享秘密,后来又出现了一些例外情况。
这是我试过的:
- 使用两种语言提供的内置工具。无法弄清楚如何将原始 public 密钥转换为 Java 可以使用的形式。
- 从头开始,使用简单的公式计算每一方的 public 和私钥。 (从统计数据来看,这可能在大约 25% 的时间内奏效……对我来说幸运的是它没有。)
- 深入研究了 ITU 的 ASN.1 文档,并发送了以与 Java 的密钥类似的方式编码的 Erlang public 密钥。通过将 Java 键保存到文件并使用十六进制编辑器来确定这一点。我没有回去进行长时间的测试。它确实摆脱了
java.security.spec.InvalidKeySpecException: Inappropriate key specification
。认为统计数据在这里也对我不利。秘密仍然不匹配。 - 将 Java 中的所有数字发送到 Erlang 端以计算密钥,使用 Java 个数字共享秘密...相同的数字。有希望!!!
- 开始仔细检查他们正在通信的数据。这有点耗时,因为 Erlang 以无符号字节组织数据。 Eclipse IDE(也许某处有一个设置需要更改)在字节数组中使用带符号的字节,在
BigInteger
. 中使用带符号的整数数组
这是我开始看东西的地方。这一切都是在多次迭代中手动输入的,以确保我找到了正确的事件模式。在 Erlang 中,我看到我的 public 键以 <<215, 101, 208, 153,
开头。 Java 侧 BigInteger
的第一个元素是 681193318
。字节数据读入的缓冲区读取:[-41, 101, -48, -103
。 (与 Erlang 的相同)。但是花时间将二进制字符串的第一个四个元素转换为整数...
<<I:32/signed-integer>> = <<215,101,208,153>>.
相对于大整数的 681193318
-681193319
我使用的代码非常简单:
二郎"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();
}
}
}
怎么了?
问题是阅读但不理解文档。我在参考页面上花费了大量时间,因为我不经常编写代码。我在 BigInteger
的文档中没有考虑太多这个特定细节:
All operations behave as if Bigintegers were represented in two's-complement notation...
我的原始代码中有两个地方存在问题:
Ys = new BigInteger(buf2);
Sc = Ys.modPow(Xc, DiffieHellman.Group2.P);
第一行的问题在于,如果在第一个字节中设置了第 8 位,则整个 buf2
数组需要添加一个 0x00
字节作为前缀。第二行也有问题......直到执行以下行才变得明显:aeskey = hash.digest(Sc.toByteArray());
这里的问题是如果在结果的第一个字节中设置了第 8 位... 0x00
被添加到它的前面。这将转发给 digest()
函数,但需要省略。
我的代码更改为以下内容并且有效::)
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);
这两行原样保留:
Xc = new BigInteger(keybuffer);
Yc = new BigInteger("2").modPow(Xc, DiffieHellman.Group2.P);
这是因为私钥可以是 "any random number." 如果需要的话,在第二行中客户端的 public 密钥前面加上 0x00
字节。然而,Erlang 将整数解释为大端,任何前导 0x00
字节最终都是无关紧要的,因为它不会影响数值,因此不会影响执行 crypto:mod_pow()
.
非常欢迎关于如何改进代码的评论。