1

I've have the following ElGamal encryption scheme

const forge = require('node-forge');
const bigInt = require("big-integer");

// Generates private and public keys
function keyPairGeneration(p, q, g) {
    var secretKey = bigInt.randBetween(2, q.minus(2));
    var publicKey = g.modPow(secretKey, p);
    const keys = {
        secret: secretKey,
        public: publicKey
    }
    return keys;
}

// Generates a proxy and a user key
function generateProxyKeys(secretKey) {
    const firstKey = bigInt.randBetween(1, secretKey);
    const secondKey = secretKey.minus(firstKey);
    const keys = {
        firstKey: firstKey,
        secondKey: secondKey
    }
    return keys;
}

// Re-encrypts 
function preEncrypt(p, q, g, m, publicKey) {
    const k = bigInt.randBetween(1, q.minus(1));
    const c1 = g.modPow(k, p);
    // g^x = publicKey
    // m.publicKey^k
    const c2 = bigInt(m).multiply(publicKey.modPow(k, p)).mod(p);
    const c = {
        c1: c1, 
        c2: c2
    }
    return c;
}

function preDecrypt(p, c1, c2, key) {
    // (mg^xr) / (g^rx1)
    var decrypt = c2.multiply(c1.modPow(key, p).modInv(p)).mod(p); 
    return decrypt;
}

Which works fine with numbers. However, I want to be able to use it to encrypt strings (btw, it's not a regular ElGamal, I don't think the difference is that relevant in this context but for more details see this question I asked)

I thought about converting the string to an integer, running the encryption, and converting back to a string whenever I needed it. I couldn't find a way of doing this in JS (there was this question posted here but the code didn't work). There is another similar question but it's in Java and the method mentioned there is not provided by the BigInt implementation in JS.

Is there any easy way of converting a string to a BigInt?

Artjom B.
  • 61,146
  • 24
  • 125
  • 222
mcansado
  • 2,026
  • 4
  • 25
  • 39

1 Answers1

5

Arbitrarily long messages

Asymmetric encryption should not be used to encrypt messages of arbitrary length, because it is much slower than symmetric encryption. So, we can use symmetric encryption for the actual message and asymmetric encryption for the key that encrypted the message.

There are basically two ways for arbitrary sized messages:

  1. If prime p is big enough that it fits a common key size of a symmetric cipher such as AES, then you can simply generate a random AES key (128, 192 or 256 bit) and use an AES-derived scheme such as AES-GCM to encrypt your message. Afterwards, you decode a number from the AES key (use fromArray) to be used as m in your ElGamal-like encryption scheme. This is called hybrid encryption.

  2. Regardless how big prime p is, you can always generate a random m number in the range of 1 to p-1 and use that to produce your asymmetric ciphertext. Afterwards, you can take the previously generated m, encode it into a byte array (use toString(16) to produce a Hex-encoded string and then simply parse it as Hex for the hashing) and hash it with a cryptographic hash function such as SHA-256 to get your AES key. Then you can use the AES key to encrypt the message with a symmetric scheme like AES-GCM. This is called key encapsulation.

The main remaining thing that you have to look out for is data format: How do you serialize the data for the asymmetric part and the symmetric part of the ciphertext? How do you read them back that you can always tell them apart? There are many possible solutions there.

Short messages

If the messages that you want to encrypt have a maximum size that is smaller than the prime that you use, then you don't need the two approaches above. You just need to take the byte representation of the message and convert it to a big integer. Something like this:

var arr = Array.prototype.slice.call(Buffer.from("some message"), 0);
var message = bigInt.fromArray(arr, 256);

This is a big endian encoding.

This makes only sense if your prime is big enough which it should be for security.

Artjom B.
  • 61,146
  • 24
  • 125
  • 222
  • Thanks for your reply. If I understand correctly, option 1 consists of encrypting data with a symmetric key and then using the ElGamal scheme to encrypt that key with the recipient's public key. I believe that would be a problem in my system because I'm trying to create a system with temporary access to encrypted files in a DB. If at any point, my delegatee knows my private key, he could then decrypt the whole contents of the DB indefinitely, right? Also I should add that the strings being encrypted are not really arbitrary size, they're ~300 bytes, worst case. – mcansado Aug 14 '17 at 11:10
  • *"If at any point, my delegatee knows my private key"* - that's the responsibility of your ElGamal-style proxy re-encryption scheme. You would need to generate a different AES key for each message that you encrypt. If your PRE scheme is correct, then your proxy will not be able to get the AES key out of this, because that's the whole point of a PRE scheme that the proxy doesn't see the original message (in this case the decoded AES key). I don't know who you mean by "delegatee". – Artjom B. Aug 14 '17 at 17:54
  • *"the strings being encrypted are not really arbitrary size, they're ~300 bytes, worst case."* - Well, judging by your previous question, you're using a 160 bit prime which is too small to encrypt 300 bytes (2400 bit). So, you would need to use one either one of the two ways I described. Since this is non-elliptic curve ElGamal, I guess 160 bit is much too small for any serious security. You would need to use a larger prime for that. Think about using a 1024 or 2048 bit prime. Or you could use a 3072 bit prime an encode your messages directly. – Artjom B. Aug 14 '17 at 17:57