2
0

README.md 16 KB

ElGamal Cryptography Module

This module defines the ElGamal cryptography API for Scytl's JavaScript cryptographic library. The starting point for using this module is the instantiation of an ElGamal cryptography service.

How to view, build and test the source code of this module

Start by git cloning the project comm/scytl-cryptolib from Stash.

The source code of this module will be found in the directory scytl-cryptolib/cryptolib-js-elgamal/lib.

To build this module, change to the directory scytl-cryptolib/cryptolib-js-elgamal and do the following:

npm install
--or--
mvn clean install -DskipTests

The unit tests of this module will be found in the directory scytl-cryptolib/cryptolib-js-elgamal/spec. To run the tests, change to the directory scytl-cryptolib/cryptolib-js-elgamal and do the following:

karma test
--or--
mvn test

To only run a chosen test suite, add an f in front of the corresponding describe statement. For example:

fdescribe('create an ElGamal cryptography service that should be able to ..', function()
...

To only run a chosen test, add an f in front of the corresponding it statement. For example:

fit('create a new ElGamal key pair', function() {
...

Note: To build and test the entire scytl-cryptolib project, change to the directory scytl-cryptolib and do the following:

mvn clean install

How to generate the JSDoc for this module

To generate the JSDoc for this module, change to the directory scytl-cryptolib/cryptolib-js-elgamal, build the module (if not already done) and do the following:

node_modules/.bin/jsdoc lib

This will generate a file called out. Double click on the file out/index.html to view the JSDoc.

How to npm install this module

To npm install this module in standalone mode, do the following:

npm install --registry https://nexus.scytl.net/content/groups/public-npm/ scytl-elgamal

To npm install this module as a dependency of your own module, do the following:

  1. Add the following lines to the dependencies section of your module's package.json file.

    "scytl-bitwise": "^2.1.0",
    "scytl-codec": "^2.1.0",
    "scytl-securerandom": "^2.1.0",
    "scytl-mathematical": "^2.1.0",
    "scytl-elgamal": "^2.1.0",
    
  2. Make sure that the .npmrc file of your module contains the following line.

    registry=https://nexus.scytl.net/content/groups/public-npm/
    
  3. Install all dependencies.

    npm install
    

    How to instantiate an ElGamal cryptography service

The following example shows the default way to create a new instance of an ElGamal cryptography service.

var elGamal = require('scytl-elgamal');

var elGamalService = elGamal.newService();

The following example shows how to supply one's own secure random service, initialized with a chosen seed, to the ElGamal cryptography service.

var elGamal = require('scytl-elgamal');
var secureRandom = require('scytl-securerandom');

var mySecureRandomService = secureRandom.newService({prngSeed: mySeed});

var elGamalService = elGamal.newService({secureRandomService: mySecureRandomService});

How to instantiate ElGamal key objects

The following examples illustrate how to create new ElGamalKeyPublicKey, ElGamalPrivateKey and ElGamalKeyPair objects, from a Zp subgroup, a set of public key elements and a set of private key exponents.

var elGamal = require('scytl-elgamal');
var mathematical = require('scytl-mathematical');
var forge = require('node-forge');

var BigInteger = BigInteger;

var elGamalService = elGamal.newService();
var mathService = mathematical.newService();

var p = new BigInteger('23');
var q = new BigInteger('11');
var g = new BigInteger('2');
var group = mathService.newZpSubgroup(p, q, g);

var keyElementValues = [new BigInteger('2'), new BigInteger('4')];
var keyExponentValues = [new BigInteger('1'), new BigInteger('2')];
var keyElements = [];
var keyExponents = [];
for (int i = 0; i < keyElementValues.length; i++) {
  keyElements.push(mathService.newZpGubroupElement(p, q, keyElementValues[i]));
  keyExponents.push(mathService.newExponent(q, keyExponentValues[i]));
}

var publicKey = elGamalService.newPublicKey(group, keyElements);
var privateKey = elGamalService.newPrivateKey(group, keyExponents);
var keyPair = elGamalService.newKeyPair(publicKey, privateKey);

How to encrypt

The following example shows how to encrypt an array of Zp group elements, with a given ElGamal public key. The output of the encryption will be an ElGamalEncryptedElements object, which encapsulates the gamma Zp group element and the array of phi Zp group elements that comprise the encryption.

var encrypter = elGamalService.newEncrypter().init(publicKey);

var p = new BigInteger('23');
var q = new BigInteger('11');
var elements = [new ZpGroupElement(p, q, '3'), new ZpGroupElement(p, q, '6'), ...];

var encryptedElements = encrypter.encrypt(elements);

The following example shows how to encrypt an array of Zp group elements that are received in JSON serialized form.

var elementJsons = [element1Json, element2Json, ...];
var elements = [];
for (var i = 0; i < elementJsons.length; i++) {
  var elements.push(new ZpGroupElement(elementJsons[i]));
}

var encryptedElements = encrypter.encrypt(elements);

The following example shows how to encrypt an array of Zp group elements that are received as an array of their values, in string format.

var elementValueStrings = ['3', '6', ...];

var encryptedElements = encrypter.encrypt(elementValueStrings);

Note In all of the examples shown above, it is required that the values of the Zp group elements being encrypted belong to the same Zp subgroup as that of the ElGamal public key used to initialize the encrypter.

How to encrypt, using a pre-computation step

The following example shows how to encrypt by performing a pre-computation step. The pre-computation step does not require knowledge of the Zp group elements to be encrypted. Therefore, it can be handled by a web worker during a voting session. Since the operations performed during the pre-computation step are computationally dominant in the encryption process, passing this step to a worker can significantly improve performance. The output of the pre-computation step will also be an ElGamalEncryptedElements object, but with no information pertaining to the Zp group elements to be encrypted. It is equivalent to ElGamal encrypting an array of identity Zp group elements.

var encrypter = elGamalService.newEncrypter().init(publicKey);

var preComputation = encrypter.preCompute();
...
var encryptedElements = encrypter.encrypt(elements, {preComputation: preComputation});

How to encrypt and save the secret exponent

The following example shows how to encrypt an array of Zp group elements, retrieve the secret exponent that was generated by and used during the encryption process and create a new ciphertext object from the public components of the encryption. An example of usage is the case where one needs to generate a zero knowledge proof of knowledge of the secret exponent generated by the encryption. The secret exponent can be retrieved as a property of the ElGamalEncryptedElements object returned by the encrypt method. Normally, this property is undefined, in order to protect the ElGamal encrypted elements from being decrypted by any other means than the private key.

var encrypter = elGamalService.newEncrypter().init(publicKey);

var encryptedElementsAndSecret = encrypter.encrypt(elements, {saveSecret: true});

var secret = encryptedElementsAndSecret.secret;
var encryptedElements = elGamalService.newEncryptedElements(encryptedElementsAndSecret.gamma, encryptedElementsAndSecret.phis);

Note: If the secret exponent is not needed, it is highly recommended to use the method encrypt without the saveSecret flag set to true, so that private information is not unnecessarily exposed.

How to encrypt and save the secret exponent, using a pre-computation step.

The following example shows how to encrypt an array of Zp group elements and retrieve the secret exponent when the pre-computation step needs to be performed. Note: The secret exponent is always generated by the pre-computation step, whether internally or externally, with regards to the encryption process.

var encrypter = elGamalService.newEncrypter().init(publicKey);
...

var preComputationAndSecret = encrypter.preCompute({saveSecret: true});

var secret = preComputationAndSecret.secret;
var preComputation = elGamalService.newEncryptedElements(preComputationAndSecret.gamma, preComputationAndSecret.phis)
...
var encryptedElements = encrypter.encrypt(elements, {preComputation: preComputation});

Note: As mentioned in the previous example, if the secret exponent is not needed, it is highly recommended to not set the saveSecret flag to true.

How to encrypt, using a short secret exponent

The following example shows how to encrypt an array of Zp group elements, using a so-called short secret exponent. Note: This method is only applicable for quadratic residue Zp subgroups, which are defined such that the modulus p and order q have the relationship: p = 2q + 1. If a pre-computation is provided as an option the short exponent option will be ignored since the secret exponent will already been generated by the pre-computation process.

var encrypter = elGamalService.newEncrypter().init(publicKey);

var encryptedElements = encrypter.encrypt(elements, {useShortExponent: true});

The following example shows how to encrypt an array of Zp group elements, using a short secret exponent and performing a pre-computation step.

var encrypter = elGamalService.newEncrypter().init(publicKey);

var preComputation = encrypter.preCompute({useShortExponent: true});
...
var encryptedElements = encrypter.encrypt(elements, {preComputation: preComputation});

How to decrypt

The following examples illustrate how to decrypt an encrypted array of Zp group elements, with a given ElGamal private key. If the confirmMembership parameter is provided and set to true, then a Zp subgroup membership check will performed on the encryption elements provided as input before starting the decryption process. Note: By default, this membership check is not performed because it is a computationally costly operation.

var decrypter = elGamalService.newDecrypter().init(privateKey);

var elements = decrypter.decrypt(encryptedElements);
var elements = decrypter.decrypt(encryptedElements, {confirmMembership: true});

How to encrypt and decrypt, using a randomly generated PRNG seed.

The following example shows how to encrypt an array of Zp group elements, by using a source of randomness created from a randomly generated PRNG seed and then decrypt the encrypted elements by using the public key and the same randomly generated seed used to encrypt. Note This shows a way in which ElGamal encrypted elements can be decrypted without the use of a private key.

var seed = secureRandomService.nextSeed();
var randomGenerator = new SecureRandomService({prngSeed: seed}).newRandomGenerator();
var encrypter = elGamalService.newEncrypter().init(publicKey, {secureRandomGenerator: randomGenerator});

var encryptedElements = encrypter.encrypt(elements);
...

var randomGenerator = new SecureRandomService({prngSeed: seed}).newRandomGenerator();
var prngDecrypter = elGamalService.newPrngDecrypter().init(publicKey, randomGenerator);

var decryptedElements = prngDecrypter.decrypt(encryptedElements);

The following example shows how to individually encrypt each member of an array of Zp group elements, using a randomly generated PRNG seed and then individually decrypt each encrypted element using the same randomly generated seed. Note In this example, the ElGamal public key is still allowed to have more than one element because it will be internally compressed to a single element before encrypting or decrypting.

var seed = secureRandomService.nextSeed();
var encrypterRandomGenerator = new SecureRandomService({prngSeed: seed}).newRandomGenerator();
var encrypter = elGamalService.newEncrypter().init(publicKey, {secureRandomGenerator: encrypterRandomGenerator});

var encryptedElementArray[];
for (var i = 0; i < elements.length; i++) {
  encryptedElementArray.push(encrypter.encrypt([elements[i]]));
}
...

var decrypterRandomGenerator = new SecureRandomService({prngSeed: seed}).newRandomGenerator();
var prngDecrypter = elGamalService.newPrngDecrypter().init(publicKey, decrypterRandomGenerator);

var decryptedElementArray = [];
for (var j = 0; j < encryptedElementArray.length; i++) {
  var decryptedElementArray.push(prngDecrypter.decrypt(encryptedElementArray[j])[0]);
}

The following example shows how to encrypt and decrypt an array of Zp group elements, using a randomly generated PRNG seed and a short secret exponent. Note In this case, the useShortExponent flag must also be set to true for the decryption because the short secret exponent will need to be regenerated during this process.

var seed = secureRandomService.nextSeed();
var randomGenerator = new SecureRandomService({prngSeed: seed}).newRandomGenerator();
var encrypter = elGamalService.newEncrypter().init(publicKey, {secureRandomGenerator: randomGenerator});

var encryptedElements = encrypter.encrypt(elements, {useShortExponent: true});
...

var randomGenerator = new SecureRandomService({prngSeed: seed}).newRandomGenerator();
var prngDecrypter = elGamalService.newPrngDecrypter().init(publicKey, randomGenerator);

var decryptedElements = prngDecrypter.decrypt(encryptedElements, {useShortExponent: true});

How to instantiate an ElGamal encrypted elements object.

In some cases, it might be necessary to construct an ElGamal encrypted elements object manually rather than obtaining it from an encryption or pre-computation. The following example shows how to create a new ElGamalEncryptedElements object, using the components of an encryption operation.

var encryptedElements = encrypter.encrypt(elements);

var newEncryptedElements = elGamalService.newEncryptedElements(encryptedElements.gamma, encryptedElements.phis);

How to serialize and deserialize ElGamal cryptography objects

The following examples illustrate how to serialize and deserialize ElGamalPublicKey, ElGamalPrivateKey and ElGamalEncryptedElements objects to and from their corresponding JSON representations.

var publicKeyJson = publicKey.toJson(publicKey);
var publicKey = elGamalService.newPublicKey(publicKeyJson);
var privateKeyJson = privateKey.toJson(privateKey);
var privateKey = elGamalService.newPrivateKey(privateKeyJson);
var encryptedElementsJson = encryptedElements.toJson(encryptedElements);
var encryptedElements = elGamalService.newEncryptedElements(encryptedElementsJson);

Note regarding input data validation and Zp subgroup membership checks

In general, the input data validation performed by the ElGamal service does not include checking that the values of a Zp group element array or the element values of an ElGamal encryption provided as input are members of the Zp subgroup provided as input (either directly or via an ElGamal key). The reason for this, is that the group membership check involves the modPow mathematical operation, which is computationally costly and is linearly proportional to the number of values to check. An exception to this rule is the ElGamal decryption operation, in which case the group membership check is provided as an option. The mathematical service provides for the creation of a MathematicalGroupHandler object which defines a method called checkGroupMembership that can, for example, be used to check an array of Zp group elements against the Zp subgroup of an ElGamal public key before providing this data as input to an encryption method of the ElGamal service.