From 46de34fbfb4d892f4cf122880a8f85708146990a Mon Sep 17 00:00:00 2001 From: Tal Moran Date: Sun, 22 Nov 2015 18:09:51 +0200 Subject: [PATCH] Started implementing encryption tests -- don't yet work --- meerkat-common/build.gradle | 2 +- .../main/java/meerkat/crypto/Encryption.java | 21 ++++- .../crypto/concrete/ECElGamalEncryption.java | 28 +++++- .../crypto/concrete/GlobalCryptoSetup.java | 25 +++--- ...Signature.java => ECDSASignatureTest.java} | 2 +- .../concrete/ECElGamalEncryptionTest.java | 61 +++++++++++++ .../crypto/concrete/ECElGamalUtils.java | 88 +++++++++++++++++++ 7 files changed, 211 insertions(+), 16 deletions(-) rename meerkat-common/src/test/java/meerkat/crypto/concrete/{TestECDSASignature.java => ECDSASignatureTest.java} (99%) create mode 100644 meerkat-common/src/test/java/meerkat/crypto/concrete/ECElGamalEncryptionTest.java create mode 100644 meerkat-common/src/test/java/meerkat/crypto/concrete/ECElGamalUtils.java diff --git a/meerkat-common/build.gradle b/meerkat-common/build.gradle index eb2c6a2..2b48d73 100644 --- a/meerkat-common/build.gradle +++ b/meerkat-common/build.gradle @@ -46,7 +46,7 @@ dependencies { compile 'com.google.protobuf:protobuf-java:3.+' // Crypto - compile 'org.factcenter.qilin:qilin:1.+' + compile 'org.factcenter.qilin:qilin:1.1+' compile 'org.bouncycastle:bcprov-jdk15on:1.53' compile 'org.bouncycastle:bcpkix-jdk15on:1.53' diff --git a/meerkat-common/src/main/java/meerkat/crypto/Encryption.java b/meerkat-common/src/main/java/meerkat/crypto/Encryption.java index 385e1dd..6a40d0d 100644 --- a/meerkat-common/src/main/java/meerkat/crypto/Encryption.java +++ b/meerkat-common/src/main/java/meerkat/crypto/Encryption.java @@ -2,6 +2,10 @@ package meerkat.crypto; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.Message; + +import java.io.IOException; +import java.util.Random; + import static meerkat.protobuf.Crypto.*; /** @@ -14,8 +18,23 @@ public interface Encryption { * @param rnd * @return */ - RerandomizableEncryptedMessage encrypt(Message plaintext, EncryptionRandomness rnd); // TODO: type of exception; throws + RerandomizableEncryptedMessage encrypt(Message plaintext, EncryptionRandomness rnd) throws IOException; // TODO: type of exception; throws + + /** + * Rerandomize a ciphertext using the supplied randomness. + * @param msg + * @param rnd + * @return + * @throws InvalidProtocolBufferException + */ RerandomizableEncryptedMessage rerandomize(RerandomizableEncryptedMessage msg, EncryptionRandomness rnd) throws InvalidProtocolBufferException; + /** + * Generate randomness compatible with {@link #encrypt(Message, EncryptionRandomness) and + * {@link #rerandomize(RerandomizableEncryptedMessage, EncryptionRandomness)}}. + * @param rand + * @return + */ + EncryptionRandomness generateRandomness(Random rand); } diff --git a/meerkat-common/src/main/java/meerkat/crypto/concrete/ECElGamalEncryption.java b/meerkat-common/src/main/java/meerkat/crypto/concrete/ECElGamalEncryption.java index 276c1a7..d724ca5 100644 --- a/meerkat-common/src/main/java/meerkat/crypto/concrete/ECElGamalEncryption.java +++ b/meerkat-common/src/main/java/meerkat/crypto/concrete/ECElGamalEncryption.java @@ -1,6 +1,7 @@ package meerkat.crypto.concrete; import com.google.protobuf.ByteString; +import com.google.protobuf.CodedOutputStream; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.Message; import meerkat.crypto.Encryption; @@ -24,14 +25,17 @@ import qilin.primitives.PseudorandomGenerator; import qilin.util.PRGRandom; import qilin.util.Pair; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.math.BigInteger; +import java.security.SecureRandom; import java.security.spec.*; +import java.util.Random; /** * Created by talm on 17/11/15. */ -public class ECElGamalEncryption implements Encryption { +public class ECElGamalEncryption extends GlobalCryptoSetup implements Encryption { final Logger logger = LoggerFactory.getLogger(getClass()); public final static String KEY_ALGORITHM = "ECDH"; @@ -74,8 +78,17 @@ public class ECElGamalEncryption implements Encryption { @Override public Crypto.RerandomizableEncryptedMessage encrypt(Message plaintext, Crypto.EncryptionRandomness rnd) { - byte[] msg = plaintext.toByteArray(); - ECPoint encodedMsg = group.injectiveEncode(plaintext.toByteArray(), new PRGRandom(msg)); + + // We write the message using writeDelimited to so the length gets prepended. + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try { + plaintext.writeDelimitedTo(out); + } catch (IOException e) { + logger.error("Should never happen!", e); + throw new RuntimeException("Error in ByteArrayOutputStream!", e); + } + byte[] msg = out.toByteArray(); + ECPoint encodedMsg = group.injectiveEncode(msg, new PRGRandom(msg)); BigInteger rndInt = BigIntegers.fromUnsignedByteArray(rnd.getData().toByteArray()); Pair cipherText = elGamalPK.encrypt(encodedMsg, rndInt); @@ -108,4 +121,13 @@ public class ECElGamalEncryption implements Encryption { .build().toByteString() ).build(); } + + @Override + public Crypto.EncryptionRandomness generateRandomness(Random rand) { + BigInteger randomInt = new BigInteger(group.getCurveParams().getN().bitLength() - 1, rand); + Crypto.EncryptionRandomness retval = Crypto.EncryptionRandomness.newBuilder() + .setData(ByteString.copyFrom(BigIntegers.asUnsignedByteArray(randomInt))).build(); + + return retval; + } } diff --git a/meerkat-common/src/main/java/meerkat/crypto/concrete/GlobalCryptoSetup.java b/meerkat-common/src/main/java/meerkat/crypto/concrete/GlobalCryptoSetup.java index 764fd8d..4f2e7a5 100644 --- a/meerkat-common/src/main/java/meerkat/crypto/concrete/GlobalCryptoSetup.java +++ b/meerkat-common/src/main/java/meerkat/crypto/concrete/GlobalCryptoSetup.java @@ -4,6 +4,7 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.security.Provider; import java.security.Security; /** @@ -13,6 +14,7 @@ public class GlobalCryptoSetup { final static Logger logger = LoggerFactory.getLogger(GlobalCryptoSetup.class); static boolean loadedBouncyCastle = false; + static Provider bouncyCastleProvider; public static boolean hasSecp256k1Curve() { // For now we just check if the java version is at least 8 @@ -22,17 +24,20 @@ public class GlobalCryptoSetup { return ((major > 1) || ((major > 0) && (minor > 7))); } - public static void doSetup() { - // Make bouncycastle our default provider if we're running on a JVM version < 8 - // (earlier version don't support the EC curve we use for signatures) - if (!hasSecp256k1Curve() && !loadedBouncyCastle) { - loadedBouncyCastle = true; - Security.insertProviderAt(new BouncyCastleProvider(), 1); - logger.info("Using BouncyCastle instead of native provider to support secp256k1 named curve"); + public static Provider getBouncyCastleProvider() { doSetup(); return bouncyCastleProvider; } + + public static synchronized void doSetup() { + if (bouncyCastleProvider == null) { + bouncyCastleProvider = new BouncyCastleProvider(); + // Make bouncycastle our default provider if we're running on a JVM version < 8 + // (earlier version don't support the EC curve we use for signatures) + if (!hasSecp256k1Curve() && !loadedBouncyCastle) { + loadedBouncyCastle = true; + Security.insertProviderAt(bouncyCastleProvider, 1); + logger.info("Using BouncyCastle instead of native provider to support secp256k1 named curve"); + } } } - public GlobalCryptoSetup() { - doSetup(); - } + public GlobalCryptoSetup() { doSetup(); } } diff --git a/meerkat-common/src/test/java/meerkat/crypto/concrete/TestECDSASignature.java b/meerkat-common/src/test/java/meerkat/crypto/concrete/ECDSASignatureTest.java similarity index 99% rename from meerkat-common/src/test/java/meerkat/crypto/concrete/TestECDSASignature.java rename to meerkat-common/src/test/java/meerkat/crypto/concrete/ECDSASignatureTest.java index 8d9a880..466c64c 100644 --- a/meerkat-common/src/test/java/meerkat/crypto/concrete/TestECDSASignature.java +++ b/meerkat-common/src/test/java/meerkat/crypto/concrete/ECDSASignatureTest.java @@ -17,7 +17,7 @@ import static org.junit.Assert.assertTrue; /** * Created by talm on 12/11/15. */ -public class TestECDSASignature { +public class ECDSASignatureTest { public static String KEYFILE_EXAMPLE = "/certs/enduser-certs/user1-key-with-password-secret.p12"; public static String KEYFILE_PASSWORD = "secret"; diff --git a/meerkat-common/src/test/java/meerkat/crypto/concrete/ECElGamalEncryptionTest.java b/meerkat-common/src/test/java/meerkat/crypto/concrete/ECElGamalEncryptionTest.java new file mode 100644 index 0000000..ba1c54c --- /dev/null +++ b/meerkat-common/src/test/java/meerkat/crypto/concrete/ECElGamalEncryptionTest.java @@ -0,0 +1,61 @@ +package meerkat.crypto.concrete; + +import meerkat.protobuf.BulletinBoardAPI; +import meerkat.protobuf.ConcreteCrypto; +import meerkat.protobuf.Crypto; +import meerkat.protobuf.Voting; +import org.junit.Before; +import org.junit.Test; +import qilin.primitives.concrete.ECElGamal; +import qilin.primitives.concrete.ECGroup; + +import java.math.BigInteger; +import java.util.Random; + +/** + * Test class for {@link ECElGamalEncryption} + */ +public class ECElGamalEncryptionTest { + Random rand = new Random(0); // Insecure deterministic random for testing. + + ECElGamal.SK key; + ECGroup group; + ECElGamalEncryption enc; + ConcreteCrypto.ElGamalPublicKey serializedPk; + + + @Before + public void setup() throws Exception { + group = new ECGroup("secp256k1"); + BigInteger sk = ECElGamal.generateSecretKey(group, rand); + key = new ECElGamal.SK(group, sk); + serializedPk = ECElGamalUtils.serializePk(group, key); + + + enc = new ECElGamalEncryption(); + + enc.init(serializedPk); + } + + @Test + public void testEncrypt() throws Exception { + Voting.PlaintextBallot msg = Voting.PlaintextBallot.newBuilder() + .setSerialNumber(1) + .addAnswers( Voting.BallotAnswer.newBuilder() + .addAnswer(2) + .addAnswer(3) + .addAnswer(0) + ) + .addAnswers( Voting.BallotAnswer.newBuilder() + .addAnswer(5) + .addAnswer(6) + .addAnswer(7) + ) + .build(); + + Crypto.RerandomizableEncryptedMessage cipherText = enc.encrypt(msg, enc.generateRandomness(rand)); + + Voting.PlaintextBallot decrypted = ECElGamalUtils.decrypt(Voting.PlaintextBallot.class, key, group, cipherText); + + } +} diff --git a/meerkat-common/src/test/java/meerkat/crypto/concrete/ECElGamalUtils.java b/meerkat-common/src/test/java/meerkat/crypto/concrete/ECElGamalUtils.java new file mode 100644 index 0000000..3a43cdb --- /dev/null +++ b/meerkat-common/src/test/java/meerkat/crypto/concrete/ECElGamalUtils.java @@ -0,0 +1,88 @@ +package meerkat.crypto.concrete; + +import com.google.protobuf.*; +import meerkat.protobuf.ConcreteCrypto; +import meerkat.protobuf.Crypto; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.jce.spec.ECParameterSpec; +import org.bouncycastle.jce.spec.ECPublicKeySpec; +import org.bouncycastle.math.ec.ECPoint; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import qilin.primitives.concrete.ECElGamal; +import qilin.primitives.concrete.ECGroup; +import qilin.primitives.generic.ElGamal; +import qilin.util.Pair; + +import java.lang.reflect.*; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PublicKey; +import java.security.spec.InvalidKeySpecException; +import java.util.Random; + +/** + * utilities for ECElgamal + */ +public class ECElGamalUtils { + final static Logger logger = LoggerFactory.getLogger(ECElGamalUtils.class); + + public final static String ENCRYPTION_KEY_ALGORITHM = "ECDH"; + + /** + * Serialize an El-Gamal public key into a form acceptable by {@link ECElGamalEncryption} + * @param pk + * @return + */ + public static ConcreteCrypto.ElGamalPublicKey serializePk(ECGroup group, ElGamal.PK pk) { + ECPoint pkPoint = pk.getPK(); + ECParameterSpec params = group.getCurveParams(); + + ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(pkPoint, params); + + try { + KeyFactory fact = KeyFactory.getInstance(ENCRYPTION_KEY_ALGORITHM, + GlobalCryptoSetup.getBouncyCastleProvider()); + PublicKey javaPk = fact.generatePublic(pubKeySpec); + ConcreteCrypto.ElGamalPublicKey serializedPk = ConcreteCrypto.ElGamalPublicKey.newBuilder() + .setSubjectPublicKeyInfo(ByteString.copyFrom(javaPk.getEncoded())).build(); + + return serializedPk; + } catch (NoSuchAlgorithmException|InvalidKeySpecException e) { + logger.error("Should never happen!", e); + throw new RuntimeException("Error converting public key!", e); + } + } + + /** + * Standard (non-threshold) decryption for testing purposes. + * @param secretKey + * @return + */ + public static T decrypt(Class plaintextMessageType, ECElGamal.SK secretKey, ECGroup group, Crypto.RerandomizableEncryptedMessage opaqueCipher) + throws InvalidProtocolBufferException { + ConcreteCrypto.ElGamalCiphertext cipherText = ConcreteCrypto.ElGamalCiphertext.parseFrom(opaqueCipher.getData()); + ByteString c1encoded = cipherText.getC1(); + ByteString c2encoded = cipherText.getC2(); + + ECPoint c1 = group.decode(c1encoded.toByteArray()); + ECPoint c2 = group.decode(c2encoded.toByteArray()); + + ECPoint plaintextEncoded = secretKey.decrypt(new Pair(c1, c2)); + + byte[] plaintext = group.injectiveDecode(plaintextEncoded); + + CodedInputStream ci = CodedInputStream.newInstance(plaintext); + + try { + java.lang.reflect.Method newBuilder = plaintextMessageType.getMethod("newBuilder"); + GeneratedMessage.Builder builder = (GeneratedMessage.Builder) newBuilder.invoke(plaintextMessageType); + return (T) builder.mergeFrom(ci).build(); + } catch (Exception e) { + logger.error("Error parsing incoming message", e); + throw new InvalidProtocolBufferException("Plaintext protobuf error"); + } + } + +}