Compare commits
13 Commits
master
...
DHExchange
Author | SHA1 | Date | |
---|---|---|---|
8e2772ef98 | |||
f18261ffde | |||
8eff857f92 | |||
3f07c623a2 | |||
47ed279410 | |||
c9d576ea6f | |||
4d8211c711 | |||
5091638837 | |||
71c2a48a1f | |||
f2cea164ae | |||
4def149519 | |||
131b296bb1 | |||
21010a04e5 |
48
src/main/java/Main/AES.java
Normal file
48
src/main/java/Main/AES.java
Normal file
@ -0,0 +1,48 @@
|
||||
package Main;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class AES {
|
||||
|
||||
public static byte[] generateIv(){
|
||||
SecureRandom randomSecureRandom = new SecureRandom();
|
||||
byte[] iv = new byte[16];
|
||||
randomSecureRandom.nextBytes(iv);
|
||||
return iv;
|
||||
}
|
||||
|
||||
public static byte[] encrypt(byte[] data, SecretKey secretKey, byte[] iv)
|
||||
{
|
||||
try
|
||||
{
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec);
|
||||
return cipher.doFinal(data);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
System.out.println("Error while encrypting: " + e.toString());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static byte[] decrypt(byte[] data, SecretKey secretKey, byte[] iv)
|
||||
{
|
||||
try
|
||||
{
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
|
||||
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
|
||||
return cipher.doFinal(data);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
System.out.println("Error while decrypting: " + e.toString());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@ public class App {
|
||||
System.out.print("Enter server IP address (leave blank if hosting): ");
|
||||
String address = scanner.nextLine();
|
||||
Connection connection = new Connection(address);
|
||||
Interactive interactive = new Interactive(connection.getIn(), connection.getOut());
|
||||
DHInteractive interactive = new DHInteractive(connection.getIn(), connection.getOut());
|
||||
interactive.beginLoopAs(connection.isHost());
|
||||
|
||||
} catch (IOException exception) {
|
||||
|
@ -6,10 +6,10 @@ import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.util.Scanner;
|
||||
|
||||
public class Interactive {
|
||||
public class CommitmentInteractive {
|
||||
ProtocolManager protocolManager;
|
||||
|
||||
public Interactive(Reader in, Writer out) {
|
||||
public CommitmentInteractive(Reader in, Writer out) {
|
||||
this.protocolManager = new ProtocolManager(in, out);
|
||||
}
|
||||
|
@ -4,13 +4,11 @@ import org.apache.log4j.Level;
|
||||
import org.apache.log4j.LogManager;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.*;
|
||||
import java.net.ConnectException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class Connection {
|
||||
private static final Logger logger = LogManager.getLogger(App.class);
|
||||
@ -46,8 +44,9 @@ public class Connection {
|
||||
} catch (ConnectException e) {
|
||||
makeServer();
|
||||
}
|
||||
out = new PrintWriter(clientSocket.getOutputStream(), true);
|
||||
in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
|
||||
out = new PrintWriter(new OutputStreamWriter(
|
||||
clientSocket.getOutputStream(), StandardCharsets.UTF_16), true);
|
||||
in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream(), "UTF-16"));
|
||||
}
|
||||
|
||||
public PrintWriter getOut() {
|
||||
|
108
src/main/java/Main/DHExchange.java
Normal file
108
src/main/java/Main/DHExchange.java
Normal file
@ -0,0 +1,108 @@
|
||||
package Main;
|
||||
|
||||
import org.apache.log4j.Level;
|
||||
import org.apache.log4j.LogManager;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import javax.crypto.KeyAgreement;
|
||||
import javax.crypto.interfaces.DHPublicKey;
|
||||
import javax.crypto.spec.DHParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.*;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
|
||||
public class DHExchange {
|
||||
private static final Logger logger = LogManager.getLogger(App.class);
|
||||
private KeyPair keyPair = null;
|
||||
private KeyAgreement agreement = null;
|
||||
private PublicKey thisPublicKey;
|
||||
private PublicKey otherPublicKey;
|
||||
private final int keySize;
|
||||
private SecretKeySpec secretKey;
|
||||
|
||||
|
||||
public DHExchange(int keySize, boolean isHost) {
|
||||
this.keySize = keySize;
|
||||
if (isHost)
|
||||
createKeyPar();
|
||||
}
|
||||
|
||||
void createKeyPar() {
|
||||
logger.log(Level.INFO, "Generating " + keySize + "-bit key pair...");
|
||||
|
||||
try {
|
||||
KeyPairGenerator kPairGen = KeyPairGenerator.getInstance("DH");
|
||||
kPairGen.initialize(keySize);
|
||||
keyPair = kPairGen.generateKeyPair();
|
||||
|
||||
|
||||
agreement = KeyAgreement.getInstance("DH");
|
||||
agreement.init(keyPair.getPrivate());
|
||||
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
thisPublicKey = keyPair.getPublic();
|
||||
}
|
||||
|
||||
void createKeyParFromPublicKey(byte[] publicKeyBytes) throws Exception {
|
||||
if (keyPair != null)
|
||||
throw new Exception("Key pair alredy exists! Perhaps is was generated before?");
|
||||
try {
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("DH");
|
||||
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(publicKeyBytes);
|
||||
|
||||
otherPublicKey = keyFactory.generatePublic(x509KeySpec);
|
||||
|
||||
DHParameterSpec dhParam = ((DHPublicKey) otherPublicKey).getParams();
|
||||
|
||||
logger.log(Level.INFO, "Generating " + keySize + "-bit key pair...");
|
||||
KeyPairGenerator kPairGen = KeyPairGenerator.getInstance("DH");
|
||||
kPairGen.initialize(dhParam);
|
||||
keyPair = kPairGen.generateKeyPair();
|
||||
|
||||
agreement = KeyAgreement.getInstance("DH");
|
||||
agreement.init(keyPair.getPrivate());
|
||||
|
||||
thisPublicKey = keyPair.getPublic();
|
||||
agreement.doPhase(otherPublicKey, true);
|
||||
|
||||
generateSecret();
|
||||
|
||||
} catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidAlgorithmParameterException | InvalidKeyException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
void onRecievePubKey(byte[] publicKeyBytes) {
|
||||
try {
|
||||
KeyFactory keyFac = KeyFactory.getInstance("DH");
|
||||
|
||||
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(publicKeyBytes);
|
||||
otherPublicKey = keyFac.generatePublic(x509KeySpec);
|
||||
agreement.doPhase(otherPublicKey, true);
|
||||
|
||||
generateSecret();
|
||||
} catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
byte[] getThisPublicKeyEncoded() {
|
||||
return thisPublicKey.getEncoded();
|
||||
}
|
||||
|
||||
byte[] getOtherPublicKeyEncoded() {
|
||||
return otherPublicKey.getEncoded();
|
||||
}
|
||||
|
||||
void generateSecret() {
|
||||
byte[] secret = agreement.generateSecret();
|
||||
secretKey = new SecretKeySpec(secret, 0, 16, "AES");
|
||||
}
|
||||
|
||||
public SecretKeySpec getSecretKey() {
|
||||
return secretKey;
|
||||
}
|
||||
}
|
||||
|
150
src/main/java/Main/DHInteractive.java
Normal file
150
src/main/java/Main/DHInteractive.java
Normal file
@ -0,0 +1,150 @@
|
||||
package Main;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Scanner;
|
||||
|
||||
public class DHInteractive {
|
||||
ProtocolManager protocolManager;
|
||||
|
||||
public DHInteractive(Reader in, Writer out) {
|
||||
this.protocolManager = new ProtocolManager(in, out);
|
||||
}
|
||||
|
||||
void beginSenderInteractive(){
|
||||
System.out.println("Starting DH key exchange...");
|
||||
DHExchange dhExchange = new DHExchange(2048, true);
|
||||
System.out.println("Your public key is: " + Utils.bytesToHex(dhExchange.getThisPublicKeyEncoded()));
|
||||
System.out.println("Sending this public key to the other side...");
|
||||
try {
|
||||
protocolManager.sendMessage(dhExchange.getThisPublicKeyEncoded());
|
||||
System.out.println("Key sent. Waiting for other sides public key...");
|
||||
|
||||
byte[] recvPKey = protocolManager.getMessageBytes();
|
||||
System.out.println("Got key: " + Utils.bytesToHex(recvPKey));
|
||||
|
||||
System.out.println("Final agreement phase...");
|
||||
dhExchange.onRecievePubKey(recvPKey);
|
||||
SecretKeySpec secret = dhExchange.getSecretKey();
|
||||
System.out.println("Secret is: " + Utils.bytesToHex(secret.getEncoded()));
|
||||
|
||||
System.out.println("Generating IV vector for AES ciphering...");
|
||||
|
||||
byte[] iv = AES.generateIv();
|
||||
|
||||
System.out.println("IV is: " + Utils.bytesToHex(iv));
|
||||
|
||||
// Sending IV as plaintext
|
||||
protocolManager.sendMessage(iv);
|
||||
|
||||
System.out.println("Specify a file to send");
|
||||
System.out.print("Path: ");
|
||||
|
||||
Scanner scanner = new Scanner(System.in);
|
||||
String fPath = scanner.nextLine();
|
||||
|
||||
File file = new File(fPath);
|
||||
long fSize = file.length();
|
||||
System.out.printf("File: %s, size: %.4fkB\n", file.getName(), (double)fSize / 1000);
|
||||
byte[] fBytes = Files.readAllBytes(file.toPath());
|
||||
|
||||
MessageDigest md5digest = MessageDigest.getInstance("MD5");
|
||||
md5digest.update(fBytes);
|
||||
byte[] checksum = md5digest.digest();
|
||||
System.out.println("MD5 checksum: " + Utils.bytesToHex(checksum));
|
||||
|
||||
System.out.println("Encrypting...");
|
||||
|
||||
byte[] encryptedFile = AES.encrypt(fBytes, secret, iv);
|
||||
byte[] encryptedFileName = AES.encrypt(file.getName().getBytes(), secret, iv);
|
||||
System.out.println("Sending encrypted file...");
|
||||
protocolManager.sendMessage(checksum);
|
||||
protocolManager.sendMessage(encryptedFileName);
|
||||
protocolManager.sendMessage(encryptedFile);
|
||||
|
||||
System.out.println("Done!");
|
||||
try {
|
||||
Thread.sleep(2000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
System.out.println("File sent.");
|
||||
} catch (IOException | NoSuchAlgorithmException exception) {
|
||||
exception.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void beginReceiverInteractive(){
|
||||
System.out.println("Starting DH key exchange...");
|
||||
DHExchange dhExchange = new DHExchange(2048, false);
|
||||
try {
|
||||
System.out.println("Waiting for other sides public key...");
|
||||
|
||||
byte[] recvPKey = protocolManager.getMessageBytes();
|
||||
System.out.println("Got key: " + Utils.bytesToHex(recvPKey));
|
||||
dhExchange.createKeyParFromPublicKey(recvPKey);
|
||||
|
||||
System.out.println("Your public key is: " + Utils.bytesToHex(dhExchange.getThisPublicKeyEncoded()));
|
||||
System.out.println("Sending the key to the other side...");
|
||||
protocolManager.sendMessage(dhExchange.getThisPublicKeyEncoded());
|
||||
|
||||
SecretKeySpec secret = dhExchange.getSecretKey();
|
||||
System.out.println("Secret is: " + Utils.bytesToHex(secret.getEncoded()));
|
||||
|
||||
System.out.println("Waiting for IV...");
|
||||
byte[] iv = protocolManager.getMessageBytes();
|
||||
System.out.println("IV is: " + Utils.bytesToHex(iv));
|
||||
|
||||
System.out.println("Waiting for encrypted file...");
|
||||
|
||||
byte[] checksum = protocolManager.getMessageBytes();
|
||||
byte[] encryptedFileName = protocolManager.getMessageBytes();
|
||||
byte[] encryptedFile = protocolManager.getMessageBytes();
|
||||
|
||||
System.out.println("Decrypting...");
|
||||
String decryptedFileName = new String(AES.decrypt(encryptedFileName, secret, iv));
|
||||
byte[] decryptedFile = AES.decrypt(encryptedFile, secret, iv);
|
||||
|
||||
System.out.println("Filename: " + decryptedFileName);
|
||||
File recievedFile = new File(decryptedFileName);
|
||||
BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(recievedFile));
|
||||
stream.write(decryptedFile);
|
||||
stream.flush();
|
||||
|
||||
System.out.println("File saved in " + recievedFile.getAbsolutePath());
|
||||
|
||||
System.out.println("Checking checksums...");
|
||||
System.out.println("Expected: " + Utils.bytesToHex(checksum));
|
||||
|
||||
byte[] fBytes = Files.readAllBytes(recievedFile.toPath());
|
||||
|
||||
MessageDigest md5digest = MessageDigest.getInstance("MD5");
|
||||
md5digest.update(fBytes);
|
||||
byte[] recvChecksum = md5digest.digest();
|
||||
System.out.println("MD5 checksum: " + Utils.bytesToHex(recvChecksum));
|
||||
|
||||
if(Arrays.equals(recvChecksum, checksum))
|
||||
System.out.println("Checksum match positive.");
|
||||
else
|
||||
System.out.println("Checksum match negative! File damaged.");
|
||||
|
||||
} catch (Exception exception) {
|
||||
exception.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void beginLoopAs(boolean isHost){
|
||||
// Host (the one who starts the app first) is Alice - ciphers the message.
|
||||
if(isHost)
|
||||
beginSenderInteractive();
|
||||
else
|
||||
beginReceiverInteractive();
|
||||
}
|
||||
}
|
@ -52,12 +52,12 @@ public class ProtocolMessage {
|
||||
|
||||
private void composeRawData(int length, char[] message){
|
||||
this.raw = new char[4 + length];
|
||||
byte[] integer = intToByte(length);
|
||||
char[] integer = intToCharArray(length);
|
||||
|
||||
this.raw[0] = (char)integer[0];
|
||||
this.raw[1] = (char)integer[1];
|
||||
this.raw[2] = (char)integer[2];
|
||||
this.raw[3] = (char)integer[3];
|
||||
this.raw[0] = integer[0];
|
||||
this.raw[1] = integer[1];
|
||||
this.raw[2] = integer[2];
|
||||
this.raw[3] = integer[3];
|
||||
|
||||
System.arraycopy(message, 0, this.raw, 4, message.length);
|
||||
}
|
||||
@ -106,10 +106,10 @@ public class ProtocolMessage {
|
||||
|
||||
public void readFromStream(Reader reader) throws IOException {
|
||||
int totalBytesRead = 0;
|
||||
char[] buffer = new char[2048];
|
||||
char[] lenBuffer = new char[4];
|
||||
// Read first 4 bytes containing the length of the incoming message
|
||||
while(totalBytesRead != 4){
|
||||
int bytesRead = reader.read(buffer, totalBytesRead, 4 - totalBytesRead);
|
||||
int bytesRead = reader.read(lenBuffer, totalBytesRead, 4 - totalBytesRead);
|
||||
if (bytesRead == -1) {
|
||||
logger.log(Level.ERROR, "Invalid packet.");
|
||||
return;
|
||||
@ -117,7 +117,10 @@ public class ProtocolMessage {
|
||||
totalBytesRead += bytesRead;
|
||||
}
|
||||
|
||||
int messageLength = charArrayToInt(buffer);
|
||||
int messageLength = charArrayToInt(lenBuffer);
|
||||
|
||||
char[] buffer = new char[4 + messageLength];
|
||||
System.arraycopy(lenBuffer, 0, buffer, 0, totalBytesRead);
|
||||
while(totalBytesRead != 4 + messageLength){
|
||||
int bytesRead = reader.read(buffer, totalBytesRead, messageLength - totalBytesRead + 4);
|
||||
if (bytesRead == -1) {
|
||||
|
36
src/test/java/Main/AESTest.java
Normal file
36
src/test/java/Main/AESTest.java
Normal file
@ -0,0 +1,36 @@
|
||||
package Main;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class AESTest {
|
||||
@Test
|
||||
void AESBothSidesTest(){
|
||||
Cipher cipher = null;
|
||||
try {
|
||||
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
|
||||
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
|
||||
keyGen.init(256); // for example
|
||||
SecretKey secretKey = keyGen.generateKey();
|
||||
byte[] iv = new byte[cipher.getBlockSize()];
|
||||
SecureRandom randomSecureRandom = new SecureRandom();
|
||||
randomSecureRandom.nextBytes(iv);
|
||||
byte[] data = Utils.stringToBytes("ASDASDASDASD12esadasd ");
|
||||
byte[] encoded = AES.encrypt(data, secretKey, iv);
|
||||
byte[] decoded = AES.decrypt(encoded, secretKey, iv);
|
||||
assertArrayEquals(data, decoded);
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
22
src/test/java/Main/DHExchangeTest.java
Normal file
22
src/test/java/Main/DHExchangeTest.java
Normal file
@ -0,0 +1,22 @@
|
||||
package Main;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class DHExchangeTest {
|
||||
|
||||
@Test
|
||||
void KeyMatchAfterExchangeTest(){
|
||||
DHExchange alice = new DHExchange(2048, true);
|
||||
DHExchange bob = new DHExchange(2048, false);
|
||||
try {
|
||||
bob.createKeyParFromPublicKey(alice.getThisPublicKeyEncoded());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
alice.onRecievePubKey(bob.getThisPublicKeyEncoded());
|
||||
|
||||
assertEquals(bob.getSecretKey(), alice.getSecretKey());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user