Save and load asymmetric keys from jPBC

2020-07-27 23:58发布

问题:

I want similar functionality as prescribe Easy way to store/restore encryption key for decrypting string in java

But my case is different. In the above link they are using javax.crypto.* but in my case I am using org.bouncycastle.crypto.* and it.unisa.dia.gas.crypto.jpbc.fe.abe.gghsw13.generators.*

I want to store master-secret-key, public-key and private-key in different files and also retrieve those keys from files. How to do it ?

Below is the code where I left TODOs. Working code can be found on github.

import it.unisa.dia.gas.crypto.circuit.BooleanCircuit;
import it.unisa.dia.gas.crypto.circuit.BooleanCircuit.BooleanCircuitGate;
import it.unisa.dia.gas.crypto.jpbc.fe.abe.gghsw13.engines.GGHSW13KEMEngine;
import it.unisa.dia.gas.crypto.jpbc.fe.abe.gghsw13.generators.GGHSW13KeyPairGenerator;
import it.unisa.dia.gas.crypto.jpbc.fe.abe.gghsw13.generators.GGHSW13ParametersGenerator;
import it.unisa.dia.gas.crypto.jpbc.fe.abe.gghsw13.generators.GGHSW13SecretKeyGenerator;
import it.unisa.dia.gas.crypto.jpbc.fe.abe.gghsw13.params.*;
import it.unisa.dia.gas.crypto.kem.cipher.engines.KEMCipher;
import it.unisa.dia.gas.crypto.kem.cipher.params.KEMCipherDecryptionParameters;
import it.unisa.dia.gas.crypto.kem.cipher.params.KEMCipherEncryptionParameters;
import it.unisa.dia.gas.plaf.jpbc.pairing.PairingFactory;
import it.unisa.dia.gas.plaf.jpbc.util.concurrent.ExecutorServiceUtils;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.spec.AlgorithmParameterSpec;
import java.util.ArrayList;
import java.util.List;

import static it.unisa.dia.gas.crypto.circuit.Gate.Type.*;

public class Example {
    protected KEMCipher kemCipher;
    protected AlgorithmParameterSpec iv;

    protected AsymmetricCipherKeyPair keyPair;


    public Example() throws GeneralSecurityException {
        this.kemCipher = new KEMCipher(
                Cipher.getInstance("AES/CBC/PKCS7Padding", "BC"),
                new GGHSW13KEMEngine()
        );

        // build the initialization vector.  This example is all zeros, but it
        // could be any value or generated using a random number generator.
        iv = new IvParameterSpec(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});
    }


    public AsymmetricCipherKeyPair setup(int n) {
        GGHSW13KeyPairGenerator setup = new GGHSW13KeyPairGenerator();
        setup.init(new GGHSW13KeyPairGenerationParameters(
                new SecureRandom(),
                new GGHSW13ParametersGenerator().init(
                        PairingFactory.getPairing("params/mm/ctl13/toy.properties"),
                        n).generateParameters()
        ));

        return (keyPair = setup.generateKeyPair());
    }


    public byte[] initEncryption(String assignment) {
        try {
            return kemCipher.init(
                    true,
                    new KEMCipherEncryptionParameters(
                            128,
                            new GGHSW13EncryptionParameters(
                                    (GGHSW13PublicKeyParameters) keyPair.getPublic(),
                                    assignment
                            )
                    ),
                    iv
            );
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public byte[] encrypt(String message) {
        try {
            return kemCipher.doFinal(message.getBytes());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    public CipherParameters keyGen(BooleanCircuit circuit) {
        GGHSW13SecretKeyGenerator keyGen = new GGHSW13SecretKeyGenerator();
        keyGen.init(new GGHSW13SecretKeyGenerationParameters(
                ((GGHSW13PublicKeyParameters) keyPair.getPublic()),
                ((GGHSW13MasterSecretKeyParameters) keyPair.getPrivate()),
                circuit
        ));

        return keyGen.generateKey();
    }

    public byte[] decrypt(CipherParameters secretKey, byte[] encapsulation, byte[] ciphertext) {
        try {
            kemCipher.init(
                    false,
                    new KEMCipherDecryptionParameters(secretKey, encapsulation, 128),
                    iv
            );
            return kemCipher.doFinal(ciphertext);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }



    public static void main(String[] args) {
        Security.addProvider(new BouncyCastleProvider());

        try {
            // Setup
            int n = 4;
            Example engine = new Example();
            engine.setup(n);

            // TODO: Here I want to store (GGHSW13PublicKeyParameters) keyPair.getPublic() and 
            // (GGHSW13MasterSecretKeyParameters) keyPair.getPrivate() in files and later to retrieve from file

            // Encrypt
            String message = "Hello World!!!";
            byte[] encapsulation = engine.initEncryption("1101");
            byte[] ciphertext = engine.encrypt(message);

            BooleanCircuitGate bcg1 = new BooleanCircuitGate(INPUT, 0, 1);

            BooleanCircuitGate[] bcgs = new BooleanCircuitGate[]{
                    new BooleanCircuitGate(INPUT, 0, 1),
                    new BooleanCircuitGate(INPUT, 1, 1),
                    new BooleanCircuitGate(INPUT, 2, 1),
                    new BooleanCircuitGate(INPUT, 3, 1),

                    new BooleanCircuitGate(AND, 4, 2, new int[]{0, 1}),
                    new BooleanCircuitGate(OR, 5, 2, new int[]{2, 3}),

                    new BooleanCircuitGate(AND, 6, 3, new int[]{4, 5}),
            };

            List<BooleanCircuitGate> bcgList = new ArrayList<BooleanCircuitGate>();

            bcgList.add(bcg1);
            bcgList.add(new BooleanCircuitGate(INPUT, 1, 1));
            bcgList.add(new BooleanCircuitGate(INPUT, 2, 1));
            bcgList.add(new BooleanCircuitGate(INPUT, 3, 1));
            bcgList.add(new BooleanCircuitGate(AND, 4, 2, new int[]{0, 1}));
            bcgList.add(new BooleanCircuitGate(OR, 5, 2, new int[]{2, 3}));
            bcgList.add(new BooleanCircuitGate(AND, 6, 3, new int[]{4, 5}));

            // Decrypt
            int q = 3;
            BooleanCircuit circuit = new BooleanCircuit(n, q, 3, bcgList.toArray(new BooleanCircuitGate[bcgList.size()]));

            GGHSW13SecretKeyParameters secretKey = (GGHSW13SecretKeyParameters) engine.keyGen(circuit);

            // TODO: Want to store secretKey in file and later to retrieve from file

            byte[] plaintext = engine.decrypt(secretKey, encapsulation, ciphertext);

            System.out.println(new String(plaintext));

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            ExecutorServiceUtils.shutdown();
        }
    }

}

回答1:

After you generate your master key and the public parameters, you should store them so that you can actually use them later on. jPBC doesn't provide you with a way to store these keys. Also, since the keys inherit from org.bouncycastle.crypto.params.AsymmetricKeyParameter, you can't use Java's serialization, because AsymmetricKeyParameter does not implement the Serializable interface. It doesn't work without.

You will need to implement the serialization yourself. First, you have to think about what kind of objects are inside of a key that you want to serialize. In the case of the GGHSW13MasterSecretKeyParameters class, this is an Element, an int and a Pairing.

First, you have to think about whether you want to include the Pairing in the serialized key. If you do, you must write it into the beginning in order to be able to use it later for deserializing the Element.

If we assume that a Pairing instance is constant or is always provided from outer code, the serialization is pretty easy. You should write a format version to the front, so you can change your formats along the way without throwing away all your previously serialized keys. Writing the element is a little more tricky, because of a bug I encountered 2 years ago. The basic idea is that you write the length of the element's bytes followed by the element's content.

public void serialize(GGHSW13MasterSecretKeyParameters msk, OutputStream out) throws IOException {
    DataOutputStream dOut = new DataOutputStream(out);

    dOut.writeInt(1); // version of the serialized format
    dOut.writeInt(msk.getParameters().getN());

    serialize(msk.getAlpha(), dOut, msk.getParameters().getPairing());
}

public void serialize(Element elem, DataOutputStream dOut, Pairing pairing) throws IOException {
    dOut.writeBoolean(elem == null);
    if (elem == null) {
        return;
    }

    dOut.writeInt(pairing.getFieldIndex(elem.getField()));
    byte[] bytes = elem.toBytes();
    dOut.writeInt(bytes.length);
    dOut.write(bytes);

    // this is a workaround because it.unisa.dia.gas.plaf.jpbc.field.curve.CurveElement does not serialize the infFlag
    dOut.writeBoolean(elem instanceof CurveElement && elem.isZero());
    if (elem instanceof CurveElement && elem.isZero()) {
        throw new IOException("Infinite element detected. They should not happen.");
    }
}

The OutputStream can be something like FileOutputSteam or ByteArrayOutputStream.

The deserialization is equally easy, but you need to explicitly supply the Pairing and you need to make sure that you always read exactly as many bytes as you requested. The number of bytes that you request is known from the length int that is written in front of the data. If you don't make a check whether that length makes sense, you might introduce a security issue such as a denial of service vulnerability or remote code execution.

public GGHSW13MasterSecretKeyParameters deserialize(InputStream in, Pairing pairing) throws IOException {
    DataInputStream dIn = new DataInputStream(in);

    int version = dIn.readInt();
    if (version != 1) {
        throw new RuntimeException("Unknown key format version: " + version);
    }

    int n = dIn.getInt();
    Element alpha = deserialize(dIn, pairing);

    return new GGHSW13MasterSecretKeyParameters(
            new GGHSW13Parameters(pairing, n),
            alpha
    );
}

public Element deserialize(DataInputStream dIn, Pairing pairing) throws IOException {
    if (dIn.readBoolean()) {
        return null;
    }

    int fieldIndex = dIn.readInt(); // TODO: check if this is in a sensible range
    int length = dIn.readInt(); // TODO: check if this is in a sensible range
    byte[] bytes = new byte[length];
    dIn.readFully(bytes); // throws an exception if there is a premature EOF
    Element e = pairing.getFieldAt(fieldIndex).newElementFromBytes(bytes);

    // this is a workaround because it.unisa.dia.gas.plaf.jpbc.field.curve.CurveElement does not serialize the infFlag
    boolean instOfCurveElementAndInf = dIn.readBoolean();
    if (instOfCurveElementAndInf) {
        //e.setToZero(); // according to the code this simply sets the infFlag to 1
        throw new IOException("The point is infinite. This shouldn't happen.");
    }
    return e;
}

This is a binary serialization which is sort of small. There are other possibilities like encoding all the components to a string and using JSON for example.



回答2:

I have the same question. I use the JPBC to realize the server and client mode. From the client side, the client should use JPBC to encrypt the plaintext, and the server should decrypt the cipertext to recover the plaintext. My question is what message should I send from the client to server so that the server can decrypt the cipertext, that is, I can serialize the keys including the public key and private key but how can I serialize the pairing because I should use the same pairing in the server side.