javacryptographybouncycastleelgamal

How to Make Encryption (c1, c2) Tuple Explicit using Bouncy Castle ElGamal and javax.crypto.Cipher


To encrypt a message with ElGamal scheme in java code, I proceed as follow:

Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

Cipher cipher = Cipher.getInstance("Elgamal/NOne/NoPadding", "BC");
KeyPaireGenerator generator = KeyPairGenerator.getInstance("ElGamal", "BC");
SecureRandom random = new SecureRandom();

generator.initialize(512, random);
KeyPair pair = generator.generateKeyPair();

String message = "myMessageToEncrypt";
cipher.init(Cipher.ENCRYPT_MODE, pair.getPublic(), random);
[]byte cipherText = cipher.doFinal(message);

I know from the ELGamal scheme that cipherText byte array contains (c1, c2) and I need to access c1 as an BigInteger.

So my question is: how to make the conversion between the byte array and the tuple (c1, c2) ?

Thank you


Solution

  • The byte[] with the ciphertext has twice the length of the key size, where the first half corresponds to c0 and the second half to c1. The conversion of ci is achievable e.g. with new BigInteger(1, ci).

    Verification is easily possible by performing the decryption manually with the BigIntegers converted in this way:

    int keysizeBits = 512;
    
    Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
    
    // Key generation
    KeyPairGenerator generator = KeyPairGenerator.getInstance("ElGamal", "BC");
    SecureRandom random = new SecureRandom();
    generator.initialize(keysizeBits, random); 
    
    KeyPair pair = generator.generateKeyPair();
    BCElGamalPublicKey publicKey = (BCElGamalPublicKey)pair.getPublic();
    BCElGamalPrivateKey privateKey = (BCElGamalPrivateKey)pair.getPrivate();
    
    // Encryption
    byte[] input = "abcdefgh".getBytes(StandardCharsets.UTF_8);
    Cipher cipher = Cipher.getInstance("ElGamal/None/NoPadding", "BC");
    cipher.init(Cipher.ENCRYPT_MODE, publicKey, random);
    byte[] ciphertext = cipher.doFinal(input);
    System.out.println("Ciphertext: " + Hex.toHexString(ciphertext));//new String(cipherText));
    
    // Decryption
    cipher.init(Cipher.DECRYPT_MODE, privateKey);
    byte[] plaintext = cipher.doFinal(ciphertext);
    System.out.println("Plaintext : " + new String(plaintext, StandardCharsets.UTF_8));
    
    // Manual decryption 
    // 1. Convert c0/c1 into BigInteger
    byte[] c0 = new byte[keysizeBits/8];
    byte[] c1 = new byte[keysizeBits/8];
    System.arraycopy(ciphertext, 0,  c0, 0, keysizeBits/8);
    System.arraycopy(ciphertext, c0.length,  c1, 0, keysizeBits/8);
    System.out.println("c0        : " + Hex.toHexString(c0));
    System.out.println("c1        : " + Hex.toHexString(c1));
    BigInteger c0BI = new BigInteger(1, c0); 
    BigInteger c1BI = new BigInteger(1, c1); 
    
    // 2. Decrypt with c0BI^(-privBI) * c1BI
    BigInteger privateKeyBI = privateKey.getX();
    BigInteger pBI = privateKey.getParameters().getP();
    BigInteger plaintextBI = c0BI.modPow(privateKeyBI.multiply(new BigInteger("-1")), pBI).multiply(c1BI).mod(pBI); 
    
    System.out.println("Plaintext : " + new String(plaintextBI.toByteArray(), StandardCharsets.UTF_8));
    

    with e.g. the following output:

    Ciphertext: adc32bbd23d80489db5843e26b26c58062a2369912915025574fd8598b8c72665e0a922ad8897719e1f9b0e3fb76e275ed15194534399781017e43c24a92cc77b13a256ff27e12667cc0f5876d1873368449b5a60ecc7a60a6b92f2640608f21dc86e7effe1dc4038b02b8c6c9d7ac03bd2e7d66d803d2a19f459ffeedfcff46
    Plaintext : abcdefgh
    c0        : adc32bbd23d80489db5843e26b26c58062a2369912915025574fd8598b8c72665e0a922ad8897719e1f9b0e3fb76e275ed15194534399781017e43c24a92cc77
    c1        : b13a256ff27e12667cc0f5876d1873368449b5a60ecc7a60a6b92f2640608f21dc86e7effe1dc4038b02b8c6c9d7ac03bd2e7d66d803d2a19f459ffeedfcff46
    Plaintext : abcdefgh
    

    Note that a key size of 512 bits is too small nowadays, see e.g. here, and a missing padding is insecure, e.g. here.