Added encryption
This commit is contained in:
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 = "";
|
||||||
|
|||||||
@@ -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
40
src/model/Message.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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
75
src/util/Security.java
Normal 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user