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:
2015-08-05 19:52:18 +02:00
parent 6542bd1451
commit b8546e28af
60 changed files with 1168 additions and 641 deletions

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

@ -0,0 +1,91 @@
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 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 19.07.15.
*/
public class SecurityTest {
public static final byte[] TEST_VALUE = "teststring".getBytes();
public static final byte[] TEST_SHA1 = DatatypeConverter.parseHexBinary(""
+ "b8473b86d4c2072ca9b08bd28e373e8253e865c4");
public static final byte[] TEST_SHA512 = DatatypeConverter.parseHexBinary(""
+ "6253b39071e5df8b5098f59202d414c37a17d6a38a875ef5f8c7d89b0212b028"
+ "692d3d2090ce03ae1de66c862fa8a561e57ed9eb7935ce627344f742c0931d72");
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));
}
@Test
public void testSha1() {
assertArrayEquals(TEST_SHA1, security.sha1(TEST_VALUE));
}
@Test
public void testSha512() {
assertArrayEquals(TEST_SHA512, security.sha512(TEST_VALUE));
}
@Test
public void testChaining() {
assertArrayEquals(TEST_SHA512, security.sha512("test".getBytes(), "string".getBytes()));
}
@Test
public void testDoubleHash() {
assertArrayEquals(security.sha512(TEST_SHA512), security.doubleSha512(TEST_VALUE));
}
@Test(expected = IOException.class)
public void testProofOfWorkFails() throws IOException {
ObjectMessage objectMessage = new ObjectMessage.Builder()
.nonce(new byte[8])
.expiresTime(UnixTime.now(+2 * DAY)) // 5 minutes
.objectType(0)
.payload(GenericPayload.read(0, new ByteArrayInputStream(new byte[0]), 1, 0))
.build();
security.checkProofOfWork(objectMessage, 1000, 1000);
}
@Test
public void testDoProofOfWork() throws IOException {
ObjectMessage objectMessage = new ObjectMessage.Builder()
.nonce(new byte[8])
.expiresTime(UnixTime.now(+2 * DAY))
.objectType(0)
.payload(GenericPayload.read(0, new ByteArrayInputStream(new byte[0]), 1, 0))
.build();
security.doProofOfWork(objectMessage, 1000, 1000);
security.checkProofOfWork(objectMessage, 1000, 1000);
}
}