Moving "Security" to a separate port, so there can be a Bouncycastle and a Spongycastle implementation. (BC doesn't work on Android, SC can't be used on Oracle's JVM)

This commit is contained in:
Christian Basler 2015-08-05 19:52:18 +02:00
parent 6542bd1451
commit b8546e28af
60 changed files with 1168 additions and 641 deletions

View File

@ -20,7 +20,7 @@ import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.networking.NetworkNode;
import ch.dissem.bitmessage.networking.DefaultNetworkHandler;
import ch.dissem.bitmessage.repository.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -45,7 +45,7 @@ public class Application {
.inventory(new JdbcInventory(jdbcConfig))
.nodeRegistry(new MemoryNodeRegistry())
.messageRepo(new JdbcMessageRepository(jdbcConfig))
.networkHandler(new NetworkNode())
.networkHandler(new DefaultNetworkHandler())
.port(48444)
.build();

View File

@ -17,7 +17,7 @@
package ch.dissem.bitmessage.demo;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.networking.NetworkNode;
import ch.dissem.bitmessage.networking.DefaultNetworkHandler;
import ch.dissem.bitmessage.repository.*;
import ch.dissem.bitmessage.wif.WifExporter;
import ch.dissem.bitmessage.wif.WifImporter;
@ -49,7 +49,7 @@ public class Main {
.inventory(new JdbcInventory(jdbcConfig))
.nodeRegistry(new MemoryNodeRegistry())
.messageRepo(new JdbcMessageRepository(jdbcConfig))
.networkHandler(new NetworkNode())
.networkHandler(new DefaultNetworkHandler())
.port(48444)
.build();

View File

@ -12,6 +12,7 @@ uploadArchives {
dependencies {
compile 'org.slf4j:slf4j-api:1.7.12'
compile 'org.bouncycastle:bcprov-jdk15on:1.52'
testCompile group: 'junit', name: 'junit', version: '4.11'
}
testCompile 'junit:junit:4.11'
testCompile 'org.mockito:mockito-core:1.10.19'
testCompile project(':security-bc')
}

View File

@ -27,12 +27,13 @@ import ch.dissem.bitmessage.exception.DecryptionFailedException;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.ports.*;
import ch.dissem.bitmessage.utils.Property;
import ch.dissem.bitmessage.utils.Security;
import ch.dissem.bitmessage.utils.UnixTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static ch.dissem.bitmessage.entity.Plaintext.Status.*;
import static ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST;
@ -57,12 +58,17 @@ public class BitmessageContext {
public static final int CURRENT_VERSION = 3;
private final static Logger LOG = LoggerFactory.getLogger(BitmessageContext.class);
private final ExecutorService pool;
private final InternalContext ctx;
private Listener listener;
private BitmessageContext(Builder builder) {
ctx = new InternalContext(builder);
// As this thread is used for parts that do POW, which itself uses parallel threads, only
// one should be executed at any time.
pool = Executors.newFixedThreadPool(1);
}
public AddressRepository addresses() {
@ -74,7 +80,7 @@ public class BitmessageContext {
}
public BitmessageAddress createIdentity(boolean shorter, Feature... features) {
BitmessageAddress identity = new BitmessageAddress(new PrivateKey(
final BitmessageAddress identity = new BitmessageAddress(new PrivateKey(
shorter,
ctx.getStreams()[0],
ctx.getNetworkNonceTrialsPerByte(),
@ -82,8 +88,12 @@ public class BitmessageContext {
features
));
ctx.getAddressRepo().save(identity);
// TODO: this should happen in a separate thread
ctx.sendPubkey(identity, identity.getStream());
pool.submit(new Runnable() {
@Override
public void run() {
ctx.sendPubkey(identity, identity.getStream());
}
});
return identity;
}
@ -91,63 +101,71 @@ public class BitmessageContext {
// TODO
}
public void broadcast(BitmessageAddress from, String subject, String message) {
// TODO: all this should happen in a separate thread
Plaintext msg = new Plaintext.Builder(BROADCAST)
.from(from)
.message(subject, message)
.build();
public void broadcast(final BitmessageAddress from, final String subject, final String message) {
pool.submit(new Runnable() {
@Override
public void run() {
Plaintext msg = new Plaintext.Builder(BROADCAST)
.from(from)
.message(subject, message)
.build();
LOG.info("Sending message.");
msg.setStatus(DOING_PROOF_OF_WORK);
ctx.getMessageRepository().save(msg);
ctx.send(
from,
from,
Factory.getBroadcast(from, msg),
+2 * DAY,
0,
0
);
msg.setStatus(SENT);
msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.BROADCAST, Label.Type.SENT));
ctx.getMessageRepository().save(msg);
LOG.info("Sending message.");
msg.setStatus(DOING_PROOF_OF_WORK);
ctx.getMessageRepository().save(msg);
ctx.send(
from,
from,
Factory.getBroadcast(from, msg),
+2 * DAY,
0,
0
);
msg.setStatus(SENT);
msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.BROADCAST, Label.Type.SENT));
ctx.getMessageRepository().save(msg);
}
});
}
public void send(BitmessageAddress from, BitmessageAddress to, String subject, String message) {
public void send(final BitmessageAddress from, final BitmessageAddress to, final String subject, final String message) {
if (from.getPrivateKey() == null) {
throw new IllegalArgumentException("'From' must be an identity, i.e. have a private key.");
}
// TODO: all this should happen in a separate thread
Plaintext msg = new Plaintext.Builder(MSG)
.from(from)
.to(to)
.message(subject, message)
.build();
if (to.getPubkey() == null) {
tryToFindMatchingPubkey(to);
}
if (to.getPubkey() == null) {
LOG.info("Public key is missing from recipient. Requesting.");
requestPubkey(from, to);
msg.setStatus(PUBKEY_REQUESTED);
ctx.getMessageRepository().save(msg);
} else {
LOG.info("Sending message.");
msg.setStatus(DOING_PROOF_OF_WORK);
ctx.getMessageRepository().save(msg);
ctx.send(
from,
to,
new Msg(msg),
+2 * DAY,
ctx.getNonceTrialsPerByte(to),
ctx.getExtraBytes(to)
);
msg.setStatus(SENT);
msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.SENT));
ctx.getMessageRepository().save(msg);
}
pool.submit(new Runnable() {
@Override
public void run() {
Plaintext msg = new Plaintext.Builder(MSG)
.from(from)
.to(to)
.message(subject, message)
.build();
if (to.getPubkey() == null) {
tryToFindMatchingPubkey(to);
}
if (to.getPubkey() == null) {
LOG.info("Public key is missing from recipient. Requesting.");
requestPubkey(from, to);
msg.setStatus(PUBKEY_REQUESTED);
ctx.getMessageRepository().save(msg);
} else {
LOG.info("Sending message.");
msg.setStatus(DOING_PROOF_OF_WORK);
ctx.getMessageRepository().save(msg);
ctx.send(
from,
to,
new Msg(msg),
+2 * DAY,
ctx.getNonceTrialsPerByte(to),
ctx.getExtraBytes(to)
);
msg.setStatus(SENT);
msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.SENT));
ctx.getMessageRepository().save(msg);
}
}
});
}
private void requestPubkey(BitmessageAddress requestingIdentity, BitmessageAddress address) {
@ -169,8 +187,7 @@ public class BitmessageContext {
.expiresTime(expires)
.payload(payload)
.build();
Security.doProofOfWork(object, ctx.getProofOfWorkEngine(),
ctx.getNetworkNonceTrialsPerByte(), ctx.getNetworkExtraBytes());
ctx.getSecurity().doProofOfWork(object, ctx.getNetworkNonceTrialsPerByte(), ctx.getNetworkExtraBytes());
ctx.getInventory().storeObject(object);
ctx.getNetworkHandler().offer(object.getInventoryVector());
}
@ -184,6 +201,10 @@ public class BitmessageContext {
ctx.getNetworkHandler().stop();
}
public boolean isRunning() {
return ctx.getNetworkHandler().isRunning();
}
public void addContact(BitmessageAddress contact) {
ctx.getAddressRepo().save(contact);
tryToFindMatchingPubkey(contact);
@ -258,6 +279,7 @@ public class BitmessageContext {
AddressRepository addressRepo;
MessageRepository messageRepo;
ProofOfWorkEngine proofOfWorkEngine;
Security security;
public Builder() {
}
@ -292,6 +314,11 @@ public class BitmessageContext {
return this;
}
public Builder security(Security security) {
this.security = security;
return this;
}
public Builder proofOfWorkEngine(ProofOfWorkEngine proofOfWorkEngine) {
this.proofOfWorkEngine = proofOfWorkEngine;
return this;

View File

@ -21,7 +21,7 @@ import ch.dissem.bitmessage.entity.payload.Broadcast;
import ch.dissem.bitmessage.entity.payload.GetPubkey;
import ch.dissem.bitmessage.entity.payload.ObjectPayload;
import ch.dissem.bitmessage.ports.*;
import ch.dissem.bitmessage.utils.Security;
import ch.dissem.bitmessage.utils.Singleton;
import ch.dissem.bitmessage.utils.UnixTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -42,6 +42,7 @@ import static ch.dissem.bitmessage.utils.UnixTime.DAY;
public class InternalContext {
private final static Logger LOG = LoggerFactory.getLogger(InternalContext.class);
private final Security security;
private final Inventory inventory;
private final NodeRegistry nodeRegistry;
private final NetworkHandler networkHandler;
@ -56,16 +57,29 @@ public class InternalContext {
private long clientNonce;
public InternalContext(BitmessageContext.Builder builder) {
this.security = builder.security;
this.inventory = builder.inventory;
this.nodeRegistry = builder.nodeRegistry;
this.networkHandler = builder.networkHandler;
this.addressRepository = builder.addressRepo;
this.messageRepository = builder.messageRepo;
this.proofOfWorkEngine = builder.proofOfWorkEngine;
this.clientNonce = Security.randomNonce();
this.clientNonce = security.randomNonce();
Singleton.initialize(security);
port = builder.port;
streams.add(1L); // FIXME
// TODO: streams of new identities and subscriptions should also be added. This works only after a restart.
for (BitmessageAddress address : addressRepository.getIdentities()) {
streams.add(address.getStream());
}
for (BitmessageAddress address : addressRepository.getSubscriptions()) {
streams.add(address.getStream());
}
if (streams.isEmpty()) {
streams.add(1L);
}
init(inventory, nodeRegistry, networkHandler, addressRepository, messageRepository, proofOfWorkEngine);
}
@ -78,6 +92,10 @@ public class InternalContext {
}
}
public Security getSecurity() {
return security;
}
public Inventory getInventory() {
return inventory;
}
@ -111,14 +129,6 @@ public class InternalContext {
return result;
}
public void addStream(long stream) {
streams.add(stream);
}
public void removeStream(long stream) {
streams.remove(stream);
}
public int getPort() {
return port;
}
@ -152,14 +162,14 @@ public class InternalContext {
.payload(payload)
.build();
if (object.isSigned()) {
object.sign(from.getPrivateKey());
object.sign( from.getPrivateKey());
}
if (payload instanceof Broadcast) {
((Broadcast) payload).encrypt();
} else if (payload instanceof Encrypted) {
object.encrypt(to.getPubkey());
}
Security.doProofOfWork(object, proofOfWorkEngine, nonceTrialsPerByte, extraBytes);
security.doProofOfWork(object, nonceTrialsPerByte, extraBytes);
if (payload instanceof PlaintextHolder) {
Plaintext plaintext = ((PlaintextHolder) payload).getPlaintext();
plaintext.setInventoryVector(object.getInventoryVector());
@ -182,13 +192,13 @@ public class InternalContext {
.payload(identity.getPubkey())
.build();
response.sign(identity.getPrivateKey());
response.encrypt(Security.createPublicKey(identity.getPublicDecryptionKey()).getEncoded(false));
Security.doProofOfWork(response, proofOfWorkEngine, networkNonceTrialsPerByte, networkExtraBytes);
response.encrypt(security.createPublicKey(identity.getPublicDecryptionKey()));
security.doProofOfWork(response, networkNonceTrialsPerByte, networkExtraBytes);
if (response.isSigned()) {
response.sign(identity.getPrivateKey());
}
if (response instanceof Encrypted) {
response.encrypt(Security.createPublicKey(identity.getPublicDecryptionKey()).getEncoded(false));
response.encrypt(security.createPublicKey(identity.getPublicDecryptionKey()));
}
inventory.storeObject(response);
networkHandler.offer(response.getInventoryVector());
@ -206,7 +216,7 @@ public class InternalContext {
.expiresTime(expires)
.payload(new GetPubkey(contact))
.build();
Security.doProofOfWork(response, proofOfWorkEngine, networkNonceTrialsPerByte, networkExtraBytes);
security.doProofOfWork(response, networkNonceTrialsPerByte, networkExtraBytes);
inventory.storeObject(response);
networkHandler.offer(response.getInventoryVector());
}
@ -215,10 +225,6 @@ public class InternalContext {
return clientNonce;
}
public void setClientNonce(long clientNonce) {
this.clientNonce = clientNonce;
}
public interface ContextHolder {
void setContext(InternalContext context);
}

View File

@ -19,22 +19,27 @@ package ch.dissem.bitmessage.entity;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.entity.payload.V4Pubkey;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.utils.*;
import ch.dissem.bitmessage.utils.AccessCounter;
import ch.dissem.bitmessage.utils.Base58;
import ch.dissem.bitmessage.utils.Bytes;
import ch.dissem.bitmessage.utils.Encode;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Objects;
import static ch.dissem.bitmessage.utils.Decode.bytes;
import static ch.dissem.bitmessage.utils.Decode.varInt;
import static ch.dissem.bitmessage.utils.Singleton.security;
/**
* A Bitmessage address. Can be a user's private address, an address string without public keys or a recipient's address
* holding private keys.
*/
public class BitmessageAddress {
public class BitmessageAddress implements Serializable {
private final long version;
private final long stream;
private final byte[] ripe;
@ -62,19 +67,19 @@ public class BitmessageAddress {
Encode.varInt(version, os);
Encode.varInt(stream, os);
if (version < 4) {
byte[] checksum = Security.sha512(os.toByteArray(), ripe);
byte[] checksum = security().sha512(os.toByteArray(), ripe);
this.tag = null;
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32);
} else {
// for tag and decryption key, the checksum has to be created with 0x00 padding
byte[] checksum = Security.doubleSha512(os.toByteArray(), ripe);
byte[] checksum = security().doubleSha512(os.toByteArray(), ripe);
this.tag = Arrays.copyOfRange(checksum, 32, 64);
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32);
}
// but for the address and its checksum they need to be stripped
int offset = Bytes.numberOfLeadingZeros(ripe);
os.write(ripe, offset, ripe.length - offset);
byte[] checksum = Security.doubleSha512(os.toByteArray());
byte[] checksum = security().doubleSha512(os.toByteArray());
os.write(checksum, 0, 4);
this.address = "BM-" + Base58.encode(os.toByteArray());
} catch (IOException e) {
@ -103,18 +108,18 @@ public class BitmessageAddress {
this.ripe = Bytes.expand(bytes(in, bytes.length - counter.length() - 4), 20);
// test checksum
byte[] checksum = Security.doubleSha512(bytes, bytes.length - 4);
byte[] checksum = security().doubleSha512(bytes, bytes.length - 4);
byte[] expectedChecksum = bytes(in, 4);
for (int i = 0; i < 4; i++) {
if (expectedChecksum[i] != checksum[i])
throw new IllegalArgumentException("Checksum of address failed");
}
if (version < 4) {
checksum = Security.sha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe);
checksum = security().sha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe);
this.tag = null;
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32);
} else {
checksum = Security.doubleSha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe);
checksum = security().doubleSha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe);
this.tag = Arrays.copyOfRange(checksum, 32, 64);
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32);
}
@ -129,7 +134,7 @@ public class BitmessageAddress {
Encode.varInt(version, out);
Encode.varInt(stream, out);
out.write(ripe);
return Arrays.copyOfRange(Security.doubleSha512(out.toByteArray()), 32, 64);
return Arrays.copyOfRange(security().doubleSha512(out.toByteArray()), 32, 64);
} catch (IOException e) {
throw new RuntimeException(e);
}

View File

@ -17,6 +17,7 @@
package ch.dissem.bitmessage.entity;
import ch.dissem.bitmessage.exception.DecryptionFailedException;
import ch.dissem.bitmessage.ports.Security;
import java.io.IOException;

View File

@ -26,7 +26,7 @@ import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import static ch.dissem.bitmessage.utils.Security.sha512;
import static ch.dissem.bitmessage.utils.Singleton.security;
/**
* A network message is exchanged between two nodes.
@ -48,7 +48,7 @@ public class NetworkMessage implements Streamable {
* First 4 bytes of sha512(payload)
*/
private byte[] getChecksum(byte[] bytes) throws NoSuchProviderException, NoSuchAlgorithmException {
byte[] d = sha512(bytes);
byte[] d = security().sha512(bytes);
return new byte[]{d[0], d[1], d[2], d[3]};
}

View File

@ -22,14 +22,16 @@ import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.exception.DecryptionFailedException;
import ch.dissem.bitmessage.ports.Security;
import ch.dissem.bitmessage.utils.Bytes;
import ch.dissem.bitmessage.utils.Encode;
import ch.dissem.bitmessage.utils.Security;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import static ch.dissem.bitmessage.utils.Singleton.security;
/**
* The 'object' command sends an object that is shared throughout the network.
*/
@ -89,7 +91,9 @@ public class ObjectMessage implements MessagePayload {
}
public InventoryVector getInventoryVector() {
return new InventoryVector(Bytes.truncate(Security.doubleSha512(nonce, getPayloadBytesWithoutNonce()), 32));
return new InventoryVector(
Bytes.truncate(security().doubleSha512(nonce, getPayloadBytesWithoutNonce()), 32)
);
}
private boolean isEncrypted() {
@ -113,7 +117,7 @@ public class ObjectMessage implements MessagePayload {
public void sign(PrivateKey key) {
if (payload.isSigned()) {
payload.setSignature(Security.getSignature(getBytesToSign(), key));
payload.setSignature(security().getSignature(getBytesToSign(), key));
}
}
@ -147,7 +151,7 @@ public class ObjectMessage implements MessagePayload {
public boolean isSignatureValid(Pubkey pubkey) throws IOException {
if (isEncrypted()) throw new IllegalStateException("Payload must be decrypted first");
return Security.isSignatureValid(getBytesToSign(), payload.getSignature(), pubkey);
return security().isSignatureValid(getBytesToSign(), payload.getSignature(), pubkey);
}
@Override

View File

@ -19,6 +19,7 @@ package ch.dissem.bitmessage.entity;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.ports.Security;
import ch.dissem.bitmessage.utils.Decode;
import ch.dissem.bitmessage.utils.Encode;
@ -28,7 +29,7 @@ import java.util.*;
/**
* The unencrypted message to be sent by 'msg' or 'broadcast'.
*/
public class Plaintext implements Streamable {
public class Plaintext implements Streamable, Serializable {
private final Type type;
private final BitmessageAddress from;
private final long encoding;

View File

@ -16,6 +16,8 @@
package ch.dissem.bitmessage.entity;
import ch.dissem.bitmessage.ports.Security;
import java.io.IOException;
import java.io.OutputStream;

View File

@ -21,11 +21,12 @@ import ch.dissem.bitmessage.entity.Encrypted;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.PlaintextHolder;
import ch.dissem.bitmessage.exception.DecryptionFailedException;
import ch.dissem.bitmessage.utils.Security;
import ch.dissem.bitmessage.ports.Security;
import java.io.IOException;
import static ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST;
import static ch.dissem.bitmessage.utils.Singleton.security;
/**
* Users who are subscribed to the sending address will see the message appear in their inbox.
@ -78,7 +79,7 @@ public abstract class Broadcast extends ObjectPayload implements Encrypted, Plai
}
public void encrypt() throws IOException {
encrypt(Security.createPublicKey(plaintext.getFrom().getPublicDecryptionKey()).getEncoded(false));
encrypt(security().createPublicKey(plaintext.getFrom().getPublicDecryptionKey()));
}
@Override

View File

@ -17,28 +17,16 @@
package ch.dissem.bitmessage.entity.payload;
import ch.dissem.bitmessage.entity.Streamable;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.exception.DecryptionFailedException;
import ch.dissem.bitmessage.utils.*;
import org.bouncycastle.crypto.BufferedBlockCipher;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.paddings.PKCS7Padding;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.math.ec.ECFieldElement;
import org.bouncycastle.math.ec.ECPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.math.BigInteger;
import java.util.Arrays;
import static ch.dissem.bitmessage.entity.valueobject.PrivateKey.PRIVATE_KEY_SIZE;
import static ch.dissem.bitmessage.utils.Singleton.security;
public class CryptoBox implements Streamable {
@ -46,35 +34,31 @@ public class CryptoBox implements Streamable {
private final byte[] initializationVector;
private final int curveType;
private final ECPoint R;
private final byte[] R;
private final byte[] mac;
private byte[] encrypted;
public CryptoBox(Streamable data, byte[] encryptionKey) throws IOException {
this(data, Security.keyToPoint(encryptionKey));
}
public CryptoBox(Streamable data, ECPoint K) throws IOException {
public CryptoBox(Streamable data, byte[] K) throws IOException {
curveType = 0x02CA;
// 1. The destination public key is called K.
// 2. Generate 16 random bytes using a secure random number generator. Call them IV.
initializationVector = Security.randomBytes(16);
initializationVector = security().randomBytes(16);
// 3. Generate a new random EC key pair with private key called r and public key called R.
byte[] r = Security.randomBytes(PRIVATE_KEY_SIZE);
R = Security.createPublicKey(r);
byte[] r = security().randomBytes(PRIVATE_KEY_SIZE);
R = security().createPublicKey(r);
// 4. Do an EC point multiply with public key K and private key r. This gives you public key P.
ECPoint P = K.multiply(Security.keyToBigInt(r)).normalize();
byte[] X = P.getXCoord().getEncoded();
byte[] P = security().multiply(K, r);
byte[] X = Points.getX(P);
// 5. Use the X component of public key P and calculate the SHA512 hash H.
byte[] H = Security.sha512(X);
byte[] H = security().sha512(X);
// 6. The first 32 bytes of H are called key_e and the last 32 bytes are called key_m.
byte[] key_e = Arrays.copyOfRange(H, 0, 32);
byte[] key_m = Arrays.copyOfRange(H, 32, 64);
// 7. Pad the input text to a multiple of 16 bytes, in accordance to PKCS7.
// 8. Encrypt the data with AES-256-CBC, using IV as initialization vector, key_e as encryption key and the padded input text as payload. Call the output cipher text.
encrypted = crypt(true, Encode.bytes(data), key_e);
encrypted = security().crypt(true, Encode.bytes(data), key_e, initializationVector);
// 9. Calculate a 32 byte MAC with HMACSHA256, using key_m as salt and IV + R + cipher text as data. Call the output MAC.
mac = calculateMac(key_m);
@ -84,7 +68,7 @@ public class CryptoBox implements Streamable {
private CryptoBox(Builder builder) {
initializationVector = builder.initializationVector;
curveType = builder.curveType;
R = Security.createPoint(builder.xComponent, builder.yComponent);
R = security().createPoint(builder.xComponent, builder.yComponent);
encrypted = builder.encrypted;
mac = builder.mac;
}
@ -102,18 +86,17 @@ public class CryptoBox implements Streamable {
}
/**
* @param privateKey a private key, typically should be 32 bytes long
* @param k a private key, typically should be 32 bytes long
* @return an InputStream yielding the decrypted data
* @throws DecryptionFailedException if the payload can't be decrypted using this private key
* @see <a href='https://bitmessage.org/wiki/Encryption#Decryption'>https://bitmessage.org/wiki/Encryption#Decryption</a>
*/
public InputStream decrypt(byte[] privateKey) throws DecryptionFailedException {
public InputStream decrypt(byte[] k) throws DecryptionFailedException {
// 1. The private key used to decrypt is called k.
BigInteger k = Security.keyToBigInt(privateKey);
// 2. Do an EC point multiply with private key k and public key R. This gives you public key P.
ECPoint P = R.multiply(k).normalize();
byte[] P = security().multiply(R, k);
// 3. Use the X component of public key P and calculate the SHA512 hash H.
byte[] H = Security.sha512(P.getXCoord().getEncoded());
byte[] H = security().sha512(Arrays.copyOfRange(P, 1, 33));
// 4. The first 32 bytes of H are called key_e and the last 32 bytes are called key_m.
byte[] key_e = Arrays.copyOfRange(H, 0, 32);
byte[] key_m = Arrays.copyOfRange(H, 32, 64);
@ -126,49 +109,28 @@ public class CryptoBox implements Streamable {
// 7. Decrypt the cipher text with AES-256-CBC, using IV as initialization vector, key_e as decryption key
// and the cipher text as payload. The output is the padded input text.
return new ByteArrayInputStream(crypt(false, encrypted, key_e));
return new ByteArrayInputStream(security().crypt(false, encrypted, key_e, initializationVector));
}
private byte[] calculateMac(byte[] key_m) {
try {
ByteArrayOutputStream macData = new ByteArrayOutputStream();
writeWithoutMAC(macData);
return Security.mac(key_m, macData.toByteArray());
return security().mac(key_m, macData.toByteArray());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private byte[] crypt(boolean encrypt, byte[] data, byte[] key_e) {
BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()), new PKCS7Padding());
CipherParameters params = new ParametersWithIV(new KeyParameter(key_e), initializationVector);
cipher.init(encrypt, params);
byte[] buffer = new byte[cipher.getOutputSize(data.length)];
int length = cipher.processBytes(data, 0, data.length, buffer, 0);
try {
length += cipher.doFinal(buffer, length);
} catch (InvalidCipherTextException e) {
throw new IllegalArgumentException(e);
}
if (length < buffer.length) {
return Arrays.copyOfRange(buffer, 0, length);
}
return buffer;
}
private void writeWithoutMAC(OutputStream out) throws IOException {
out.write(initializationVector);
Encode.int16(curveType, out);
writeCoordinateComponent(out, R.getXCoord());
writeCoordinateComponent(out, R.getYCoord());
writeCoordinateComponent(out, Points.getX(R));
writeCoordinateComponent(out, Points.getY(R));
out.write(encrypted);
}
private void writeCoordinateComponent(OutputStream out, ECFieldElement coord) throws IOException {
byte[] x = coord.getEncoded();
private void writeCoordinateComponent(OutputStream out, byte[] x) throws IOException {
int offset = Bytes.numberOfLeadingZeros(x);
int length = x.length - offset;
Encode.int16(length, out);

View File

@ -20,8 +20,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import static ch.dissem.bitmessage.utils.Security.ripemd160;
import static ch.dissem.bitmessage.utils.Security.sha512;
import static ch.dissem.bitmessage.utils.Singleton.security;
/**
* Public keys for signing and encryption, the answer to a 'getpubkey' request.
@ -34,7 +33,7 @@ public abstract class Pubkey extends ObjectPayload {
}
public static byte[] getRipe(byte[] publicSigningKey, byte[] publicEncryptionKey) {
return ripemd160(sha512(publicSigningKey, publicEncryptionKey));
return security().ripemd160(security().sha512(publicSigningKey, publicEncryptionKey));
}
public abstract byte[] getSigningKey();
@ -44,7 +43,7 @@ public abstract class Pubkey extends ObjectPayload {
public abstract int getBehaviorBitfield();
public byte[] getRipe() {
return ripemd160(sha512(getSigningKey(), getEncryptionKey()));
return security().ripemd160(security().sha512(getSigningKey(), getEncryptionKey()));
}
public long getNonceTrialsPerByte() {

View File

@ -18,6 +18,7 @@ package ch.dissem.bitmessage.entity.payload;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.ports.Security;
import java.io.IOException;
import java.io.InputStream;

View File

@ -21,9 +21,10 @@ import ch.dissem.bitmessage.utils.Strings;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.Arrays;
public class InventoryVector implements Streamable {
public class InventoryVector implements Streamable, Serializable {
/**
* Hash of the object
*/

View File

@ -18,18 +18,18 @@ package ch.dissem.bitmessage.entity.valueobject;
import ch.dissem.bitmessage.entity.Streamable;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.entity.payload.V3Pubkey;
import ch.dissem.bitmessage.entity.payload.V4Pubkey;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.utils.Bytes;
import ch.dissem.bitmessage.utils.Decode;
import ch.dissem.bitmessage.utils.Encode;
import ch.dissem.bitmessage.utils.Security;
import java.io.*;
import static ch.dissem.bitmessage.utils.Singleton.security;
/**
* Created by chris on 18.04.15.
* Represents a private key. Additional information (stream, version, features, ...) is stored in the accompanying
* {@link Pubkey} object.
*/
public class PrivateKey implements Streamable {
public static final int PRIVATE_KEY_SIZE = 32;
@ -45,15 +45,15 @@ public class PrivateKey implements Streamable {
byte[] pubEK;
byte[] ripe;
do {
privSK = Security.randomBytes(PRIVATE_KEY_SIZE);
privEK = Security.randomBytes(PRIVATE_KEY_SIZE);
pubSK = Security.createPublicKey(privSK).getEncoded(false);
pubEK = Security.createPublicKey(privEK).getEncoded(false);
privSK = security().randomBytes(PRIVATE_KEY_SIZE);
privEK = security().randomBytes(PRIVATE_KEY_SIZE);
pubSK = security().createPublicKey(privSK);
pubEK = security().createPublicKey(privEK);
ripe = Pubkey.getRipe(pubSK, pubEK);
} while (ripe[0] != 0 || (shorter && ripe[1] != 0));
this.privateSigningKey = privSK;
this.privateEncryptionKey = privEK;
this.pubkey = Security.createPubkey(Pubkey.LATEST_VERSION, stream, privateSigningKey, privateEncryptionKey,
this.pubkey = security().createPubkey(Pubkey.LATEST_VERSION, stream, privateSigningKey, privateEncryptionKey,
nonceTrialsPerByte, extraBytes, features);
}
@ -66,9 +66,9 @@ public class PrivateKey implements Streamable {
public PrivateKey(long version, long stream, String passphrase, long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) {
try {
// FIXME: this is most definitely wrong
this.privateSigningKey = Bytes.truncate(Security.sha512(passphrase.getBytes("UTF-8"), new byte[]{0}), 32);
this.privateEncryptionKey = Bytes.truncate(Security.sha512(passphrase.getBytes("UTF-8"), new byte[]{1}), 32);
this.pubkey = Security.createPubkey(version, stream, privateSigningKey, privateEncryptionKey,
this.privateSigningKey = Bytes.truncate(security().sha512(passphrase.getBytes("UTF-8"), new byte[]{0}), 32);
this.privateEncryptionKey = Bytes.truncate(security().sha512(passphrase.getBytes("UTF-8"), new byte[]{1}), 32);
this.pubkey = security().createPubkey(version, stream, privateSigningKey, privateEncryptionKey,
nonceTrialsPerByte, extraBytes, features);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);

View File

@ -23,7 +23,7 @@ import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.payload.*;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.exception.NodeException;
import ch.dissem.bitmessage.utils.Security;
import ch.dissem.bitmessage.ports.Security;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -32,6 +32,8 @@ import java.io.InputStream;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import static ch.dissem.bitmessage.utils.Singleton.security;
/**
* Creates {@link NetworkMessage} objects from {@link InputStream InputStreams}
*/
@ -115,8 +117,8 @@ public class Factory {
BitmessageAddress temp = new BitmessageAddress(address);
PrivateKey privateKey = new PrivateKey(privateSigningKey, privateEncryptionKey,
createPubkey(temp.getVersion(), temp.getStream(),
Security.createPublicKey(privateSigningKey).getEncoded(false),
Security.createPublicKey(privateEncryptionKey).getEncoded(false),
security().createPublicKey(privateSigningKey),
security().createPublicKey(privateEncryptionKey),
nonceTrialsPerByte, extraBytes, behaviourBitfield));
BitmessageAddress result = new BitmessageAddress(privateKey);
if (!result.getAddress().equals(address)) {
@ -126,11 +128,17 @@ public class Factory {
return result;
}
public static BitmessageAddress generatePrivateAddress(boolean shorter, long stream, Pubkey.Feature... features) {
public static BitmessageAddress generatePrivateAddress(boolean shorter,
long stream,
Pubkey.Feature... features) {
return new BitmessageAddress(new PrivateKey(shorter, stream, 1000, 1000, features));
}
static ObjectPayload getObjectPayload(long objectType, long version, long streamNumber, InputStream stream, int length) throws IOException {
static ObjectPayload getObjectPayload(long objectType,
long version,
long streamNumber,
InputStream stream,
int length) throws IOException {
ObjectType type = ObjectType.fromNumber(objectType);
if (type != null) {
switch (type) {

View File

@ -22,9 +22,9 @@ import ch.dissem.bitmessage.entity.payload.ObjectPayload;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
import ch.dissem.bitmessage.exception.NodeException;
import ch.dissem.bitmessage.ports.Security;
import ch.dissem.bitmessage.utils.AccessCounter;
import ch.dissem.bitmessage.utils.Decode;
import ch.dissem.bitmessage.utils.Security;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -33,6 +33,7 @@ import java.io.IOException;
import java.io.InputStream;
import static ch.dissem.bitmessage.entity.NetworkMessage.MAGIC_BYTES;
import static ch.dissem.bitmessage.utils.Singleton.security;
/**
* Creates protocol v3 network messages from {@link InputStream InputStreams}
@ -174,7 +175,7 @@ class V3MessageFactory {
}
private static boolean testChecksum(byte[] checksum, byte[] payload) {
byte[] payloadChecksum = Security.sha512(payload);
byte[] payloadChecksum = security().sha512(payload);
for (int i = 0; i < checksum.length; i++) {
if (checksum[i] != payloadChecksum[i]) {
return false;

View File

@ -0,0 +1,175 @@
/*
* Copyright 2015 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.bitmessage.ports;
import ch.dissem.bitmessage.InternalContext;
import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.utils.Bytes;
import ch.dissem.bitmessage.utils.UnixTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.SecureRandom;
/**
* Implements everything that isn't directly dependent on either Spongy- or Bouncycastle.
*/
public abstract class AbstractSecurity implements Security, InternalContext.ContextHolder {
public static final Logger LOG = LoggerFactory.getLogger(Security.class);
private static final SecureRandom RANDOM = new SecureRandom();
private static final BigInteger TWO = BigInteger.valueOf(2);
private final String provider;
private InternalContext context;
protected AbstractSecurity(String provider) {
this.provider = provider;
}
@Override
public void setContext(InternalContext context) {
this.context = context;
}
public byte[] sha512(byte[]... data) {
return hash("SHA-512", data);
}
public byte[] doubleSha512(byte[]... data) {
MessageDigest mda = md("SHA-512");
for (byte[] d : data) {
mda.update(d);
}
return mda.digest(mda.digest());
}
public byte[] doubleSha512(byte[] data, int length) {
MessageDigest mda = md("SHA-512");
mda.update(data, 0, length);
return mda.digest(mda.digest());
}
public byte[] ripemd160(byte[]... data) {
return hash("RIPEMD160", data);
}
public byte[] doubleSha256(byte[] data, int length) {
MessageDigest mda = md("SHA-256");
mda.update(data, 0, length);
return mda.digest(mda.digest());
}
public byte[] sha1(byte[]... data) {
return hash("SHA-1", data);
}
public byte[] randomBytes(int length) {
byte[] result = new byte[length];
RANDOM.nextBytes(result);
return result;
}
public void doProofOfWork(ObjectMessage object, long nonceTrialsPerByte,
long extraBytes) {
try {
if (nonceTrialsPerByte < 1000) nonceTrialsPerByte = 1000;
if (extraBytes < 1000) extraBytes = 1000;
byte[] initialHash = getInitialHash(object);
byte[] target = getProofOfWorkTarget(object, nonceTrialsPerByte, extraBytes);
byte[] nonce = context.getProofOfWorkEngine().calculateNonce(initialHash, target);
object.setNonce(nonce);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void checkProofOfWork(ObjectMessage object, long nonceTrialsPerByte, long extraBytes)
throws IOException {
byte[] target = getProofOfWorkTarget(object, nonceTrialsPerByte, extraBytes);
byte[] value = doubleSha512(object.getNonce(), getInitialHash(object));
if (Bytes.lt(target, value, 8)) {
throw new InsufficientProofOfWorkException(target, value);
}
}
private byte[] getInitialHash(ObjectMessage object) throws IOException {
return sha512(object.getPayloadBytesWithoutNonce());
}
private byte[] getProofOfWorkTarget(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) throws IOException {
BigInteger TTL = BigInteger.valueOf(object.getExpiresTime() - UnixTime.now());
LOG.debug("TTL: " + TTL + "s");
BigInteger numerator = TWO.pow(64);
BigInteger powLength = BigInteger.valueOf(object.getPayloadBytesWithoutNonce().length + extraBytes);
BigInteger denominator = BigInteger.valueOf(nonceTrialsPerByte).multiply(powLength.add(powLength.multiply(TTL).divide(BigInteger.valueOf(2).pow(16))));
return Bytes.expand(numerator.divide(denominator).toByteArray(), 8);
}
private byte[] hash(String algorithm, byte[]... data) {
MessageDigest mda = md(algorithm);
for (byte[] d : data) {
mda.update(d);
}
return mda.digest();
}
private MessageDigest md(String algorithm) {
try {
return MessageDigest.getInstance(algorithm, provider);
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
public byte[] mac(byte[] key_m, byte[] data) {
try {
Mac mac = Mac.getInstance("HmacSHA256", provider);
mac.init(new SecretKeySpec(key_m, "HmacSHA256"));
return mac.doFinal(data);
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
public Pubkey createPubkey(long version, long stream, byte[] privateSigningKey, byte[] privateEncryptionKey,
long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) {
return Factory.createPubkey(version, stream,
createPublicKey(privateSigningKey),
createPublicKey(privateEncryptionKey),
nonceTrialsPerByte, extraBytes, features);
}
public BigInteger keyToBigInt(byte[] privateKey) {
return new BigInteger(1, privateKey);
}
public long randomNonce() {
return RANDOM.nextLong();
}
}

View File

@ -34,6 +34,8 @@ public interface NetworkHandler {
Property getNetworkStatus();
boolean isRunning();
interface MessageListener {
void receive(ObjectMessage object) throws IOException;
}

View File

@ -0,0 +1,206 @@
/*
* Copyright 2015 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.bitmessage.ports;
import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException;
import java.io.IOException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.SecureRandom;
/**
* Provides some methods to help with hashing and encryption. All randoms are created using {@link SecureRandom},
* which should be secure enough.
*/
public interface Security {
/**
* A helper method to calculate SHA-512 hashes. Please note that a new {@link MessageDigest} object is created at
* each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in
* success on the same thread.
*
* @param data to get hashed
* @return SHA-512 hash of data
*/
byte[] sha512(byte[]... data);
/**
* A helper method to calculate doubleSHA-512 hashes. Please note that a new {@link MessageDigest} object is created
* at each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in
* success on the same thread.
*
* @param data to get hashed
* @return SHA-512 hash of data
*/
byte[] doubleSha512(byte[]... data);
/**
* A helper method to calculate double SHA-512 hashes. This method allows to only use a part of the available bytes
* to use for the hash calculation.
* <p>
* Please note that a new {@link MessageDigest} object is created at each call (to ensure thread safety), so you
* shouldn't use this if you need to do many hash calculations in short order on the same thread.
* </p>
*
* @param data to get hashed
* @param length number of bytes to be taken into account
* @return SHA-512 hash of data
*/
byte[] doubleSha512(byte[] data, int length);
/**
* A helper method to calculate RIPEMD-160 hashes. Supplying multiple byte arrays has the same result as a
* concatenation of all arrays, but might perform better.
* <p>
* Please note that a new {@link MessageDigest} object is created at
* each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in short
* order on the same thread.
* </p>
*
* @param data to get hashed
* @return RIPEMD-160 hash of data
*/
byte[] ripemd160(byte[]... data);
/**
* A helper method to calculate double SHA-256 hashes. This method allows to only use a part of the available bytes
* to use for the hash calculation.
* <p>
* Please note that a new {@link MessageDigest} object is created at
* each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in short
* order on the same thread.
* </p>
*
* @param data to get hashed
* @param length number of bytes to be taken into account
* @return SHA-256 hash of data
*/
byte[] doubleSha256(byte[] data, int length);
/**
* A helper method to calculate SHA-1 hashes. Supplying multiple byte arrays has the same result as a
* concatenation of all arrays, but might perform better.
* <p>
* Please note that a new {@link MessageDigest} object is created at
* each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in short
* order on the same thread.
* </p>
*
* @param data to get hashed
* @return SHA hash of data
*/
byte[] sha1(byte[]... data);
/**
* @param length number of bytes to return
* @return an array of the given size containing random bytes
*/
byte[] randomBytes(int length);
/**
* Calculates the proof of work. This might take a long time, depending on the hardware, message size and time to
* live.
*
* @param object to do the proof of work for
* @param nonceTrialsPerByte difficulty
* @param extraBytes bytes to add to the object size (makes it more difficult to send small messages)
*/
void doProofOfWork(ObjectMessage object, long nonceTrialsPerByte,
long extraBytes);
/**
* @param object to be checked
* @param nonceTrialsPerByte difficulty
* @param extraBytes bytes to add to the object size
* @throws InsufficientProofOfWorkException if proof of work doesn't check out (makes it more difficult to send small messages)
*/
void checkProofOfWork(ObjectMessage object, long nonceTrialsPerByte, long extraBytes)
throws IOException;
/**
* Calculates the MAC for a message (data)
*
* @param key_m the symmetric key used
* @param data the message data to calculate the MAC for
* @return the MAC
*/
byte[] mac(byte[] key_m, byte[] data);
/**
*
* @param encrypt if true, encrypts data, otherwise tries to decrypt it.
* @param data
* @param key_e
* @return
*/
byte[] crypt(boolean encrypt, byte[] data, byte[] key_e, byte[] initializationVector);
/**
* Create a new public key fom given private keys.
*
* @param version of the public key / address
* @param stream of the address
* @param privateSigningKey private key used for signing
* @param privateEncryptionKey private key used for encryption
* @param nonceTrialsPerByte proof of work difficulty
* @param extraBytes bytes to add for the proof of work (make it harder for small messages)
* @param features of the address
* @return a public key object
*/
Pubkey createPubkey(long version, long stream, byte[] privateSigningKey, byte[] privateEncryptionKey,
long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features);
/**
* @param privateKey private key as byte array
* @return a public key corresponding to the given private key
*/
byte[] createPublicKey(byte[] privateKey);
/**
* @param privateKey private key as byte array
* @return a big integer representation (unsigned) of the given bytes
*/
BigInteger keyToBigInt(byte[] privateKey);
/**
* @param data to check
* @param signature the signature of the message
* @param pubkey the sender's public key
* @return true if the signature is valid, false otherwise
*/
boolean isSignatureValid(byte[] data, byte[] signature, Pubkey pubkey);
/**
* Calculate the signature of data, using the given private key.
*
* @param data to be signed
* @param privateKey to be used for signing
* @return the signature
*/
byte[] getSignature(byte[] data, ch.dissem.bitmessage.entity.valueobject.PrivateKey privateKey);
/**
* @return a random number of type long
*/
long randomNonce();
byte[] multiply(byte[] k, byte[] r);
byte[] createPoint(byte[] x, byte[] y);
}

View File

@ -23,7 +23,7 @@ import java.security.MessageDigest;
import static ch.dissem.bitmessage.utils.Bytes.inc;
/**
* Created by chris on 14.04.15.
* You should really use the MultiThreadedPOWEngine, but this one might help you grok the other one.
*/
public class SimplePOWEngine implements ProofOfWorkEngine {
@Override

View File

@ -0,0 +1,32 @@
/*
* Copyright 2015 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.bitmessage.utils;
import java.util.Arrays;
/**
* Created by chris on 20.07.15.
*/
public class Points {
public static byte[] getX(byte[] P) {
return Arrays.copyOfRange(P, 1, ((P.length - 1) / 2) + 1);
}
public static byte[] getY(byte[] P) {
return Arrays.copyOfRange(P, ((P.length - 1) / 2) + 1, P.length);
}
}

View File

@ -17,7 +17,12 @@
package ch.dissem.bitmessage.utils;
/**
* Created by chris on 14.06.15.
* Some property that has a name, a value and/or other properties. This can be used for any purpose, but is for now
* used to contain different status information. It is by default displayed in some JSON inspired human readable
* notation, but you might only want to rely on the 'human readable' part.
* <p>
* If you need a real JSON representation, please add a method <code>toJson()</code>.
* </p>
*/
public class Property {
private String name;

View File

@ -1,365 +0,0 @@
/*
* Copyright 2015 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.bitmessage.utils;
import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.ports.ProofOfWorkEngine;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.ec.CustomNamedCurves;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.KeySpec;
import java.util.Arrays;
/**
* Provides some methods to help with hashing and encryption. All randoms are created using {@link SecureRandom},
* which should be secure enough.
*/
public class Security {
public static final Logger LOG = LoggerFactory.getLogger(Security.class);
private static final SecureRandom RANDOM = new SecureRandom();
private static final BigInteger TWO = BigInteger.valueOf(2);
private static final X9ECParameters EC_CURVE_PARAMETERS = CustomNamedCurves.getByName("secp256k1");
static {
java.security.Security.addProvider(new BouncyCastleProvider());
}
/**
* A helper method to calculate SHA-512 hashes. Please note that a new {@link MessageDigest} object is created at
* each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in
* success on the same thread.
*
* @param data to get hashed
* @return SHA-512 hash of data
*/
public static byte[] sha512(byte[]... data) {
return hash("SHA-512", data);
}
/**
* A helper method to calculate doubleSHA-512 hashes. Please note that a new {@link MessageDigest} object is created
* at each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in
* success on the same thread.
*
* @param data to get hashed
* @return SHA-512 hash of data
*/
public static byte[] doubleSha512(byte[]... data) {
MessageDigest mda = md("SHA-512");
for (byte[] d : data) {
mda.update(d);
}
return mda.digest(mda.digest());
}
/**
* A helper method to calculate double SHA-512 hashes. This method allows to only use a part of the available bytes
* to use for the hash calculation.
* <p>
* Please note that a new {@link MessageDigest} object is created at each call (to ensure thread safety), so you
* shouldn't use this if you need to do many hash calculations in short order on the same thread.
* </p>
*
* @param data to get hashed
* @param length number of bytes to be taken into account
* @return SHA-512 hash of data
*/
public static byte[] doubleSha512(byte[] data, int length) {
MessageDigest mda = md("SHA-512");
mda.update(data, 0, length);
return mda.digest(mda.digest());
}
/**
* A helper method to calculate RIPEMD-160 hashes. Supplying multiple byte arrays has the same result as a
* concatenation of all arrays, but might perform better.
* <p>
* Please note that a new {@link MessageDigest} object is created at
* each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in short
* order on the same thread.
* </p>
*
* @param data to get hashed
* @return RIPEMD-160 hash of data
*/
public static byte[] ripemd160(byte[]... data) {
return hash("RIPEMD160", data);
}
/**
* A helper method to calculate double SHA-256 hashes. This method allows to only use a part of the available bytes
* to use for the hash calculation.
* <p>
* Please note that a new {@link MessageDigest} object is created at
* each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in short
* order on the same thread.
* </p>
*
* @param data to get hashed
* @param length number of bytes to be taken into account
* @return SHA-256 hash of data
*/
public static byte[] doubleSha256(byte[] data, int length) {
MessageDigest mda = md("SHA-256");
mda.update(data, 0, length);
return mda.digest(mda.digest());
}
/**
* A helper method to calculate SHA-1 hashes. Supplying multiple byte arrays has the same result as a
* concatenation of all arrays, but might perform better.
* <p>
* Please note that a new {@link MessageDigest} object is created at
* each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in short
* order on the same thread.
* </p>
*
* @param data to get hashed
* @return SHA hash of data
*/
public static byte[] sha1(byte[]... data) {
return hash("SHA-1", data);
}
/**
* @param length number of bytes to return
* @return an array of the given size containing random bytes
*/
public static byte[] randomBytes(int length) {
byte[] result = new byte[length];
RANDOM.nextBytes(result);
return result;
}
/**
* Calculates the proof of work. This might take a long time, depending on the hardware, message size and time to
* live.
*
* @param object to do the proof of work for
* @param worker doing the actual proof of work
* @param nonceTrialsPerByte difficulty
* @param extraBytes bytes to add to the object size (makes it more difficult to send small messages)
*/
public static void doProofOfWork(ObjectMessage object, ProofOfWorkEngine worker, long nonceTrialsPerByte,
long extraBytes) {
try {
if (nonceTrialsPerByte < 1000) nonceTrialsPerByte = 1000;
if (extraBytes < 1000) extraBytes = 1000;
byte[] initialHash = getInitialHash(object);
byte[] target = getProofOfWorkTarget(object, nonceTrialsPerByte, extraBytes);
byte[] nonce = worker.calculateNonce(initialHash, target);
object.setNonce(nonce);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* @param object to be checked
* @param nonceTrialsPerByte difficulty
* @param extraBytes bytes to add to the object size
* @throws InsufficientProofOfWorkException if proof of work doesn't check out (makes it more difficult to send small messages)
*/
public static void checkProofOfWork(ObjectMessage object, long nonceTrialsPerByte, long extraBytes)
throws IOException {
byte[] target = getProofOfWorkTarget(object, nonceTrialsPerByte, extraBytes);
byte[] value = Security.doubleSha512(object.getNonce(), getInitialHash(object));
if (Bytes.lt(target, value, 8)) {
throw new InsufficientProofOfWorkException(target, value);
}
}
private static byte[] getInitialHash(ObjectMessage object) throws IOException {
return Security.sha512(object.getPayloadBytesWithoutNonce());
}
private static byte[] getProofOfWorkTarget(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) throws IOException {
BigInteger TTL = BigInteger.valueOf(object.getExpiresTime() - UnixTime.now());
LOG.debug("TTL: " + TTL + "s");
BigInteger numerator = TWO.pow(64);
BigInteger powLength = BigInteger.valueOf(object.getPayloadBytesWithoutNonce().length + extraBytes);
BigInteger denominator = BigInteger.valueOf(nonceTrialsPerByte).multiply(powLength.add(powLength.multiply(TTL).divide(BigInteger.valueOf(2).pow(16))));
return Bytes.expand(numerator.divide(denominator).toByteArray(), 8);
}
private static byte[] hash(String algorithm, byte[]... data) {
MessageDigest mda = md(algorithm);
for (byte[] d : data) {
mda.update(d);
}
return mda.digest();
}
private static MessageDigest md(String algorithm) {
try {
return MessageDigest.getInstance(algorithm, "BC");
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
/**
* Calculates the MAC for a message (data)
*
* @param key_m the symmetric key used
* @param data the message data to calculate the MAC for
* @return the MAC
*/
public static byte[] mac(byte[] key_m, byte[] data) {
try {
Mac mac = Mac.getInstance("HmacSHA256", "BC");
mac.init(new SecretKeySpec(key_m, "HmacSHA256"));
return mac.doFinal(data);
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
/**
* Create a new public key fom given private keys.
*
* @param version of the public key / address
* @param stream of the address
* @param privateSigningKey private key used for signing
* @param privateEncryptionKey private key used for encryption
* @param nonceTrialsPerByte proof of work difficulty
* @param extraBytes bytes to add for the proof of work (make it harder for small messages)
* @param features of the address
* @return a public key object
*/
public static Pubkey createPubkey(long version, long stream, byte[] privateSigningKey, byte[] privateEncryptionKey,
long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) {
return Factory.createPubkey(version, stream,
createPublicKey(privateSigningKey).getEncoded(false),
createPublicKey(privateEncryptionKey).getEncoded(false),
nonceTrialsPerByte, extraBytes, features);
}
/**
* @param privateKey private key as byte array
* @return a public key corresponding to the given private key
*/
public static ECPoint createPublicKey(byte[] privateKey) {
return EC_CURVE_PARAMETERS.getG().multiply(keyToBigInt(privateKey)).normalize();
}
/**
* @param privateKey private key as byte array
* @return a big integer representation (unsigned) of the given bytes
*/
public static BigInteger keyToBigInt(byte[] privateKey) {
return new BigInteger(1, privateKey);
}
public static ECPoint keyToPoint(byte[] publicKey) {
BigInteger x = new BigInteger(1, Arrays.copyOfRange(publicKey, 1, 33));
BigInteger y = new BigInteger(1, Arrays.copyOfRange(publicKey, 33, 65));
return EC_CURVE_PARAMETERS.getCurve().createPoint(x, y);
}
public static ECPoint createPoint(byte[] x, byte[] y) {
return EC_CURVE_PARAMETERS.getCurve().createPoint(
new BigInteger(1, x),
new BigInteger(1, y)
);
}
/**
* @param data to check
* @param signature the signature of the message
* @param pubkey the sender's public key
* @return true if the signature is valid, false otherwise
*/
public static boolean isSignatureValid(byte[] data, byte[] signature, Pubkey pubkey) {
try {
ECParameterSpec spec = new ECParameterSpec(
EC_CURVE_PARAMETERS.getCurve(),
EC_CURVE_PARAMETERS.getG(),
EC_CURVE_PARAMETERS.getN(),
EC_CURVE_PARAMETERS.getH(),
EC_CURVE_PARAMETERS.getSeed()
);
ECPoint Q = keyToPoint(pubkey.getSigningKey());
KeySpec keySpec = new ECPublicKeySpec(Q, spec);
PublicKey publicKey = KeyFactory.getInstance("ECDSA", "BC").generatePublic(keySpec);
Signature sig = Signature.getInstance("ECDSA", "BC");
sig.initVerify(publicKey);
sig.update(data);
return sig.verify(signature);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Calculate the signature of data, using the given private key.
*
* @param data to be signed
* @param privateKey to be used for signing
* @return the signature
*/
public static byte[] getSignature(byte[] data, ch.dissem.bitmessage.entity.valueobject.PrivateKey privateKey) {
try {
ECParameterSpec spec = new ECParameterSpec(
EC_CURVE_PARAMETERS.getCurve(),
EC_CURVE_PARAMETERS.getG(),
EC_CURVE_PARAMETERS.getN(),
EC_CURVE_PARAMETERS.getH(),
EC_CURVE_PARAMETERS.getSeed()
);
BigInteger d = keyToBigInt(privateKey.getPrivateSigningKey());
KeySpec keySpec = new ECPrivateKeySpec(d, spec);
PrivateKey privKey = KeyFactory.getInstance("ECDSA", "BC").generatePrivate(keySpec);
Signature sig = Signature.getInstance("ECDSA", "BC");
sig.initSign(privKey);
sig.update(data);
return sig.sign();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* @return a random number of type long
*/
public static long randomNonce() {
return RANDOM.nextLong();
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright 2015 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.bitmessage.utils;
import ch.dissem.bitmessage.ports.Security;
/**
* Created by chris on 20.07.15.
*/
public class Singleton {
private static Security security;
public static void initialize(Security security) {
Singleton.security = security;
}
public static Security security() {
return security;
}
}

View File

@ -21,6 +21,7 @@ import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.payload.V4Broadcast;
import ch.dissem.bitmessage.entity.payload.V5Broadcast;
import ch.dissem.bitmessage.exception.DecryptionFailedException;
import ch.dissem.bitmessage.utils.TestBase;
import ch.dissem.bitmessage.utils.TestUtils;
import org.junit.Test;
@ -29,7 +30,7 @@ import java.io.IOException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class DecryptionTest {
public class DecryptionTest extends TestBase {
@Test
public void ensureV4BroadcastIsDecryptedCorrectly() throws IOException, DecryptionFailedException {
BitmessageAddress address = new BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ");

View File

@ -24,20 +24,20 @@ import ch.dissem.bitmessage.entity.payload.GenericPayload;
import ch.dissem.bitmessage.entity.payload.Msg;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.exception.DecryptionFailedException;
import ch.dissem.bitmessage.utils.Security;
import ch.dissem.bitmessage.utils.TestBase;
import ch.dissem.bitmessage.utils.TestUtils;
import org.junit.Test;
import java.io.IOException;
import static ch.dissem.bitmessage.utils.Singleton.security;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
public class EncryptionTest {
public class EncryptionTest extends TestBase {
@Test
public void ensureDecryptedDataIsSameAsBeforeEncryption() throws IOException, DecryptionFailedException {
GenericPayload before = new GenericPayload(0, 1, Security.randomBytes(100));
GenericPayload before = new GenericPayload(0, 1, security().randomBytes(100));
PrivateKey privateKey = new PrivateKey(false, 1, 1000, 1000);
CryptoBox cryptoBox = new CryptoBox(before, privateKey.getPubkey().getEncryptionKey());

View File

@ -25,6 +25,7 @@ import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.entity.payload.V4Pubkey;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.exception.DecryptionFailedException;
import ch.dissem.bitmessage.utils.TestBase;
import ch.dissem.bitmessage.utils.TestUtils;
import org.junit.Test;
@ -33,7 +34,7 @@ import java.util.Date;
import static org.junit.Assert.*;
public class SignatureTest {
public class SignatureTest extends TestBase {
@Test
public void ensureValidationWorks() throws IOException {
ObjectMessage object = TestUtils.loadObjectMessage(3, "V3Pubkey.payload");

View File

@ -27,6 +27,7 @@ import java.io.IOException;
import java.util.Arrays;
import static ch.dissem.bitmessage.entity.payload.Pubkey.Feature.DOES_ACK;
import static ch.dissem.bitmessage.utils.Singleton.security;
import static org.junit.Assert.*;
public class BitmessageAddressTest {
@ -102,7 +103,7 @@ public class BitmessageAddressTest {
System.out.println("\n\n" + Strings.hex(privsigningkey) + "\n\n");
BitmessageAddress address = new BitmessageAddress(new PrivateKey(privsigningkey, privencryptionkey,
Security.createPubkey(3, 1, privsigningkey, privencryptionkey, 320, 14000)));
security().createPubkey(3, 1, privsigningkey, privencryptionkey, 320, 14000)));
assertEquals(address_string, address.getAddress());
}
@ -119,7 +120,7 @@ public class BitmessageAddressTest {
if (bytes.length != 37)
throw new IOException("Unknown format: 37 bytes expected, but secret " + walletImportFormat + " was " + bytes.length + " long");
byte[] hash = Security.doubleSha256(bytes, 33);
byte[] hash = security().doubleSha256(bytes, 33);
for (int i = 0; i < 4; i++) {
if (hash[i] != bytes[33 + i]) throw new IOException("Hash check failed for secret " + walletImportFormat);
}
@ -132,7 +133,7 @@ public class BitmessageAddressTest {
byte[] privsigningkey = getSecret("5KMWqfCyJZGFgW6QrnPJ6L9Gatz25B51y7ErgqNr1nXUVbtZbdU");
byte[] privencryptionkey = getSecret("5JXXWEuhHQEPk414SzEZk1PHDRi8kCuZd895J7EnKeQSahJPxGz");
BitmessageAddress address = new BitmessageAddress(new PrivateKey(privsigningkey, privencryptionkey,
Security.createPubkey(4, 1, privsigningkey, privencryptionkey, 320, 14000)));
security().createPubkey(4, 1, privsigningkey, privencryptionkey, 320, 14000)));
assertEquals("BM-2cV5f9EpzaYARxtoruSpa6pDoucSf9ZNke", address.getAddress());
}

View File

@ -17,14 +17,11 @@
package ch.dissem.bitmessage.ports;
import ch.dissem.bitmessage.utils.Bytes;
import ch.dissem.bitmessage.utils.Security;
import org.junit.Test;
import static ch.dissem.bitmessage.utils.Singleton.security;
import static org.junit.Assert.assertTrue;
/**
* Created by chris on 17.04.15.
*/
public class ProofOfWorkEngineTest {
@Test
public void testSimplePOWEngine() {
@ -38,11 +35,11 @@ public class ProofOfWorkEngineTest {
private void testPOW(ProofOfWorkEngine engine) {
long time = System.currentTimeMillis();
byte[] initialHash = Security.sha512(new byte[]{1, 3, 6, 4});
byte[] initialHash = security().sha512(new byte[]{1, 3, 6, 4});
byte[] target = {0, 0, -1, -1, -1, -1, -1, -1};
byte[] nonce = engine.calculateNonce(initialHash, target);
System.out.println("Calculating nonce took " + (System.currentTimeMillis() - time) + "ms");
assertTrue(Bytes.lt(Security.doubleSha512(nonce, initialHash), target, 8));
assertTrue(Bytes.lt(security().doubleSha512(nonce, initialHash), target, 8));
}
}

View File

@ -25,9 +25,6 @@ import java.util.Random;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
/**
* Created by chris on 10.04.15.
*/
public class BytesTest {
public static final Random rnd = new Random();

View File

@ -22,9 +22,6 @@ import java.io.*;
import static org.junit.Assert.assertEquals;
/**
* Created by chris on 20.03.15.
*/
public class DecodeTest {
@Test
public void ensureDecodingWorks() throws Exception {

View File

@ -23,9 +23,6 @@ import java.io.IOException;
import static org.junit.Assert.assertEquals;
/**
* Created by chris on 13.03.15.
*/
public class EncodeTest {
@Test
public void testUint8() throws IOException {

View File

@ -0,0 +1,28 @@
/*
* Copyright 2015 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.bitmessage.utils;
import ch.dissem.bitmessage.security.bc.BouncySecurity;
/**
* Created by chris on 20.07.15.
*/
public class TestBase {
static {
Singleton.initialize(new BouncySecurity());
}
}

View File

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip

View File

@ -26,7 +26,6 @@ import ch.dissem.bitmessage.exception.NodeException;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.ports.NetworkHandler.MessageListener;
import ch.dissem.bitmessage.utils.DebugUtils;
import ch.dissem.bitmessage.utils.Security;
import ch.dissem.bitmessage.utils.UnixTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -44,6 +43,7 @@ import java.util.concurrent.ConcurrentMap;
import static ch.dissem.bitmessage.networking.Connection.Mode.CLIENT;
import static ch.dissem.bitmessage.networking.Connection.State.*;
import static ch.dissem.bitmessage.utils.Singleton.security;
import static ch.dissem.bitmessage.utils.UnixTime.MINUTE;
/**
@ -275,7 +275,7 @@ public class Connection implements Runnable {
ObjectMessage objectMessage = (ObjectMessage) messagePayload;
try {
LOG.debug("Received object " + objectMessage.getInventoryVector());
Security.checkProofOfWork(objectMessage, ctx.getNetworkNonceTrialsPerByte(), ctx.getNetworkExtraBytes());
security().checkProofOfWork(objectMessage, ctx.getNetworkNonceTrialsPerByte(), ctx.getNetworkExtraBytes());
listener.receive(objectMessage);
ctx.getInventory().storeObject(objectMessage);
// offer object to some random nodes so it gets distributed throughout the network:

View File

@ -44,9 +44,9 @@ import static ch.dissem.bitmessage.utils.DebugUtils.inc;
/**
* Handles all the networky stuff.
*/
public class NetworkNode implements NetworkHandler, ContextHolder {
public class DefaultNetworkHandler implements NetworkHandler, ContextHolder {
public final static int NETWORK_MAGIC_NUMBER = 8;
private final static Logger LOG = LoggerFactory.getLogger(NetworkNode.class);
private final static Logger LOG = LoggerFactory.getLogger(DefaultNetworkHandler.class);
private final ExecutorService pool;
private final List<Connection> connections = new LinkedList<>();
private InternalContext ctx;
@ -56,7 +56,7 @@ public class NetworkNode implements NetworkHandler, ContextHolder {
private ConcurrentMap<InventoryVector, Long> requestedObjects = new ConcurrentHashMap<>();
public NetworkNode() {
public DefaultNetworkHandler() {
pool = Executors.newCachedThreadPool();
}
@ -128,6 +128,11 @@ public class NetworkNode implements NetworkHandler, ContextHolder {
}
}
@Override
public boolean isRunning() {
return connectionManager != null && connectionManager.isAlive();
}
@Override
public void stop() {
connectionManager.interrupt();

View File

@ -24,16 +24,21 @@ import org.junit.Ignore;
import org.junit.Test;
/**
* Created by chris on 20.03.15.
* FIXME: there really should be sensible tests for the network handler
*/
public class NetworkNodeTest {
public class DefaultNetworkHandlerTest {
private NetworkAddress localhost = new NetworkAddress.Builder().ipv4(127, 0, 0, 1).port(8444).build();
// void start(MessageListener listener);
// void stop();
// void offer(InventoryVector iv);
// Property getNetworkStatus();
@Ignore
@Test(expected = InterruptedException.class)
public void testSendMessage() throws Exception {
final Thread baseThread = Thread.currentThread();
NetworkNode net = new NetworkNode();
DefaultNetworkHandler net = new DefaultNetworkHandler();
// net.setListener(localhost, new NetworkHandler.MessageListener() {
// @Override
// public void receive(ObjectPayload payload) {

View File

@ -12,7 +12,9 @@ uploadArchives {
dependencies {
compile project(':domain')
compile 'com.h2database:h2:1.4.187'
compile 'org.flywaydb:flyway-core:3.2.1'
testCompile 'junit:junit:4.11'
testCompile 'com.h2database:h2:1.4.187'
testCompile 'org.mockito:mockito-core:1.10.19'
testCompile project(':security-bc')
}

View File

@ -28,9 +28,9 @@ import java.sql.SQLException;
*/
public class JdbcConfig {
protected final Flyway flyway;
private final String dbUrl;
private final String dbUser;
private final String dbPassword;
protected final String dbUrl;
protected final String dbUser;
protected final String dbPassword;
public JdbcConfig(String dbUrl, String dbUser, String dbPassword) {
this.dbUrl = dbUrl;

View File

@ -18,14 +18,15 @@ package ch.dissem.bitmessage.repository;
import ch.dissem.bitmessage.entity.Streamable;
import ch.dissem.bitmessage.entity.payload.ObjectType;
import org.flywaydb.core.Flyway;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.sql.*;
import java.sql.Blob;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import static ch.dissem.bitmessage.utils.Strings.hex;
@ -81,8 +82,8 @@ abstract class JdbcHelper {
if (data != null) {
ByteArrayOutputStream os = new ByteArrayOutputStream();
data.write(os);
ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
ps.setBlob(parameterIndex, is);
byte[] bytes = os.toByteArray();
ps.setBinaryStream(parameterIndex, new ByteArrayInputStream(bytes), bytes.length);
} else {
ps.setBlob(parameterIndex, (Blob) null);
}

View File

@ -25,7 +25,7 @@ import java.util.List;
import static org.junit.Assert.*;
public class JdbcAddressRepositoryTest {
public class JdbcAddressRepositoryTest extends TestBase {
public static final String CONTACT_A = "BM-2cW7cD5cDQJDNkE7ibmyTxfvGAmnPqa9Vt";
public static final String CONTACT_B = "BM-2cTtkBnb4BUYDndTKun6D9PjtueP2h1bQj";
public static final String CONTACT_C = "BM-2cV5f9EpzaYARxtoruSpa6pDoucSf9ZNke";

View File

@ -34,7 +34,7 @@ import static ch.dissem.bitmessage.utils.UnixTime.DAY;
import static ch.dissem.bitmessage.utils.UnixTime.now;
import static org.junit.Assert.*;
public class JdbcInventoryTest {
public class JdbcInventoryTest extends TestBase {
private TestJdbcConfig config;
private Inventory inventory;

View File

@ -25,7 +25,6 @@ import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.ports.AddressRepository;
import ch.dissem.bitmessage.ports.MessageRepository;
import ch.dissem.bitmessage.utils.Security;
import org.junit.Before;
import org.junit.Test;
@ -33,10 +32,11 @@ import java.util.Arrays;
import java.util.List;
import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG;
import static ch.dissem.bitmessage.utils.Singleton.security;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
public class JdbcMessageRepositoryTest {
public class JdbcMessageRepositoryTest extends TestBase {
private BitmessageAddress contactA;
private BitmessageAddress contactB;
private BitmessageAddress identity;
@ -54,7 +54,11 @@ public class JdbcMessageRepositoryTest {
config.reset();
addressRepo = new JdbcAddressRepository(config);
repo = new JdbcMessageRepository(config);
new InternalContext(new BitmessageContext.Builder().addressRepo(addressRepo).messageRepo(repo));
new InternalContext(new BitmessageContext.Builder()
.security(security())
.addressRepo(addressRepo)
.messageRepo(repo)
);
BitmessageAddress tmp = new BitmessageAddress(new PrivateKey(false, 1, 1000, 1000));
contactA = new BitmessageAddress(tmp.getAddress());
@ -120,7 +124,7 @@ public class JdbcMessageRepositoryTest {
@Test
public void testSave() throws Exception {
Plaintext message = new Plaintext.Builder(MSG)
.IV(new InventoryVector(Security.randomBytes(32)))
.IV(new InventoryVector(security().randomBytes(32)))
.from(identity)
.to(contactA)
.message("Subject", "Message")
@ -143,7 +147,7 @@ public class JdbcMessageRepositoryTest {
public void testUpdate() throws Exception {
List<Plaintext> messages = repo.findMessages(Plaintext.Status.DRAFT, contactA);
Plaintext message = messages.get(0);
message.setInventoryVector(new InventoryVector(Security.randomBytes(32)));
message.setInventoryVector(new InventoryVector(security().randomBytes(32)));
repo.save(message);
messages = repo.findMessages(Plaintext.Status.DRAFT, contactA);

View File

@ -27,7 +27,7 @@ import java.util.List;
import static ch.dissem.bitmessage.utils.UnixTime.now;
import static org.junit.Assert.assertEquals;
public class JdbcNodeRegistryTest {
public class JdbcNodeRegistryTest extends TestBase {
private TestJdbcConfig config;
private NodeRegistry registry;

View File

@ -0,0 +1,38 @@
/*
* Copyright 2015 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.bitmessage.repository;
import ch.dissem.bitmessage.InternalContext;
import ch.dissem.bitmessage.ports.MultiThreadedPOWEngine;
import ch.dissem.bitmessage.security.bc.BouncySecurity;
import ch.dissem.bitmessage.utils.Singleton;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Created by chris on 20.07.15.
*/
public class TestBase {
static {
BouncySecurity security = new BouncySecurity();
Singleton.initialize(security);
InternalContext ctx = mock(InternalContext.class);
when(ctx.getProofOfWorkEngine()).thenReturn(new MultiThreadedPOWEngine());
security.setContext(ctx);
}
}

View File

@ -17,7 +17,8 @@
package ch.dissem.bitmessage.repository;
/**
* Created by chris on 02.06.15.
* JdbcConfig to be used for tests. Uses an in-memory database and adds a useful {@link #reset()} method resetting
* the database.
*/
public class TestJdbcConfig extends JdbcConfig {
public TestJdbcConfig() {

18
security-bc/build.gradle Normal file
View File

@ -0,0 +1,18 @@
uploadArchives {
repositories {
mavenDeployer {
pom.project {
name 'Jabit Spongy Security'
artifactId = 'jabit-security-spongy'
description 'The Security implementation using spongy castle (needed for Android)'
}
}
}
}
dependencies {
compile project(':domain')
compile 'org.bouncycastle:bcprov-jdk15on:1.52'
testCompile 'junit:junit:4.11'
testCompile 'org.mockito:mockito-core:1.10.19'
}

View File

@ -0,0 +1,153 @@
/*
* Copyright 2015 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.bitmessage.security.bc;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.ports.AbstractSecurity;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.BufferedBlockCipher;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.ec.CustomNamedCurves;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.paddings.PKCS7Padding;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECPoint;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.KeySpec;
import java.util.Arrays;
/**
* As Spongycastle can't be used on the Oracle JVM, and Bouncycastle doesn't work properly on Android (thanks, Google),
* this is the Bouncycastle implementation.
*/
public class BouncySecurity extends AbstractSecurity {
private static final X9ECParameters EC_CURVE_PARAMETERS = CustomNamedCurves.getByName("secp256k1");
static {
java.security.Security.addProvider(new BouncyCastleProvider());
}
public BouncySecurity() {
super("BC");
}
@Override
public byte[] crypt(boolean encrypt, byte[] data, byte[] key_e, byte[] initializationVector) {
BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()), new PKCS7Padding());
CipherParameters params = new ParametersWithIV(new KeyParameter(key_e), initializationVector);
cipher.init(encrypt, params);
byte[] buffer = new byte[cipher.getOutputSize(data.length)];
int length = cipher.processBytes(data, 0, data.length, buffer, 0);
try {
length += cipher.doFinal(buffer, length);
} catch (InvalidCipherTextException e) {
throw new IllegalArgumentException(e);
}
if (length < buffer.length) {
return Arrays.copyOfRange(buffer, 0, length);
}
return buffer;
}
@Override
public byte[] createPublicKey(byte[] privateKey) {
return EC_CURVE_PARAMETERS.getG().multiply(keyToBigInt(privateKey)).normalize().getEncoded(false);
}
private ECPoint keyToPoint(byte[] publicKey) {
BigInteger x = new BigInteger(1, Arrays.copyOfRange(publicKey, 1, 33));
BigInteger y = new BigInteger(1, Arrays.copyOfRange(publicKey, 33, 65));
return EC_CURVE_PARAMETERS.getCurve().createPoint(x, y);
}
@Override
public boolean isSignatureValid(byte[] data, byte[] signature, Pubkey pubkey) {
try {
ECParameterSpec spec = new ECParameterSpec(
EC_CURVE_PARAMETERS.getCurve(),
EC_CURVE_PARAMETERS.getG(),
EC_CURVE_PARAMETERS.getN(),
EC_CURVE_PARAMETERS.getH(),
EC_CURVE_PARAMETERS.getSeed()
);
ECPoint Q = keyToPoint(pubkey.getSigningKey());
KeySpec keySpec = new ECPublicKeySpec(Q, spec);
PublicKey publicKey = KeyFactory.getInstance("ECDSA", "BC").generatePublic(keySpec);
Signature sig = Signature.getInstance("ECDSA", "BC");
sig.initVerify(publicKey);
sig.update(data);
return sig.verify(signature);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public byte[] getSignature(byte[] data, PrivateKey privateKey) {
try {
ECParameterSpec spec = new ECParameterSpec(
EC_CURVE_PARAMETERS.getCurve(),
EC_CURVE_PARAMETERS.getG(),
EC_CURVE_PARAMETERS.getN(),
EC_CURVE_PARAMETERS.getH(),
EC_CURVE_PARAMETERS.getSeed()
);
BigInteger d = keyToBigInt(privateKey.getPrivateSigningKey());
KeySpec keySpec = new ECPrivateKeySpec(d, spec);
java.security.PrivateKey privKey = KeyFactory.getInstance("ECDSA", "BC").generatePrivate(keySpec);
Signature sig = Signature.getInstance("ECDSA", "BC");
sig.initSign(privKey);
sig.update(data);
return sig.sign();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public byte[] multiply(byte[] K, byte[] r) {
return keyToPoint(K).multiply(keyToBigInt(r)).normalize().getEncoded(false);
}
@Override
public byte[] createPoint(byte[] x, byte[] y) {
return EC_CURVE_PARAMETERS.getCurve().createPoint(
new BigInteger(1, x),
new BigInteger(1, y)
).getEncoded(false);
}
}

View File

@ -1,36 +1,25 @@
/*
* Copyright 2015 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.bitmessage.utils;
package ch.dissem.bitmessage.security;
import ch.dissem.bitmessage.InternalContext;
import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.payload.GenericPayload;
import ch.dissem.bitmessage.ports.MultiThreadedPOWEngine;
import ch.dissem.bitmessage.security.bc.BouncySecurity;
import ch.dissem.bitmessage.utils.Singleton;
import ch.dissem.bitmessage.utils.UnixTime;
import org.junit.Test;
import javax.xml.bind.DatatypeConverter;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.KeyPairGenerator;
import static ch.dissem.bitmessage.utils.UnixTime.DAY;
import static org.junit.Assert.assertArrayEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Created by chris on 10.04.15.
* Created by chris on 19.07.15.
*/
public class SecurityTest {
public static final byte[] TEST_VALUE = "teststring".getBytes();
@ -42,29 +31,39 @@ public class SecurityTest {
public static final byte[] TEST_RIPEMD160 = DatatypeConverter.parseHexBinary(""
+ "cd566972b5e50104011a92b59fa8e0b1234851ae");
private static BouncySecurity security;
public SecurityTest() {
security = new BouncySecurity();
Singleton.initialize(security);
InternalContext ctx = mock(InternalContext.class);
when(ctx.getProofOfWorkEngine()).thenReturn(new MultiThreadedPOWEngine());
security.setContext(ctx);
}
@Test
public void testRipemd160() {
assertArrayEquals(TEST_RIPEMD160, Security.ripemd160(TEST_VALUE));
assertArrayEquals(TEST_RIPEMD160, security.ripemd160(TEST_VALUE));
}
@Test
public void testSha1() {
assertArrayEquals(TEST_SHA1, Security.sha1(TEST_VALUE));
assertArrayEquals(TEST_SHA1, security.sha1(TEST_VALUE));
}
@Test
public void testSha512() {
assertArrayEquals(TEST_SHA512, Security.sha512(TEST_VALUE));
assertArrayEquals(TEST_SHA512, security.sha512(TEST_VALUE));
}
@Test
public void testChaining() {
assertArrayEquals(TEST_SHA512, Security.sha512("test".getBytes(), "string".getBytes()));
assertArrayEquals(TEST_SHA512, security.sha512("test".getBytes(), "string".getBytes()));
}
@Test
public void testDoubleHash() {
assertArrayEquals(Security.sha512(TEST_SHA512), Security.doubleSha512(TEST_VALUE));
assertArrayEquals(security.sha512(TEST_SHA512), security.doubleSha512(TEST_VALUE));
}
@Test(expected = IOException.class)
@ -75,7 +74,7 @@ public class SecurityTest {
.objectType(0)
.payload(GenericPayload.read(0, new ByteArrayInputStream(new byte[0]), 1, 0))
.build();
Security.checkProofOfWork(objectMessage, 1000, 1000);
security.checkProofOfWork(objectMessage, 1000, 1000);
}
@Test
@ -86,14 +85,7 @@ public class SecurityTest {
.objectType(0)
.payload(GenericPayload.read(0, new ByteArrayInputStream(new byte[0]), 1, 0))
.build();
Security.doProofOfWork(objectMessage, new MultiThreadedPOWEngine(), 1000, 1000);
Security.checkProofOfWork(objectMessage, 1000, 1000);
security.doProofOfWork(objectMessage, 1000, 1000);
security.checkProofOfWork(objectMessage, 1000, 1000);
}
@Test
public void testECIES() throws Exception {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("ECIES", "BC");
// kpg.initialize();
kpg.generateKeyPair();
}
}
}

17
security-sc/build.gradle Normal file
View File

@ -0,0 +1,17 @@
uploadArchives {
repositories {
mavenDeployer {
pom.project {
name 'Jabit Spongy Security'
artifactId = 'jabit-security-spongy'
description 'The Security implementation using spongy castle (needed for Android)'
}
}
}
}
dependencies {
compile project(':domain')
compile 'com.madgag.spongycastle:prov:1.52.0.0'
testCompile 'junit:junit:4.11'
}

View File

@ -0,0 +1,153 @@
/*
* Copyright 2015 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.bitmessage.security.sc;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.ports.AbstractSecurity;
import org.spongycastle.asn1.x9.X9ECParameters;
import org.spongycastle.crypto.BufferedBlockCipher;
import org.spongycastle.crypto.CipherParameters;
import org.spongycastle.crypto.InvalidCipherTextException;
import org.spongycastle.crypto.ec.CustomNamedCurves;
import org.spongycastle.crypto.engines.AESEngine;
import org.spongycastle.crypto.modes.CBCBlockCipher;
import org.spongycastle.crypto.paddings.PKCS7Padding;
import org.spongycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.spongycastle.crypto.params.KeyParameter;
import org.spongycastle.crypto.params.ParametersWithIV;
import org.spongycastle.jce.provider.BouncyCastleProvider;
import org.spongycastle.jce.spec.ECParameterSpec;
import org.spongycastle.jce.spec.ECPrivateKeySpec;
import org.spongycastle.jce.spec.ECPublicKeySpec;
import org.spongycastle.math.ec.ECPoint;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.KeySpec;
import java.util.Arrays;
/**
* As Spongycastle can't be used on the Oracle JVM, and Bouncycastle doesn't work properly on Android (thanks, Google),
* this is the Spongycastle implementation.
*/
public class SpongySecurity extends AbstractSecurity {
private static final X9ECParameters EC_CURVE_PARAMETERS = CustomNamedCurves.getByName("secp256k1");
static {
java.security.Security.addProvider(new BouncyCastleProvider());
}
public SpongySecurity() {
super("SC");
}
@Override
public byte[] crypt(boolean encrypt, byte[] data, byte[] key_e, byte[] initializationVector) {
BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()), new PKCS7Padding());
CipherParameters params = new ParametersWithIV(new KeyParameter(key_e), initializationVector);
cipher.init(encrypt, params);
byte[] buffer = new byte[cipher.getOutputSize(data.length)];
int length = cipher.processBytes(data, 0, data.length, buffer, 0);
try {
length += cipher.doFinal(buffer, length);
} catch (InvalidCipherTextException e) {
throw new IllegalArgumentException(e);
}
if (length < buffer.length) {
return Arrays.copyOfRange(buffer, 0, length);
}
return buffer;
}
@Override
public byte[] createPublicKey(byte[] privateKey) {
return EC_CURVE_PARAMETERS.getG().multiply(keyToBigInt(privateKey)).normalize().getEncoded(false);
}
private ECPoint keyToPoint(byte[] publicKey) {
BigInteger x = new BigInteger(1, Arrays.copyOfRange(publicKey, 1, 33));
BigInteger y = new BigInteger(1, Arrays.copyOfRange(publicKey, 33, 65));
return EC_CURVE_PARAMETERS.getCurve().createPoint(x, y);
}
@Override
public boolean isSignatureValid(byte[] data, byte[] signature, Pubkey pubkey) {
try {
ECParameterSpec spec = new ECParameterSpec(
EC_CURVE_PARAMETERS.getCurve(),
EC_CURVE_PARAMETERS.getG(),
EC_CURVE_PARAMETERS.getN(),
EC_CURVE_PARAMETERS.getH(),
EC_CURVE_PARAMETERS.getSeed()
);
ECPoint Q = keyToPoint(pubkey.getSigningKey());
KeySpec keySpec = new ECPublicKeySpec(Q, spec);
PublicKey publicKey = KeyFactory.getInstance("ECDSA", "SC").generatePublic(keySpec);
Signature sig = Signature.getInstance("ECDSA", "SC");
sig.initVerify(publicKey);
sig.update(data);
return sig.verify(signature);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public byte[] getSignature(byte[] data, PrivateKey privateKey) {
try {
ECParameterSpec spec = new ECParameterSpec(
EC_CURVE_PARAMETERS.getCurve(),
EC_CURVE_PARAMETERS.getG(),
EC_CURVE_PARAMETERS.getN(),
EC_CURVE_PARAMETERS.getH(),
EC_CURVE_PARAMETERS.getSeed()
);
BigInteger d = keyToBigInt(privateKey.getPrivateSigningKey());
KeySpec keySpec = new ECPrivateKeySpec(d, spec);
java.security.PrivateKey privKey = KeyFactory.getInstance("ECDSA", "SC").generatePrivate(keySpec);
Signature sig = Signature.getInstance("ECDSA", "SC");
sig.initSign(privKey);
sig.update(data);
return sig.sign();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public byte[] multiply(byte[] K, byte[] r) {
return keyToPoint(K).multiply(keyToBigInt(r)).normalize().getEncoded(false);
}
@Override
public byte[] createPoint(byte[] x, byte[] y) {
return EC_CURVE_PARAMETERS.getCurve().createPoint(
new BigInteger(1, x),
new BigInteger(1, y)
).getEncoded(false);
}
}

View File

@ -9,3 +9,7 @@ include 'repositories'
include 'demo'
include 'wif'
include 'security-sc'
include 'security-bc'

View File

@ -15,4 +15,5 @@ dependencies {
compile 'org.ini4j:ini4j:0.5.4'
testCompile 'junit:junit:4.11'
testCompile 'org.mockito:mockito-core:1.10.19'
testCompile project(':security-bc')
}

View File

@ -19,7 +19,6 @@ package ch.dissem.bitmessage.wif;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.utils.Base58;
import ch.dissem.bitmessage.utils.Security;
import org.ini4j.Ini;
import org.ini4j.Profile;
@ -27,6 +26,7 @@ import java.io.*;
import java.util.Collection;
import static ch.dissem.bitmessage.entity.valueobject.PrivateKey.PRIVATE_KEY_SIZE;
import static ch.dissem.bitmessage.utils.Singleton.security;
/**
* @author Christian Basler
@ -73,7 +73,7 @@ public class WifExporter {
byte[] result = new byte[37];
result[0] = (byte) 0x80;
System.arraycopy(privateKey, 0, result, 1, PRIVATE_KEY_SIZE);
byte[] hash = Security.doubleSha256(result, PRIVATE_KEY_SIZE + 1);
byte[] hash = security().doubleSha256(result, PRIVATE_KEY_SIZE + 1);
System.arraycopy(hash, 0, result, PRIVATE_KEY_SIZE + 1, 4);
return Base58.encode(result);
}

View File

@ -21,7 +21,6 @@ import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.utils.Base58;
import ch.dissem.bitmessage.utils.Security;
import org.ini4j.Ini;
import org.ini4j.Profile;
import org.slf4j.Logger;
@ -34,6 +33,8 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
import static ch.dissem.bitmessage.utils.Singleton.security;
/**
* @author Christian Basler
*/
@ -84,7 +85,7 @@ public class WifImporter {
if (bytes.length != 37)
throw new IOException("Unknown format: 37 bytes expected, but secret " + walletImportFormat + " was " + bytes.length + " long");
byte[] hash = Security.doubleSha256(bytes, 33);
byte[] hash = security().doubleSha256(bytes, 33);
for (int i = 0; i < 4; i++) {
if (hash[i] != bytes[33 + i]) throw new IOException("Hash check failed for secret " + walletImportFormat);
}

View File

@ -18,6 +18,7 @@ package ch.dissem.bitmessage.wif;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.ports.*;
import ch.dissem.bitmessage.security.bc.BouncySecurity;
import org.junit.Before;
import org.junit.Test;
@ -34,6 +35,7 @@ public class WifExporterTest {
@Before
public void setUp() throws Exception {
ctx = new BitmessageContext.Builder()
.security(new BouncySecurity())
.networkHandler(mock(NetworkHandler.class))
.inventory(mock(Inventory.class))
.messageRepo(mock(MessageRepository.class))

View File

@ -19,6 +19,7 @@ package ch.dissem.bitmessage.wif;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.ports.*;
import ch.dissem.bitmessage.security.bc.BouncySecurity;
import org.junit.Before;
import org.junit.Test;
@ -37,6 +38,7 @@ public class WifImporterTest {
@Before
public void setUp() throws Exception {
ctx = new BitmessageContext.Builder()
.security(new BouncySecurity())
.networkHandler(mock(NetworkHandler.class))
.inventory(mock(Inventory.class))
.messageRepo(mock(MessageRepository.class))