Added encryption

This commit is contained in:
2021-08-02 00:34:44 +02:00
parent a1db603c7f
commit 0abd4c4fec
7 changed files with 209 additions and 27 deletions

View File

@@ -1,9 +1,11 @@
import java.io.File;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import model.BlockChain; import model.BlockChain;
import model.ChatClient; import model.ChatClient;
import model.Miner; import model.Miner;
import util.Security;
public final class Main { public final class Main {
@@ -12,6 +14,14 @@ public final class Main {
final var blockChain = new BlockChain(); final var blockChain = new BlockChain();
blockChain.load(); blockChain.load();
// Cryptographic keys management
final File publicKey = new File(Security.PUBLIC_KEY);
final File privateKey = new File(Security.PRIVATE_KEY);
if (!publicKey.exists() || !privateKey.exists()) {
Security.generateKeys();
}
final var chatExecutor = Executors.newScheduledThreadPool(nThreads); final var chatExecutor = Executors.newScheduledThreadPool(nThreads);
// Mocks 3 chat clients who send messages to the blockchain // Mocks 3 chat clients who send messages to the blockchain
@@ -21,8 +31,8 @@ public final class Main {
final var minerExecutor = Executors.newFixedThreadPool(nThreads); final var minerExecutor = Executors.newFixedThreadPool(nThreads);
// Creation of 5 miners // Creation of 10 miners
IntStream.range(0, 5) IntStream.range(0, 10)
.mapToObj(minerId -> new Miner(minerId, blockChain)) .mapToObj(minerId -> new Miner(minerId, blockChain))
.forEach(minerExecutor::submit); .forEach(minerExecutor::submit);

View File

@@ -14,7 +14,7 @@ public class Block implements Serializable {
private final int id; private final int id;
private final int magicNumber; private final int magicNumber;
private final float generationSecs; private final float generationSecs;
private final List<String> chatMessages; private final List<Message> chatMessages;
private final long timeStamp; private final long timeStamp;
public Block( public Block(
@@ -24,7 +24,7 @@ public class Block implements Serializable {
final int id, final int id,
final int magicNumber, final int magicNumber,
final float generationSecs, final float generationSecs,
final List<String> chatMessages final List<Message> chatMessages
) { ) {
this.previousBlockHash = previousBlockHash; this.previousBlockHash = previousBlockHash;
this.blockHash = blockHash; this.blockHash = blockHash;
@@ -53,14 +53,18 @@ public class Block implements Serializable {
+ "Hash of the previous block: " + "\n" + this.previousBlockHash + "\n" + "Hash of the previous block: " + "\n" + this.previousBlockHash + "\n"
+ "Hash of the block: \n" + this.blockHash + "\n" + "Hash of the block: \n" + this.blockHash + "\n"
+ "Block data: \n" + "Block data: \n"
+ getMessages() + messagesToString()
+ "Block was generating for " + this.generationSecs + " seconds"; + "Block was generating for " + this.generationSecs + " seconds";
} }
public String getMessages() { public String messagesToString() {
return chatMessages.stream() return chatMessages.stream()
.map(m -> m.concat("\n")) .map(m -> m.getText().concat("\n"))
.collect(Collectors.joining()); .collect(Collectors.joining());
} }
public List<Message> getMessages() {
return chatMessages;
}
} }

View File

@@ -2,6 +2,7 @@ package model;
import util.FileManagement; import util.FileManagement;
import util.HashFunction; import util.HashFunction;
import util.Security;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -12,7 +13,7 @@ public class BlockChain {
private final Random random = new Random(); private final Random random = new Random();
private final List<Block> blockList = new LinkedList<>(); private final List<Block> blockList = new LinkedList<>();
private final List<String> chatMessages = new ArrayList<>(); private final List<Message> chatMessages = new ArrayList<>();
private int hashZeroes; private int hashZeroes;
private int magicNumber; private int magicNumber;
private float generationSecs = 0; private float generationSecs = 0;
@@ -47,12 +48,20 @@ public class BlockChain {
} }
public void acceptMessage(String message) { public void acceptMessage(Message message) {
if (!blockList.isEmpty()) { if (!blockList.isEmpty() || message.getId() > getLastMessageId()) {
chatMessages.add(message); chatMessages.add(message);
} }
} }
public int getLastMessageId() {
return blockList.stream()
.map(Block::getMessages)
.flatMap(Collection::stream)
.mapToInt(Message::getId)
.max().orElse(0);
}
public void load() { public void load() {
FileManagement.loadBlockchain(blockList); FileManagement.loadBlockchain(blockList);
} }
@@ -73,9 +82,14 @@ public class BlockChain {
return true; return true;
} }
final List<String> previousHashes = blockList.stream().map(Block::getPreviousBlockHash).collect( //TODO implement message validation
Collectors.toList());
final List<String> hashes = blockList.stream().map(Block::getBlockHash).collect(Collectors.toList()); final List<String> previousHashes = blockList.stream()
.map(Block::getPreviousBlockHash)
.collect(Collectors.toList());
final List<String> hashes = blockList.stream()
.map(Block::getBlockHash)
.collect(Collectors.toList());
for (var index = 1; index < hashes.size(); index++) { for (var index = 1; index < hashes.size(); index++) {
if (!previousHashes.get(index).equals(hashes.get(index - 1))) { if (!previousHashes.get(index).equals(hashes.get(index - 1))) {
@@ -86,6 +100,11 @@ public class BlockChain {
return true; return true;
} }
private boolean messageIsValid(Message msg) {
//TODO correct signature issues
return Security.verifySignature(msg.getId() + msg.getText(), msg.getSignature(), msg.getPublicKey());
}
private String calculateBlockHash(final int minerId) { private String calculateBlockHash(final int minerId) {
final long start = System.currentTimeMillis(); final long start = System.currentTimeMillis();
var hash = ""; var hash = "";

View File

@@ -1,5 +1,12 @@
package model; package model;
import util.Security;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.util.Random; import java.util.Random;
public class ChatClient implements Runnable { public class ChatClient implements Runnable {
@@ -8,24 +15,40 @@ public class ChatClient implements Runnable {
private final BlockChain blockChain; private final BlockChain blockChain;
private static final Random random = new Random(); private static final Random random = new Random();
public ChatClient(int chatClientId, BlockChain blockChain) { public ChatClient(final int chatClientId, final BlockChain blockChain) {
this.clientId = chatClientId; this.clientId = chatClientId;
this.blockChain = blockChain; this.blockChain = blockChain;
} }
@Override @Override
public void run() { public void run() {
blockChain.acceptMessage(generateRandomAlphabeticString()); try {
blockChain.acceptMessage(createMessage());
} catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IOException | InvalidKeySpecException e) {
e.printStackTrace();
}
}
private Message createMessage()
throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, IOException, InvalidKeySpecException {
return new Message(
blockChain.getLastMessageId() + 1,
clientId,
generateRandomAlphabeticString(),
Security.getPrivate(),
Security.getPublic()
);
} }
private String generateRandomAlphabeticString() { private String generateRandomAlphabeticString() {
int leftLimit = 97; // letter 'a' final int leftLimit = 97; // letter 'a'
int rightLimit = 122; // letter 'z' final int rightLimit = 122; // letter 'z'
int targetStringLength = 10; final int targetStringLength = 10;
return "Client " + clientId + " says: " + random.ints(leftLimit, rightLimit + 1) return random.ints(leftLimit, rightLimit + 1)
.limit(targetStringLength) .limit(targetStringLength)
.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
.toString(); .toString();
} }
} }

40
src/model/Message.java Normal file
View File

@@ -0,0 +1,40 @@
package model;
import util.Security;
import java.io.Serializable;
import java.security.*;
public class Message implements Serializable {
private static final long serialVersionUID = 1L;
private final int id;
private final String text;
private final byte[] signature;
private final PublicKey publicKey;
public Message(final int id, final int clientId, final String text, final PrivateKey privateKey, final PublicKey publicKey)
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
this.id = id;
this.text = "Client " + clientId + " says: " + text;
this.signature = Security.sign(text, privateKey);
this.publicKey = publicKey;
}
public int getId() {
return id;
}
public String getText() {
return text;
}
public byte[] getSignature() {
return signature;
}
public PublicKey getPublicKey() {
return publicKey;
}
}

View File

@@ -11,12 +11,12 @@ import java.util.List;
public final class FileManagement { public final class FileManagement {
private static final String FILE = "blockChain.txt"; private static final String BLOCKCHAIN = "blockChain.txt";
public static void saveBlockchain(final List<Block> blockList) { public static void saveBlockchain(final List<Block> blockList) {
final var file = new File(FILE); final var blockChainFile = new File(BLOCKCHAIN);
try (final var fileOut = new FileOutputStream(file); try (final var fileOut = new FileOutputStream(blockChainFile);
final var objectOut = new ObjectOutputStream(fileOut)) { final var objectOut = new ObjectOutputStream(fileOut)) {
for (final var block : blockList) { for (final var block : blockList) {
objectOut.writeObject(block); objectOut.writeObject(block);
} }
@@ -26,10 +26,10 @@ public final class FileManagement {
} }
public static void loadBlockchain(final List<Block> blockList) { public static void loadBlockchain(final List<Block> blockList) {
final var file = new File(FILE); final var blockChainFile = new File(BLOCKCHAIN);
if (file.exists()) { if (blockChainFile.exists()) {
try (final var fileIn = new FileInputStream(file); try (final var fileIn = new FileInputStream(blockChainFile);
final var objectIn = new ObjectInputStream(fileIn)) { final var objectIn = new ObjectInputStream(fileIn)) {
while (fileIn.available() > 0) { while (fileIn.available() > 0) {
final var object = objectIn.readObject(); final var object = objectIn.readObject();
blockList.add((Block) object); blockList.add((Block) object);
@@ -40,4 +40,15 @@ public final class FileManagement {
} }
} }
public static void saveKey(final String path, final byte[] key) throws IOException {
final var file = new File(path);
file.getParentFile().mkdirs();
try (final var fileOut = new FileOutputStream(file)) {
fileOut.write(key);
} catch (final IOException e) {
e.printStackTrace();
}
}
} }

75
src/util/Security.java Normal file
View File

@@ -0,0 +1,75 @@
package util;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
public final class Security {
public static final String PRIVATE_KEY = "KeyPair/privateKey";
public static final String PUBLIC_KEY = "KeyPair/publicKey";
public static PublicKey getPublic()
throws NoSuchAlgorithmException, IOException, InvalidKeySpecException {
final byte[] keyBytes = Files.readAllBytes(new File(PUBLIC_KEY).toPath());
final X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
final KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePublic(spec);
}
public static PrivateKey getPrivate()
throws NoSuchAlgorithmException, IOException, InvalidKeySpecException {
final byte[] keyBytes = Files.readAllBytes(new File(PRIVATE_KEY).toPath());
final PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
final KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(spec);
}
public static byte[] sign(final String data, final PrivateKey privateKey)
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
final Signature rsa = Signature.getInstance("SHA1withRSA");
rsa.initSign(privateKey);
rsa.update(data.getBytes());
return rsa.sign();
}
public static boolean verifySignature(final String data, final byte[] signature, final PublicKey publicKey) {
try {
final Signature verifier = Signature.getInstance("SHA1withRSA");
verifier.initVerify(publicKey);
verifier.update(data.getBytes());
return verifier.verify(signature);
} catch (final Exception e) {
e.printStackTrace();
return false;
}
}
public static void generateKeys() {
KeyPairGenerator keyGen = null;
try {
keyGen = KeyPairGenerator.getInstance("RSA");
} catch (final NoSuchAlgorithmException e) {
e.printStackTrace();
}
try {
if (keyGen != null) {
keyGen.initialize(2048);
final KeyPair keyPair = keyGen.generateKeyPair();
final PrivateKey privateKey = keyPair.getPrivate();
final PublicKey publicKey = keyPair.getPublic();
FileManagement.saveKey(PUBLIC_KEY, publicKey.getEncoded());
FileManagement.saveKey(PRIVATE_KEY, privateKey.getEncoded());
}
} catch (final IOException e) {
e.printStackTrace();
}
}
}