Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Encrypt AES secret key with Elliptic Curve ElGamal

There is Alice and Bob. I want to realize the following process:

  1. Alice encrypts a text with AES and generates a secret key
  2. Alice encrypts this secret key with Bobs public key using Elliptic Curves with El Gamal
  3. Alice sends the encrypted text & encrypted secret key to Bob
  4. Bob decrypts the secret key with his private key
  5. Bob decrypts the text with the decrypted secret key
  6. Done

I am using the class ECElGamalEncryptor from bouncycastle. My problem is, that as far as I understand, this class encrypts a point on an Elliptic Curve using a public key but my AES secret key is not a ECPoint but a Hexadecimal.

Lets pretend I have this 128-Bit Secret Key for the AES encryption:

6D5A7134743777397A24432646294A40

And this is what I have so far:

import java.math.BigInteger;
import java.security.SecureRandom;

import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.asn1.sec.SECNamedCurves;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.ec.ECElGamalDecryptor;
import org.bouncycastle.crypto.ec.ECElGamalEncryptor;
import org.bouncycastle.crypto.ec.ECPair;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.math.ec.ECPoint;
class TestClass {

  public static void main(String[] argv) {

    // Get domain parameters for example curve secp256r1
    X9ECParameters ecp = SECNamedCurves.getByName("secp256r1");
    ECDomainParameters domainParams = new ECDomainParameters(ecp.getCurve(),
                                                             ecp.getG(), ecp.getN(), ecp.getH(),
                                                             ecp.getSeed());

    // Generate a private key and a public key
    AsymmetricCipherKeyPair keyPair;
    ECKeyGenerationParameters keyGenParams = new ECKeyGenerationParameters(domainParams, new SecureRandom());
    ECKeyPairGenerator generator = new ECKeyPairGenerator();
    generator.init(keyGenParams);
    keyPair = generator.generateKeyPair();

    ECPrivateKeyParameters privateKey = (ECPrivateKeyParameters) keyPair.getPrivate();
    ECPublicKeyParameters publicKey = (ECPublicKeyParameters) keyPair.getPublic();
    byte[] privateKeyBytes = privateKey.getD().toByteArray();


    // Get ECPoint Q from privateKey
    ECPoint Q = domainParams.getG().multiply(new BigInteger(privateKeyBytes));  

    //Initialize ECElGamalEncryptor
    ECElGamalEncryptor elgamalEn = new ECElGamalEncryptor();
    elgamalEn.init(publicKey);
    ECPair encrypted = elgamalEn.encrypt(Q);

    //Encryption
    ECElGamalDecryptor elgamalDe = new ECElGamalDecryptor();
    elgamalDe.init(privateKey);
    ECPoint original = elgamalDe.decrypt(encrypted);

  }
}

So I am able to initialize the ECElGamalEncryptor and to encrypt ECPoint Q with the public Key. But actually, I want to encrypt the AES Secret key and I have no idea what I have to do now.

like image 350
aslanj Avatar asked Oct 03 '19 17:10

aslanj


1 Answers

Let me try rephrasing a part of your question to make it much more clear along with some symbols necessary. The way you worded your scheme is slightly tedious to understand. However, as @JamesKPolk and @MaartenBodewes pointed out what you'd need for Elliptic Curve cryptography which supports encryption is an IES scheme called ECIES which can be obtained as a combination of the ECDH and a Symmetric encryption scheme like AES for example. So let's revisit the scheme you've been trying to implement with Alice and Bob.

Bootstrap

  • Alice and Bob generate their respective AES Keys which consist of a SecretKey and an IV. In this example we'll use AES256
  • Alice and Bob generate their corresponding EC KeyPairs and in some way share their PublicKeys so that each one has the knowledge of the other public key.
    • Alice has Bob's public key.
    • Bob has Alice's public key.

Scheme Desired

  1. Alice encrypts a plain text message m with AES to generate encrypted message em.
    • Before this can happen Alice generates an AES Key which contains the SecretKey used for encryption and an IV vector. In the code example that'll follow we'll call this tuple as an AESPair.
  2. Alice encrypts the SecretKey (SK) || IV message with Bobs public key using ECIES to obtain esk||iv.
    • Alice generates the SharedSecret using Alice's Private Key and Bob's Public Key. Let's call this SSK1
    • Bob can generate the SharedSecet using Bob's Private Key and Alice's Public Key. Let's call this SSK2
    • At this point SSK1==SSK2. You can find the reason for this here in the Decryption section of IES.
  3. Alice sends the encrypted text em & encrypted secret key & IV parameters necessary (esk||iv) to Bob.
  4. Bob decrypts the encrypted message containing the AES secret and IV (esk||iv) with his private key to obtain (SK || IV)
  5. Bob decrypts the encrypted text em with the obtained secret key in Step 4 to receive the original message Alice sent i.e. m
  6. Done

Code

Helper Functions

private final static char[] hexArray = "0123456789ABCDEF".toCharArray();

public static String convertBytesToHex(byte[] bytes) {
  char[] hexChars = new char[bytes.length * 2];
  for ( int j = 0; j < bytes.length; j++ ) {
    int v = bytes[j] & 0xFF;
    hexChars[j * 2] = hexArray[v >>> 4];
    hexChars[j * 2 + 1] = hexArray[v & 0x0F];
  }
  return new String(hexChars).toLowerCase();
}

public static byte[] hexStringToByteArray(String hexString){
  byte[] bytes = new byte[hexString.length() / 2];

  for(int i = 0; i < hexString.length(); i += 2){
    String sub = hexString.substring(i, i + 2);
    Integer intVal = Integer.parseInt(sub, 16);
    bytes[i / 2] = intVal.byteValue();
    String hex = "".format("0x%x", bytes[i / 2]);
  }
  return bytes;
}

ECC.java

public class ECC {
  // Both Alice and Bob agree upon this value in some manner before starting this protocol.
  public static byte[] iv = new SecureRandom().generateSeed(16);

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

  public static KeyPair generateKeyPair() throws InvalidAlgorithmParameterException, NoSuchProviderException, NoSuchAlgorithmException {
      ECNamedCurveParameterSpec parameterSpec = ECNamedCurveTable.getParameterSpec("secp256r1");
      KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("ECDH", "BC");

      keyPairGenerator.initialize(parameterSpec);

    return keyPairGenerator.generateKeyPair();
  }

  public static SecretKey generateSharedSecret(PrivateKey privateKey, PublicKey publicKey) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeyException {
      KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH", "BC");
      keyAgreement.init(privateKey);
      keyAgreement.doPhase(publicKey, true);

    return keyAgreement.generateSecret("AES");
  }

  public static byte[] encrypt(SecretKey key, byte[] plainTextBytes) throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException, InvalidKeyException, ShortBufferException, BadPaddingException, IllegalBlockSizeException {
      IvParameterSpec ivSpec = new IvParameterSpec(iv);
      Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");
      byte[] cipherText;

      cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
      cipherText = new byte[cipher.getOutputSize(plainTextBytes.length)];
      int encryptLength = cipher.update(plainTextBytes, 0, plainTextBytes.length, cipherText, 0);
      encryptLength += cipher.doFinal(cipherText, encryptLength);

      return cipherText;
  }

  public static byte[] decrypt(SecretKey key, byte[] cipherTextBytes) throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException, InvalidKeyException, ShortBufferException, BadPaddingException, IllegalBlockSizeException {
      Key decryptionKey = new SecretKeySpec(key.getEncoded(),
          key.getAlgorithm());
      IvParameterSpec ivSpec = new IvParameterSpec(iv);
      Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");
      byte[] plainText;

      cipher.init(Cipher.DECRYPT_MODE, decryptionKey, ivSpec);
      plainText = new byte[cipher.getOutputSize(cipherTextBytes.length)];
      int decryptLength = cipher.update(cipherTextBytes, 0, cipherTextBytes.length, plainText, 0);
      decryptLength += cipher.doFinal(plainText, decryptLength);

      return plainText;
  }
}

AES256.java

public class AES256 {

  public static AESPair generateKeyPair() throws NoSuchAlgorithmException {
    KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
    keyGenerator.init(256);

    SecretKey key = keyGenerator.generateKey();
    byte[] IV = new byte[16];
    SecureRandom random = new SecureRandom();
    random.nextBytes(IV);

    AESPair response = new AESPair(key, IV);
    return response;
  }

  public static byte[] encrypt(byte[] plainText, SecretKey key, byte[] IV) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), "AES");
    IvParameterSpec ivParameterSpec = new IvParameterSpec(IV);
    cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivParameterSpec);
    byte[] cipherText = cipher.doFinal(plainText);
    return cipherText;
  }

  public static byte[] decrypt(byte[] cipherText, SecretKey key, byte[] IV) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), "AES");
    IvParameterSpec ivSpec = new IvParameterSpec(IV);
    cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
    byte[] decryptedText = cipher.doFinal(cipherText);
    return decryptedText;
  }

  public static byte[] serializeSecretKey (SecretKey key) {
    return key.getEncoded();
  }

  public static SecretKey deserializeSecretKey (byte[] sk) {
    return new SecretKeySpec(sk, 0, sk.length, "AES");
  }

}

AESPair.java which is the corresponding helper for AES.

public class AESPair {
  private SecretKey key;
  private byte[] IV;

  public void setIV(byte[] IV) {
    this.IV = IV;
  }

  public void setKey(SecretKey key) {
    this.key = key;
  }

  public byte[] getIV() {
    return IV;
  }

  public SecretKey getKey() {
    return key;
  }

  public AESPair(SecretKey sk, byte[] ivBytes) {
    key = sk;
    IV = ivBytes;
  }

  // This takes in SK || IV for AES256 and creates the SecretKey object and corresponding IV byte array.
  public AESPair(byte[] skConcatIVBytes) {
    int total_bytes = skConcatIVBytes.length;
    // FOR AES256 the key is 32 bytes and the IV is 16 bytes
    byte[] sk = Arrays.copyOfRange(skConcatIVBytes, 0, 32);
    byte[] iv = Arrays.copyOfRange(skConcatIVBytes, 32, total_bytes);

    key = new SecretKeySpec(sk, 0, sk.length, Constant.AES);
    IV = iv;
  }
}

Now that we have the pieces we need, let's put together the scheme desired as a test.

@Test
public void test_scheme_ecc() throws NoSuchAlgorithmException, IllegalBlockSizeException, InvalidKeyException, BadPaddingException, InvalidAlgorithmParameterException, NoSuchPaddingException, NoSuchProviderException, ShortBufferException {
    String plainText = "plaintext message from alice to bob";
    System.out.println("Original plaintext message: " + plainText);

    AESPair aliceAESPair = AES256.generateKeyPair();
    AESPair bobAESPair = AES256.generateKeyPair();

    byte[] encryptedPlainTextMessageFromAlice = AES256.encrypt(plainText.getBytes(StandardCharsets.UTF_8), aliceAESPair.getKey(), aliceAESPair.getIV());
    System.out.println("Alice encrypted message : " + convertBytesToHex(encryptedPlainTextMessageFromAlice));

    // Necessary Key + IV information to reconstruct the key
    byte[] keyInformation = ByteBuffer.allocate(aliceAESPair.getKey().getEncoded().length + aliceAESPair.getIV().length)
        .put(aliceAESPair.getKey().getEncoded())
        .put(aliceAESPair.getIV())
        .array();
    System.out.println("Alice's SK || IV : " + convertBytesToHex(keyInformation));

    // Initialize two key pairs
    KeyPair aliceECKeyPair = ECC.generateKeyPair();
    KeyPair bobECKeyPair = ECC.generateKeyPair();

    System.out.println("Alice EC PK : " + convertBytesToHex(aliceECKeyPair.getPublic().getEncoded()));
    System.out.println("Bob EC PK   : " + convertBytesToHex(bobECKeyPair.getPublic().getEncoded()));

    // Create two AES secret keys to encrypt/decrypt the message
    SecretKey aliceSharedSecret = ECC.generateSharedSecret(aliceECKeyPair.getPrivate(), bobECKeyPair.getPublic());
    System.out.println("Alice Shared Secret Key : " + convertBytesToHex(aliceSharedSecret.getEncoded()));

    // Encrypt the message using 'aliceSharedSecret'
    byte[] cipherText = ECC.encrypt(aliceSharedSecret, keyInformation);
    System.out.println("Encrypted cipher text: " + convertBytesToHex(cipherText));

    // Decrypt the message using 'bobSharedSecret'
    SecretKey bobSharedSecret = ECC.generateSharedSecret(bobECKeyPair.getPrivate(), aliceECKeyPair.getPublic());
    System.out.println("Bob Shared Secret Key : " + convertBytesToHex(bobSharedSecret.getEncoded()));

    byte[] decrypted_EncryptedTextFromAlice = ECC.decrypt(bobSharedSecret, cipherText);
    System.out.println("Decrypted cipher text to obtain Alice generated secret key: " + convertBytesToHex(decrypted_EncryptedTextFromAlice));

    AESPair reconstructedKey = new AESPair(decrypted_EncryptedTextFromAlice);

    byte[] decryptedText = AES256.decrypt(encryptedPlainTextMessageFromAlice, reconstructedKey.getKey(), reconstructedKey.getIV());
    System.out.println("Decrypted plain text message : " + new String(decryptedText));
}

Here's a run from the test:

Original plaintext message: plaintext message from alice to bob
Alice encrypted message : 9d273ea89ab6b8d170941d2578f0d4e11b1d6a3be199189dbbf4a5ff64fbf1348edbb459e38dac17aad6a68b1a95300f
Alice's SK || IV : 857248ab0171a652926fcc46353831965dd2d98cb4920de7d629c07250bc60fb60306f67d2c44e725b2e8344d970b34b
Alice EC PK : 3059301306072a8648ce3d020106082a8648ce3d030107034200042499c59fea8ab010782444825c7872c04407a4f034d907ca9014b9f8d4be1226cb9fc9eff57f8e0e7b8e1aa83290c6d6c3a56aeeef3490e1e55476e94abb4128
Bob EC PK   : 3059301306072a8648ce3d020106082a8648ce3d03010703420004d91562882f30b54177449941b9812b17ac5a59d2b80cc5fbaef833426152623dfb17965ba9897edd5da26b4044071882f8ae53ce37c24f0ea5b55b7e42b689ac
Alice Shared Secret Key : 3fa7b4ae68ff51296293b69ac1b0d8d139bf3f6a60732a124734a19f2987b772
Encrypted cipher text: 758506913bee96816f7a3190720ce7f01ddb8acbeaef1e669af420c04036a4b2ab446ce2a2bee62f603a0400b9076c927f2eeffc2a4cec0ffad756fed19dc6d9
Bob Shared Secret Key : 3fa7b4ae68ff51296293b69ac1b0d8d139bf3f6a60732a124734a19f2987b772
Decrypted cipher text to obtain Alice generated secret key: 857248ab0171a652926fcc46353831965dd2d98cb4920de7d629c07250bc60fb60306f67d2c44e725b2e8344d970b34b
Decrypted plain text message : plaintext message from alice to bob
BUILD SUCCESSFUL in 1s

Explanation of the test case code

  1. An AES256 key and IV is generated for Alice
  2. A plain text message "plain text from alice to bob" is encrypted by Alice using the key generated in step 1.
  3. A new byte array is created with the concatenation of key || IV of Alice's key. This is the message that should be encrypted and sent to Bob via ECIES.
  4. Alice and Bob generate their Elliptic Curve KeyPairs and we assume they known each others' public keys. The key generation happens in the ECC.generateKeyPair() method.
  5. Alice uses Bob's public key and Alice's private key to generate a Shared Secret which is a symmetric SecretKey
  6. Alice encrypts the message in step 3 using the shared secret key in step 5 which creates the encrypted message that needs to be sent to Bob.
  7. Bob receives the messages (Step 6 and Step 2) and computes the shared secret key using Bob's private key and Alice's public key.
  8. Bob uses the key constructed in step 7 to decrypt the encrypted message that was received from Alice.
  9. Now Bob knows the AES Key that Alice used in Step 2 to encrypt the original plain text message. The message obtained after decryption is a byte[] which is converted to an AESPair object creating the SecretKey and IV necessary.
  10. Bob decrypts the encrypted message in Step 2 and recovers the original message "plain text from alice to bob"

Hope this helps. Do let me know if you wanted some clarifications.

like image 125
Sudheesh Singanamalla Avatar answered Oct 05 '22 14:10

Sudheesh Singanamalla