From 0abd4c4fec63c35101ab0d1ba093431b9bd55f38 Mon Sep 17 00:00:00 2001 From: droideparanoico Date: Mon, 2 Aug 2021 00:34:44 +0200 Subject: [PATCH] Added encryption --- src/Main.java | 14 ++++++- src/model/Block.java | 14 ++++--- src/model/BlockChain.java | 31 ++++++++++++--- src/model/ChatClient.java | 35 ++++++++++++++--- src/model/Message.java | 40 +++++++++++++++++++ src/util/FileManagement.java | 27 +++++++++---- src/util/Security.java | 75 ++++++++++++++++++++++++++++++++++++ 7 files changed, 209 insertions(+), 27 deletions(-) create mode 100644 src/model/Message.java create mode 100644 src/util/Security.java diff --git a/src/Main.java b/src/Main.java index 906ef06..29ff8a6 100644 --- a/src/Main.java +++ b/src/Main.java @@ -1,9 +1,11 @@ +import java.io.File; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.stream.IntStream; import model.BlockChain; import model.ChatClient; import model.Miner; +import util.Security; public final class Main { @@ -12,6 +14,14 @@ public final class Main { final var blockChain = new BlockChain(); 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); // Mocks 3 chat clients who send messages to the blockchain @@ -21,8 +31,8 @@ public final class Main { final var minerExecutor = Executors.newFixedThreadPool(nThreads); - // Creation of 5 miners - IntStream.range(0, 5) + // Creation of 10 miners + IntStream.range(0, 10) .mapToObj(minerId -> new Miner(minerId, blockChain)) .forEach(minerExecutor::submit); diff --git a/src/model/Block.java b/src/model/Block.java index 12d39f7..1baf3e0 100644 --- a/src/model/Block.java +++ b/src/model/Block.java @@ -14,7 +14,7 @@ public class Block implements Serializable { private final int id; private final int magicNumber; private final float generationSecs; - private final List chatMessages; + private final List chatMessages; private final long timeStamp; public Block( @@ -24,7 +24,7 @@ public class Block implements Serializable { final int id, final int magicNumber, final float generationSecs, - final List chatMessages + final List chatMessages ) { this.previousBlockHash = previousBlockHash; this.blockHash = blockHash; @@ -53,14 +53,18 @@ public class Block implements Serializable { + "Hash of the previous block: " + "\n" + this.previousBlockHash + "\n" + "Hash of the block: \n" + this.blockHash + "\n" + "Block data: \n" - + getMessages() + + messagesToString() + "Block was generating for " + this.generationSecs + " seconds"; } - public String getMessages() { + public String messagesToString() { return chatMessages.stream() - .map(m -> m.concat("\n")) + .map(m -> m.getText().concat("\n")) .collect(Collectors.joining()); } + public List getMessages() { + return chatMessages; + } + } diff --git a/src/model/BlockChain.java b/src/model/BlockChain.java index a100662..0da3ea9 100644 --- a/src/model/BlockChain.java +++ b/src/model/BlockChain.java @@ -2,6 +2,7 @@ package model; import util.FileManagement; import util.HashFunction; +import util.Security; import java.util.*; import java.util.stream.Collectors; @@ -12,7 +13,7 @@ public class BlockChain { private final Random random = new Random(); private final List blockList = new LinkedList<>(); - private final List chatMessages = new ArrayList<>(); + private final List chatMessages = new ArrayList<>(); private int hashZeroes; private int magicNumber; private float generationSecs = 0; @@ -47,12 +48,20 @@ public class BlockChain { } - public void acceptMessage(String message) { - if (!blockList.isEmpty()) { + public void acceptMessage(Message message) { + if (!blockList.isEmpty() || message.getId() > getLastMessageId()) { chatMessages.add(message); } } + public int getLastMessageId() { + return blockList.stream() + .map(Block::getMessages) + .flatMap(Collection::stream) + .mapToInt(Message::getId) + .max().orElse(0); + } + public void load() { FileManagement.loadBlockchain(blockList); } @@ -73,9 +82,14 @@ public class BlockChain { return true; } - final List previousHashes = blockList.stream().map(Block::getPreviousBlockHash).collect( - Collectors.toList()); - final List hashes = blockList.stream().map(Block::getBlockHash).collect(Collectors.toList()); + //TODO implement message validation + + final List previousHashes = blockList.stream() + .map(Block::getPreviousBlockHash) + .collect(Collectors.toList()); + final List hashes = blockList.stream() + .map(Block::getBlockHash) + .collect(Collectors.toList()); for (var index = 1; index < hashes.size(); index++) { if (!previousHashes.get(index).equals(hashes.get(index - 1))) { @@ -86,6 +100,11 @@ public class BlockChain { 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) { final long start = System.currentTimeMillis(); var hash = ""; diff --git a/src/model/ChatClient.java b/src/model/ChatClient.java index 94ebf5a..2043dee 100644 --- a/src/model/ChatClient.java +++ b/src/model/ChatClient.java @@ -1,5 +1,12 @@ 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; public class ChatClient implements Runnable { @@ -8,24 +15,40 @@ public class ChatClient implements Runnable { private final BlockChain blockChain; 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.blockChain = blockChain; } @Override 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() { - int leftLimit = 97; // letter 'a' - int rightLimit = 122; // letter 'z' - int targetStringLength = 10; + final int leftLimit = 97; // letter 'a' + final int rightLimit = 122; // letter 'z' + final int targetStringLength = 10; - return "Client " + clientId + " says: " + random.ints(leftLimit, rightLimit + 1) + return random.ints(leftLimit, rightLimit + 1) .limit(targetStringLength) .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) .toString(); } + } \ No newline at end of file diff --git a/src/model/Message.java b/src/model/Message.java new file mode 100644 index 0000000..2854ce4 --- /dev/null +++ b/src/model/Message.java @@ -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; + } + +} diff --git a/src/util/FileManagement.java b/src/util/FileManagement.java index ba1c531..b3cb60e 100644 --- a/src/util/FileManagement.java +++ b/src/util/FileManagement.java @@ -11,12 +11,12 @@ import java.util.List; public final class FileManagement { - private static final String FILE = "blockChain.txt"; + private static final String BLOCKCHAIN = "blockChain.txt"; public static void saveBlockchain(final List blockList) { - final var file = new File(FILE); - try (final var fileOut = new FileOutputStream(file); - final var objectOut = new ObjectOutputStream(fileOut)) { + final var blockChainFile = new File(BLOCKCHAIN); + try (final var fileOut = new FileOutputStream(blockChainFile); + final var objectOut = new ObjectOutputStream(fileOut)) { for (final var block : blockList) { objectOut.writeObject(block); } @@ -26,10 +26,10 @@ public final class FileManagement { } public static void loadBlockchain(final List blockList) { - final var file = new File(FILE); - if (file.exists()) { - try (final var fileIn = new FileInputStream(file); - final var objectIn = new ObjectInputStream(fileIn)) { + final var blockChainFile = new File(BLOCKCHAIN); + if (blockChainFile.exists()) { + try (final var fileIn = new FileInputStream(blockChainFile); + final var objectIn = new ObjectInputStream(fileIn)) { while (fileIn.available() > 0) { final var object = objectIn.readObject(); 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(); + } + } + } diff --git a/src/util/Security.java b/src/util/Security.java new file mode 100644 index 0000000..5a0ce79 --- /dev/null +++ b/src/util/Security.java @@ -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(); + } + + } + +}