Compare commits

...

13 Commits

9 changed files with 383 additions and 17 deletions

View 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;
}
}

View File

@ -15,7 +15,7 @@ public class App {
System.out.print("Enter server IP address (leave blank if hosting): "); System.out.print("Enter server IP address (leave blank if hosting): ");
String address = scanner.nextLine(); String address = scanner.nextLine();
Connection connection = new Connection(address); 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()); interactive.beginLoopAs(connection.isHost());
} catch (IOException exception) { } catch (IOException exception) {

View File

@ -6,10 +6,10 @@ import java.io.Reader;
import java.io.Writer; import java.io.Writer;
import java.util.Scanner; import java.util.Scanner;
public class Interactive { public class CommitmentInteractive {
ProtocolManager protocolManager; ProtocolManager protocolManager;
public Interactive(Reader in, Writer out) { public CommitmentInteractive(Reader in, Writer out) {
this.protocolManager = new ProtocolManager(in, out); this.protocolManager = new ProtocolManager(in, out);
} }

View File

@ -4,13 +4,11 @@ import org.apache.log4j.Level;
import org.apache.log4j.LogManager; import org.apache.log4j.LogManager;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import java.io.BufferedReader; import java.io.*;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ConnectException; import java.net.ConnectException;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.nio.charset.StandardCharsets;
public class Connection { public class Connection {
private static final Logger logger = LogManager.getLogger(App.class); private static final Logger logger = LogManager.getLogger(App.class);
@ -46,8 +44,9 @@ public class Connection {
} catch (ConnectException e) { } catch (ConnectException e) {
makeServer(); makeServer();
} }
out = new PrintWriter(clientSocket.getOutputStream(), true); out = new PrintWriter(new OutputStreamWriter(
in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); clientSocket.getOutputStream(), StandardCharsets.UTF_16), true);
in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream(), "UTF-16"));
} }
public PrintWriter getOut() { public PrintWriter getOut() {

View 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;
}
}

View 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();
}
}

View File

@ -52,12 +52,12 @@ public class ProtocolMessage {
private void composeRawData(int length, char[] message){ private void composeRawData(int length, char[] message){
this.raw = new char[4 + length]; this.raw = new char[4 + length];
byte[] integer = intToByte(length); char[] integer = intToCharArray(length);
this.raw[0] = (char)integer[0]; this.raw[0] = integer[0];
this.raw[1] = (char)integer[1]; this.raw[1] = integer[1];
this.raw[2] = (char)integer[2]; this.raw[2] = integer[2];
this.raw[3] = (char)integer[3]; this.raw[3] = integer[3];
System.arraycopy(message, 0, this.raw, 4, message.length); System.arraycopy(message, 0, this.raw, 4, message.length);
} }
@ -106,10 +106,10 @@ public class ProtocolMessage {
public void readFromStream(Reader reader) throws IOException { public void readFromStream(Reader reader) throws IOException {
int totalBytesRead = 0; int totalBytesRead = 0;
char[] buffer = new char[2048]; char[] lenBuffer = new char[4];
// Read first 4 bytes containing the length of the incoming message // Read first 4 bytes containing the length of the incoming message
while(totalBytesRead != 4){ while(totalBytesRead != 4){
int bytesRead = reader.read(buffer, totalBytesRead, 4 - totalBytesRead); int bytesRead = reader.read(lenBuffer, totalBytesRead, 4 - totalBytesRead);
if (bytesRead == -1) { if (bytesRead == -1) {
logger.log(Level.ERROR, "Invalid packet."); logger.log(Level.ERROR, "Invalid packet.");
return; return;
@ -117,7 +117,10 @@ public class ProtocolMessage {
totalBytesRead += bytesRead; 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){ while(totalBytesRead != 4 + messageLength){
int bytesRead = reader.read(buffer, totalBytesRead, messageLength - totalBytesRead + 4); int bytesRead = reader.read(buffer, totalBytesRead, messageLength - totalBytesRead + 4);
if (bytesRead == -1) { if (bytesRead == -1) {

View 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();
}
}
}

View 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());
}
}