Added encryption
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ public class Block implements Serializable {
|
||||
private final int id;
|
||||
private final int magicNumber;
|
||||
private final float generationSecs;
|
||||
private final List<String> chatMessages;
|
||||
private final List<Message> 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<String> chatMessages
|
||||
final List<Message> 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<Message> getMessages() {
|
||||
return chatMessages;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<Block> blockList = new LinkedList<>();
|
||||
private final List<String> chatMessages = new ArrayList<>();
|
||||
private final List<Message> 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<String> previousHashes = blockList.stream().map(Block::getPreviousBlockHash).collect(
|
||||
Collectors.toList());
|
||||
final List<String> hashes = blockList.stream().map(Block::getBlockHash).collect(Collectors.toList());
|
||||
//TODO implement message validation
|
||||
|
||||
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++) {
|
||||
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 = "";
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
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,11 +11,11 @@ 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<Block> blockList) {
|
||||
final var file = new File(FILE);
|
||||
try (final var fileOut = new FileOutputStream(file);
|
||||
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,9 +26,9 @@ public final class FileManagement {
|
||||
}
|
||||
|
||||
public static void loadBlockchain(final List<Block> blockList) {
|
||||
final var file = new File(FILE);
|
||||
if (file.exists()) {
|
||||
try (final var fileIn = new FileInputStream(file);
|
||||
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();
|
||||
@@ -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