Decryption works now!

(well, for v4 pubkeys at least)
This commit is contained in:
Christian Basler 2015-05-09 17:27:45 +02:00
parent d0250444d5
commit f23f432f07
18 changed files with 467 additions and 142 deletions

View File

@ -17,11 +17,9 @@
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.AccessCounter;
import ch.dissem.bitmessage.utils.Base58;
import ch.dissem.bitmessage.utils.Encode;
import ch.dissem.bitmessage.utils.Security;
import ch.dissem.bitmessage.utils.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@ -36,9 +34,14 @@ import static ch.dissem.bitmessage.utils.Decode.varInt;
* holding private keys.
*/
public class BitmessageAddress {
private long version;
private long stream;
private byte[] ripe;
private final long version;
private final long stream;
private final byte[] ripe;
private final byte[] tag;
/**
* Used for V4 address encryption. It's easier to just create it regardless of address version.
*/
private final byte[] privateEncryptionKey;
private String address;
@ -47,45 +50,55 @@ public class BitmessageAddress {
private String alias;
public BitmessageAddress(PrivateKey privateKey) {
this.privateKey = privateKey;
this.pubkey = privateKey.getPubkey();
this.ripe = pubkey.getRipe();
this.address = generateAddress();
}
public BitmessageAddress(String address) {
private BitmessageAddress(long version, long stream, byte[] ripe) {
try {
byte[] bytes = Base58.decode(address.substring(3));
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
AccessCounter counter = new AccessCounter();
this.version = varInt(in, counter);
this.stream = varInt(in, counter);
this.ripe = bytes(in, bytes.length - counter.length() - 4);
testChecksum(bytes(in, 4), bytes);
this.address = generateAddress();
this.version = version;
this.stream = stream;
this.ripe = ripe;
ByteArrayOutputStream os = new ByteArrayOutputStream();
Encode.varInt(version, os);
Encode.varInt(stream, os);
// for the tag, the checksum has to be created with 0x00 padding
byte[] checksum = Security.doubleSha512(os.toByteArray(), ripe);
this.tag = Arrays.copyOfRange(checksum, 32, 64);
this.privateEncryptionKey = Arrays.copyOfRange(checksum, 0, 32);
// but for the address and its checksum they need to be stripped
os.write(Bytes.stripLeadingZeros(ripe));
checksum = Security.doubleSha512(os.toByteArray(), ripe);
os.write(checksum, 0, 4);
this.address = "BM-" + Base58.encode(os.toByteArray());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void testChecksum(byte[] expected, byte[] address) {
byte[] checksum = Security.doubleSha512(address, address.length - 4);
for (int i = 0; i < 4; i++) {
if (expected[i] != checksum[i]) throw new IllegalArgumentException("Checksum of address failed");
}
public BitmessageAddress(PrivateKey privateKey) {
this(privateKey.getPubkey().getVersion(), privateKey.getPubkey().getStream(), privateKey.getPubkey().getRipe());
this.privateKey = privateKey;
this.pubkey = privateKey.getPubkey();
}
private String generateAddress() {
public BitmessageAddress(String address) {
try {
ByteArrayOutputStream os = new ByteArrayOutputStream();
Encode.varInt(version, os);
Encode.varInt(stream, os);
os.write(ripe);
this.address = address;
byte[] bytes = Base58.decode(address.substring(3));
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
AccessCounter counter = new AccessCounter();
this.version = varInt(in, counter);
this.stream = varInt(in, counter);
this.ripe = Bytes.expand(bytes(in, bytes.length - counter.length() - 4), 20);
byte[] checksum = Security.doubleSha512(os.toByteArray());
os.write(checksum, 0, 4);
return "BM-" + Base58.encode(os.toByteArray());
// test checksum
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");
}
checksum = Security.doubleSha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe);
this.tag = Arrays.copyOfRange(checksum, 32, 64);
this.privateEncryptionKey = Arrays.copyOfRange(checksum, 0, 32);
} catch (IOException e) {
throw new RuntimeException(e);
}
@ -104,8 +117,18 @@ public class BitmessageAddress {
}
public void setPubkey(Pubkey pubkey) {
if (pubkey instanceof V4Pubkey) {
try {
V4Pubkey v4 = (V4Pubkey) pubkey;
if (!Arrays.equals(tag, v4.getTag()))
throw new IllegalArgumentException("Pubkey has incompatible tag");
v4.decrypt(privateEncryptionKey);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (!Arrays.equals(ripe, pubkey.getRipe()))
throw new IllegalArgumentException("Pubkey has incompatible RIPE");
throw new IllegalArgumentException("Pubkey has incompatible ripe");
this.pubkey = pubkey;
}
@ -133,4 +156,8 @@ public class BitmessageAddress {
public byte[] getRipe() {
return ripe;
}
public byte[] getTag() {
return tag;
}
}

View File

@ -0,0 +1,203 @@
/*
* 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.entity.payload;
import ch.dissem.bitmessage.entity.Streamable;
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.jce.interfaces.ECPublicKey;
import org.bouncycastle.math.ec.ECPoint;
import java.io.*;
import java.math.BigInteger;
import java.util.Arrays;
/**
* Created by chris on 09.04.15.
*/
public class CryptoBox implements Streamable {
private final byte[] initializationVector;
private final int curveType;
private final byte[] xComponent;
private final byte[] yComponent;
private final byte[] mac;
private byte[] encrypted;
public CryptoBox(Streamable data, byte[] encryptionKey) {
curveType = 0x02CA;
// 1. The destination public key is called K.
ECPublicKey K = Security.getPublicKey(encryptionKey);
// 2. Generate 16 random bytes using a secure random number generator. Call them IV.
initializationVector = Security.randomBytes(16);
// 3. Generate a new random EC key pair with private key called r and public key called R.
// TODO
BigInteger r = null;
// 4. Do an EC point multiply with public key K and private key r. This gives you public key P.
ECPoint P = K.getQ().multiply(r).normalize();
xComponent = Bytes.stripLeadingZeros(P.getXCoord().getEncoded());
yComponent = Bytes.stripLeadingZeros(P.getYCoord().getEncoded());
// 5. Use the X component of public key P and calculate the SHA512 hash H.
byte[] H = Security.sha512(xComponent);
// 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, H.length - 32, 32);
// 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 = null; // TODO
// 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 = null; // TODO
// The resulting data is: IV + R + cipher text + MAC
}
private CryptoBox(Builder builder) {
initializationVector = builder.initializationVector;
curveType = builder.curveType;
xComponent = builder.xComponent;
yComponent = builder.yComponent;
encrypted = builder.encrypted;
mac = builder.mac;
}
public static CryptoBox read(InputStream stream, int length) throws IOException {
AccessCounter counter = new AccessCounter();
return new Builder()
.IV(Decode.bytes(stream, 16, counter))
.curveType(Decode.uint16(stream, counter))
.X(Decode.shortVarBytes(stream, counter))
.Y(Decode.shortVarBytes(stream, counter))
.encrypted(Decode.bytes(stream, length - counter.length() - 32))
.MAC(Decode.bytes(stream, 32))
.build();
}
/**
* @see <a href='https://bitmessage.org/wiki/Encryption#Decryption'>https://bitmessage.org/wiki/Encryption#Decryption</a>
*/
public InputStream decrypt(byte[] privateKey) {
// 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.
ECPublicKey R = Security.getPublicKey(xComponent, yComponent);
ECPoint P = R.getQ().multiply(K).normalize();
// 3. Use the X component of public key P and calculate the SHA512 hash H.
byte[] sha512key = Security.sha512(Bytes.expand(P.getXCoord().toBigInteger().toByteArray(), 32));
// 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(sha512key, 0, 32);
byte[] key_m = Arrays.copyOfRange(sha512key, 32, 64);
// 5. Calculate MAC' with HMACSHA256, using key_m as salt and IV + R + cipher text as data.
ByteArrayOutputStream macData = new ByteArrayOutputStream();
try {
writeWithoutMAC(macData);
} catch (IOException e) {
throw new RuntimeException(e);
}
// 6. Compare MAC with MAC'. If not equal, decryption will fail.
if (!Arrays.equals(mac, Security.mac(key_m, macData.toByteArray()))) {
throw new RuntimeException("Invalid MAC while decrypting");
}
// 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.
BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()), new PKCS7Padding());
CipherParameters params = new ParametersWithIV(new KeyParameter(key_e), initializationVector);
cipher.init(false, params);
byte[] buffer = new byte[cipher.getOutputSize(encrypted.length)];
int length = cipher.processBytes(encrypted, 0, encrypted.length, buffer, 0);
try {
length += cipher.doFinal(buffer, length);
} catch (InvalidCipherTextException e) {
throw new IllegalArgumentException(e);
}
return new ByteArrayInputStream(buffer, 0, length);
}
private void writeWithoutMAC(OutputStream stream) throws IOException {
stream.write(initializationVector);
Encode.int16(curveType, stream);
Encode.int16(xComponent.length, stream);
stream.write(xComponent);
Encode.int16(yComponent.length, stream);
stream.write(yComponent);
stream.write(encrypted);
}
@Override
public void write(OutputStream stream) throws IOException {
writeWithoutMAC(stream);
stream.write(mac);
}
public static final class Builder {
private byte[] initializationVector;
private int curveType;
private byte[] xComponent;
private byte[] yComponent;
private byte[] encrypted;
private byte[] mac;
public Builder IV(byte[] initializationVector) {
this.initializationVector = initializationVector;
return this;
}
public Builder curveType(int curveType) {
if (curveType != 0x2CA) System.out.println("Unexpected curve type " + curveType);
this.curveType = curveType;
return this;
}
public Builder X(byte[] xComponent) {
this.xComponent = xComponent;
return this;
}
public Builder Y(byte[] yComponent) {
this.yComponent = yComponent;
return this;
}
private Builder encrypted(byte[] encrypted) {
this.encrypted = encrypted;
return this;
}
public Builder MAC(byte[] mac) {
this.mac = mac;
return this;
}
public CryptoBox build() {
return new CryptoBox(this);
}
}
}

View File

@ -30,7 +30,7 @@ public class GenericPayload extends ObjectPayload {
private long stream;
private byte[] data;
private GenericPayload(long stream, byte[] data) {
public GenericPayload(long stream, byte[] data) {
this.stream = stream;
this.data = data;
}

View File

@ -19,6 +19,7 @@ package ch.dissem.bitmessage.entity.payload;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.utils.Bytes;
import ch.dissem.bitmessage.utils.Decode;
import ch.dissem.bitmessage.utils.Security;
import java.io.IOException;
import java.io.InputStream;
@ -37,7 +38,7 @@ public class GetPubkey extends ObjectPayload {
if (address.getVersion() < 4)
this.ripe = address.getRipe();
else
this.tag = ((V4Pubkey) address.getPubkey()).getTag();
this.tag = address.getTag();
}
private GetPubkey(long stream, long version, byte[] ripeOrTag) {

View File

@ -16,9 +16,6 @@
package ch.dissem.bitmessage.entity.payload;
import ch.dissem.bitmessage.utils.Decode;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@ -28,21 +25,22 @@ import java.io.OutputStream;
*/
public class Msg extends ObjectPayload {
private long stream;
private byte[] encrypted;
private UnencryptedMessage unencrypted;
private CryptoBox encrypted;
private UnencryptedMessage decrypted;
private Msg(long stream, byte[] encrypted) {
private Msg(long stream, CryptoBox encrypted) {
this.stream = stream;
this.encrypted = encrypted;
}
public Msg(UnencryptedMessage unencrypted) {
public Msg(UnencryptedMessage unencrypted, Pubkey publicKey) {
this.stream = unencrypted.getStream();
this.unencrypted = unencrypted;
this.decrypted = unencrypted;
this.encrypted = new CryptoBox(unencrypted, publicKey.getEncryptionKey());
}
public static Msg read(InputStream is, long stream, int length) throws IOException {
return new Msg(stream, Decode.bytes(is, length));
public static Msg read(InputStream in, long stream, int length) throws IOException {
return new Msg(stream, CryptoBox.read(in, length));
}
@Override
@ -57,33 +55,30 @@ public class Msg extends ObjectPayload {
@Override
public boolean isSigned() {
return unencrypted != null;
return decrypted != null;
}
@Override
public void writeBytesToSign(OutputStream out) throws IOException {
unencrypted.write(out, false);
decrypted.write(out, false);
}
@Override
public byte[] getSignature() {
return unencrypted.getSignature();
return decrypted.getSignature();
}
@Override
public void setSignature(byte[] signature) {
unencrypted.setSignature(signature);
decrypted.setSignature(signature);
}
public byte[] getEncrypted() {
if (encrypted == null) {
// TODO encrypt
}
return encrypted;
public void decrypt(byte[] privateKey) throws IOException {
decrypted = UnencryptedMessage.read(encrypted.decrypt(privateKey));
}
@Override
public void write(OutputStream stream) throws IOException {
stream.write(getEncrypted());
public void write(OutputStream out) throws IOException {
encrypted.write(out);
}
}

View File

@ -36,7 +36,7 @@ public abstract class Pubkey extends ObjectPayload {
public abstract byte[] getEncryptionKey();
public byte[] getRipe() {
return Bytes.stripLeadingZeros(ripemd160(sha512(getSigningKey(), getEncryptionKey())));
return ripemd160(sha512(getSigningKey(), getEncryptionKey()));
}
protected byte[] add0x04(byte[] key){

View File

@ -16,15 +16,18 @@
package ch.dissem.bitmessage.entity.payload;
import ch.dissem.bitmessage.entity.Streamable;
import ch.dissem.bitmessage.utils.Decode;
import ch.dissem.bitmessage.utils.Encode;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* The unencrypted message to be sent by 'msg' or 'broadcast'.
*/
public class UnencryptedMessage {
public class UnencryptedMessage implements Streamable {
private final long addressVersion;
private final long stream;
private final int behaviorBitfield;
@ -49,6 +52,21 @@ public class UnencryptedMessage {
signature = builder.signature;
}
public static UnencryptedMessage read(InputStream is) throws IOException {
return new Builder()
.addressVersion(Decode.varInt(is))
.stream(Decode.varInt(is))
.behaviorBitfield(Decode.int32(is))
.publicSigningKey(Decode.bytes(is, 64))
.publicEncryptionKey(Decode.bytes(is, 64))
.nonceTrialsPerByte(Decode.varInt(is))
.extraBytes(Decode.varInt(is))
.encoding(Decode.varInt(is))
.message(Decode.varBytes(is))
.signature(Decode.varBytes(is))
.build();
}
public long getStream() {
return stream;
}
@ -61,23 +79,28 @@ public class UnencryptedMessage {
this.signature = signature;
}
public void write(OutputStream os, boolean includeSignature) throws IOException {
Encode.varInt(addressVersion, os);
Encode.varInt(stream, os);
Encode.int32(behaviorBitfield, os);
os.write(publicSigningKey);
os.write(publicEncryptionKey);
Encode.varInt(nonceTrialsPerByte, os);
Encode.varInt(extraBytes, os);
Encode.varInt(encoding, os);
Encode.varInt(message.length, os);
os.write(message);
public void write(OutputStream out, boolean includeSignature) throws IOException {
Encode.varInt(addressVersion, out);
Encode.varInt(stream, out);
Encode.int32(behaviorBitfield, out);
out.write(publicSigningKey);
out.write(publicEncryptionKey);
Encode.varInt(nonceTrialsPerByte, out);
Encode.varInt(extraBytes, out);
Encode.varInt(encoding, out);
Encode.varInt(message.length, out);
out.write(message);
if (includeSignature) {
Encode.varInt(signature.length, os);
os.write(signature);
Encode.varInt(signature.length, out);
out.write(signature);
}
}
@Override
public void write(OutputStream out) throws IOException {
write(out, true);
}
public static final class Builder {
private long addressVersion;
private long stream;

View File

@ -18,6 +18,7 @@ package ch.dissem.bitmessage.entity.payload;
import ch.dissem.bitmessage.utils.Decode;
import ch.dissem.bitmessage.utils.Encode;
import ch.dissem.bitmessage.utils.Security;
import java.io.IOException;
import java.io.InputStream;
@ -36,23 +37,21 @@ public class V3Pubkey extends V2Pubkey {
behaviorBitfield = builder.behaviorBitfield;
publicSigningKey = add0x04(builder.publicSigningKey);
publicEncryptionKey = add0x04(builder.publicEncryptionKey);
nonceTrialsPerByte = builder.nonceTrialsPerByte;
extraBytes = builder.extraBytes;
signature = builder.signature;
}
public static V3Pubkey read(InputStream is, long stream) throws IOException {
V3Pubkey.Builder v3 = new V3Pubkey.Builder()
return new V3Pubkey.Builder()
.stream(stream)
.behaviorBitfield((int) Decode.uint32(is))
.behaviorBitfield(Decode.int32(is))
.publicSigningKey(Decode.bytes(is, 64))
.publicEncryptionKey(Decode.bytes(is, 64))
.nonceTrialsPerByte(Decode.varInt(is))
.extraBytes(Decode.varInt(is));
int sigLength = (int) Decode.varInt(is);
v3.signature(Decode.bytes(is, sigLength));
return v3.build();
.extraBytes(Decode.varInt(is))
.signature(Decode.varBytes(is))
.build();
}
@Override
@ -92,7 +91,6 @@ public class V3Pubkey extends V2Pubkey {
private int behaviorBitfield;
private byte[] publicSigningKey;
private byte[] publicEncryptionKey;
private long nonceTrialsPerByte;
private long extraBytes;
private byte[] signature;
@ -136,7 +134,6 @@ public class V3Pubkey extends V2Pubkey {
}
public V3Pubkey build() {
// TODO: check signature
return new V3Pubkey(this);
}
}

View File

@ -31,30 +31,36 @@ import java.io.OutputStream;
public class V4Pubkey extends Pubkey {
private long stream;
private byte[] tag;
private byte[] encrypted;
private CryptoBox encrypted;
private V3Pubkey decrypted;
private V4Pubkey(long stream, byte[] tag, byte[] encrypted) {
private V4Pubkey(long stream, byte[] tag, CryptoBox encrypted) {
this.stream = stream;
this.tag = tag;
this.encrypted = encrypted;
}
public V4Pubkey(V3Pubkey decrypted) {
public V4Pubkey(byte[] tag, V3Pubkey decrypted) {
this.stream = decrypted.stream;
// TODO: this.tag = new BitmessageAddress(this).doubleHash
this.tag = tag;
this.decrypted = decrypted;
// TODO: this.encrypted
this.encrypted = new CryptoBox(decrypted, null);
}
public static V4Pubkey read(InputStream stream, long streamNumber, int length) throws IOException {
return new V4Pubkey(streamNumber, Decode.bytes(stream, 32), Decode.bytes(stream, length - 32));
public static V4Pubkey read(InputStream in, long stream, int length) throws IOException {
return new V4Pubkey(stream,
Decode.bytes(in, 32),
CryptoBox.read(in, length - 32));
}
public void decrypt(byte[] privateKey) throws IOException {
decrypted = V3Pubkey.read(encrypted.decrypt(privateKey), stream);
}
@Override
public void write(OutputStream stream) throws IOException {
stream.write(tag);
stream.write(encrypted);
encrypted.write(stream);
}
@Override

View File

@ -56,10 +56,10 @@ public class Factory {
public static Pubkey createPubkey(long version, long stream, byte[] publicSigningKey, byte[] publicEncryptionKey,
long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) {
if (publicSigningKey.length != 64)
if (publicSigningKey.length != 64 && publicSigningKey.length != 65)
throw new IllegalArgumentException("64 bytes signing key expected, but it was "
+ publicSigningKey.length + " bytes long.");
if (publicEncryptionKey.length != 64)
if (publicEncryptionKey.length != 64 && publicEncryptionKey.length != 65)
throw new IllegalArgumentException("64 bytes encryption key expected, but it was "
+ publicEncryptionKey.length + " bytes long.");
@ -82,6 +82,7 @@ public class Factory {
.build();
case 4:
return new V4Pubkey(
null, // FIXME: calculate tag
new V3Pubkey.Builder()
.stream(stream)
.publicSigningKey(publicSigningKey)

View File

@ -17,6 +17,7 @@
package ch.dissem.bitmessage.factory;
import ch.dissem.bitmessage.entity.*;
import ch.dissem.bitmessage.entity.payload.GenericPayload;
import ch.dissem.bitmessage.entity.payload.ObjectPayload;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
@ -79,22 +80,30 @@ class V3MessageFactory {
}
}
public static ObjectMessage readObject(InputStream stream, int length) throws IOException {
public static ObjectMessage readObject(InputStream in, int length) throws IOException {
AccessCounter counter = new AccessCounter();
byte nonce[] = Decode.bytes(stream, 8, counter);
long expiresTime = Decode.int64(stream, counter);
long objectType = Decode.uint32(stream, counter);
long version = Decode.varInt(stream, counter);
long streamNumber = Decode.varInt(stream, counter);
byte nonce[] = Decode.bytes(in, 8, counter);
long expiresTime = Decode.int64(in, counter);
long objectType = Decode.uint32(in, counter);
long version = Decode.varInt(in, counter);
long stream = Decode.varInt(in, counter);
ObjectPayload payload = Factory.getObjectPayload(objectType, version, streamNumber, stream, length - counter.length());
byte[] data = Decode.bytes(in, length - counter.length());
ObjectPayload payload;
try {
ByteArrayInputStream dataStream = new ByteArrayInputStream(data);
payload = Factory.getObjectPayload(objectType, version, stream, dataStream, data.length);
} catch (IOException e) {
LOG.trace("Could not parse object payload - using generic payload instead", e);
payload = new GenericPayload(stream, data);
}
return new ObjectMessage.Builder()
.nonce(nonce)
.expiresTime(expiresTime)
.objectType(objectType)
.version(version)
.stream(streamNumber)
.stream(stream)
.payload(payload)
.build();
}

View File

@ -27,6 +27,21 @@ import static ch.dissem.bitmessage.utils.AccessCounter.inc;
* https://bitmessage.org/wiki/Protocol_specification#Common_structures
*/
public class Decode {
public static byte[] shortVarBytes(InputStream stream, AccessCounter counter) throws IOException {
int length = uint16(stream, counter);
return bytes(stream, length, counter);
}
public static byte[] varBytes(InputStream stream) throws IOException {
int length = (int) varInt(stream, null);
return bytes(stream, length, null);
}
public static byte[] varBytes(InputStream stream, AccessCounter counter) throws IOException {
int length = (int) varInt(stream, counter);
return bytes(stream, length, counter);
}
public static byte[] bytes(InputStream stream, int count) throws IOException {
return bytes(stream, count, null);
}
@ -97,6 +112,11 @@ public class Decode {
}
public static int int32(InputStream stream) throws IOException {
return int32(stream, null);
}
public static int int32(InputStream stream, AccessCounter counter) throws IOException {
inc(counter, 4);
return ByteBuffer.wrap(bytes(stream, 4)).getInt();
}

View File

@ -20,19 +20,23 @@ import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.payload.Pubkey;
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.crypto.params.ECDomainParameters;
import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.ECPointUtil;
import org.bouncycastle.jce.interfaces.ECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
import org.bouncycastle.util.encoders.Hex;
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.interfaces.ECPublicKey;
import java.security.spec.*;
import java.util.Arrays;
/**
* Provides some methods to help with hashing and encryption.
@ -41,7 +45,14 @@ 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 ECGenParameterSpec EC_PARAMETERS = new ECGenParameterSpec("secp256k1");
private static final String EC_CURVE_NAME = "secp256k1";
private static final X9ECParameters EC_CURVE_PARAMETERS = CustomNamedCurves.getByName(EC_CURVE_NAME);
private static final ECDomainParameters EC_DOMAIN_PARAMETERS = new ECDomainParameters(
EC_CURVE_PARAMETERS.getCurve(),
EC_CURVE_PARAMETERS.getG(),
EC_CURVE_PARAMETERS.getN(),
EC_CURVE_PARAMETERS.getH()
);
static {
java.security.Security.addProvider(new BouncyCastleProvider());
@ -138,50 +149,75 @@ public class Security {
}
}
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);
}
}
public static Pubkey createPubkey(long version, long stream, byte[] privateSigningKey, byte[] privateEncryptionKey,
long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) {
// ECPublicKeySpec pubKey = new ECPublicKeySpec(
// ECPointUtil.decodePoint(curve, Hex.decode("025b6dc53bc61a2548ffb0f671472de6c9521a9d2d2534e65abfcbd5fe0c70")), // Q
// EC_PARAMETERS);
// byte[] publicSigningKey = EC_PARAMETERS.getG().multiply(keyToBigInt(privateSigningKey)).getEncoded(false);
// byte[] publicEncryptionKey = EC_PARAMETERS.getG().multiply(keyToBigInt(privateEncryptionKey)).getEncoded(false);
// return Factory.createPubkey(version, stream, // publicSigningKey, publicEncryptionKey,
// Bytes.subArray(publicSigningKey, 1, publicSigningKey.length - 1),
// Bytes.subArray(publicEncryptionKey, 1, publicEncryptionKey.length - 1),
// nonceTrialsPerByte, extraBytes, features);
return null;
return Factory.createPubkey(version, stream,
createPublicKey(privateSigningKey),
createPublicKey(privateEncryptionKey),
nonceTrialsPerByte, extraBytes, features);
}
private static byte[] createPublicKey(byte[] privateKey) {
// ECParameterSpec spec = new ECNamedCurveSpec(ECNamedCurveTable.getParameterSpec("prime239v1"));
// ECPrivateKeySpec priKey = new ECPrivateKeySpec(
// new BigInteger("876300101507107567501066130761671078357010671067781776716671676178726717"), // d
// spec);
// ECPublicKeySpec pubKey = new ECPublicKeySpec(
// ECPointUtil.decodePoint(
// spec.getCurve(),
// Hex.decode("025b6dc53bc61a2548ffb0f671472de6c9521a9d2d2534e65abfcbd5fe0c70")), // Q
// spec);
return null;
return EC_DOMAIN_PARAMETERS.getG().multiply(keyToBigInt(privateKey)).getEncoded(false);
}
private static BigInteger keyToBigInt(byte[] key) {
return new BigInteger(1, key);
public static BigInteger keyToBigInt(byte[] privateKey) {
return new BigInteger(1, privateKey);
}
private static ECPoint keyToPoint(byte[] publicKey) {
BigInteger x = new BigInteger(Arrays.copyOfRange(publicKey, 1, 33));
BigInteger y = new BigInteger(Arrays.copyOfRange(publicKey, 33, 65));
return new ECPoint(x, y);
}
public static boolean isSignatureValid(byte[] bytesToSign, byte[] signature, Pubkey pubkey) {
// ECPoint W = EC_CURVE.getCurve().decodePoint(pubkey.getSigningKey()); // TODO: probably this needs 0x04 added
ECPoint W = keyToPoint(pubkey.getSigningKey());
try {
ECParameterSpec param = null;
// KeySpec keySpec = new ECPublicKeySpec(W,param);;
// PublicKey publicKey = KeyFactory.getInstance("ECDSA", "BC").generatePublic(keySpec);
KeySpec keySpec = new ECPublicKeySpec(W, param);
PublicKey publicKey = KeyFactory.getInstance("ECDSA", "BC").generatePublic(keySpec);
Signature sig = Signature.getInstance("ECDSA", "BC");
// sig.initVerify(publicKey);
sig.initVerify(publicKey);
sig.update(bytesToSign);
return sig.verify(signature);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static ECPublicKey getPublicKey(byte[] publicKey) {
if (publicKey[0] != 0x04) throw new IllegalArgumentException("Public key starting with 0x04 expected");
return getPublicKey(
Arrays.copyOfRange(publicKey, 1, 33),
Arrays.copyOfRange(publicKey, 33, 65)
);
}
public static ECPublicKey getPublicKey(byte[] X, byte[] Y) {
try {
ECPoint w = new ECPoint(keyToBigInt(X), keyToBigInt(Y));
EllipticCurve curve = EC5Util.convertCurve(EC_DOMAIN_PARAMETERS.getCurve(), EC_CURVE_PARAMETERS.getSeed());
ECParameterSpec params = EC5Util.convertSpec(curve, ECNamedCurveTable.getParameterSpec(EC_CURVE_NAME));
ECPublicKeySpec keySpec = new ECPublicKeySpec(w, params);
return (ECPublicKey) getKeyFactory().generatePublic(keySpec);
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
private static KeyFactory getKeyFactory() throws NoSuchProviderException, NoSuchAlgorithmException {
return KeyFactory.getInstance("EC", "BC");
}
}

View File

@ -18,6 +18,7 @@ package ch.dissem.bitmessage.entity;
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.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.utils.*;
import org.junit.Test;
@ -67,22 +68,22 @@ public class BitmessageAddressTest {
@Test
public void testV3PubkeyImport() throws IOException {
BitmessageAddress address = new BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ");
assertArrayEquals(Bytes.fromHex("7402be6e76c3cb87caa946d0c003a3d4d8e1d5"), address.getRipe());
assertArrayEquals(Bytes.fromHex("007402be6e76c3cb87caa946d0c003a3d4d8e1d5"), address.getRipe());
ObjectMessage object = TestUtils.loadObjectMessage(3, "V3Pubkey.payload");
Pubkey pubkey = (Pubkey) object.getPayload();
address.setPubkey(pubkey);
assertArrayEquals(Bytes.fromHex("7402be6e76c3cb87caa946d0c003a3d4d8e1d5"), pubkey.getRipe());
assertArrayEquals(Bytes.fromHex("007402be6e76c3cb87caa946d0c003a3d4d8e1d5"), pubkey.getRipe());
}
@Test
public void testV4PubkeyImport() throws IOException {
// TODO
// ObjectMessage object = TestUtils.loadObjectMessage(3, "V4Pubkey.payload");
// Pubkey pubkey = (Pubkey) object.getPayload();
// BitmessageAddress address = new BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ");
// address.setPubkey(pubkey);
ObjectMessage object = TestUtils.loadObjectMessage(4, "V4Pubkey.payload");
V4Pubkey pubkey = (V4Pubkey) object.getPayload();
BitmessageAddress address = new BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h");
address.setPubkey(pubkey);
}
@Test

View File

@ -28,7 +28,6 @@ import java.io.InputStream;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* Created by chris on 28.04.15.
@ -60,15 +59,22 @@ public class SerializationTest {
public void ensureV1MsgIsDeserializedAndSerializedCorrectly() throws IOException {
doTest("V1Msg.payload", 1, Msg.class);
}
@Test
public void ensureV4BroadcastIsDeserializedAndSerializedCorrectly() throws IOException {
doTest("V4Broadcast.payload", 4, V4Broadcast.class);
}
@Test
public void ensureV5BroadcastIsDeserializedAndSerializedCorrectly() throws IOException {
doTest("V5Broadcast.payload", 5, V5Broadcast.class);
}
@Test
public void ensureUnknownDataIsDeserializedAndSerializedCorrectly() throws IOException {
doTest("V1MsgStrangeData.payload", 1, GenericPayload.class);
}
private void doTest(String resourceName, int version, Class<?> expectedPayloadType) throws IOException {
byte[] data = TestUtils.getBytes(resourceName);
InputStream in = new ByteArrayInputStream(data);

Binary file not shown.