使用椭圆曲线ElGamal加密AES密钥

asl*_*anj 5 java cryptography bouncycastle elliptic-curve elgamal

有爱丽丝和鲍勃。我想实现以下过程:

  1. 爱丽丝使用AES加密文本并生成密钥
  2. 爱丽丝使用El Gamal的椭圆曲线使用Bobs公钥对该秘密密钥进行加密
  3. 爱丽丝将加密的文本和加密的密钥发送给鲍勃
  4. 鲍勃用他的私钥解密密钥
  5. 鲍勃使用解密的密钥解密文本
  6. 完成了

我正在使用来自Bouncycastle的ECElGamalEncryptor类。我的问题是,据我所知,此类使用公共密钥对椭圆曲线上的点进行加密,但是我的AES秘密密钥不是ECPoint,而是十六进制。

假设我有此128位密钥用于AES加密:

6D5A7134743777397A24432646294A40
Run Code Online (Sandbox Code Playgroud)

这就是我到目前为止所拥有的:

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);

  }
}
Run Code Online (Sandbox Code Playgroud)

因此,我能够初始化ECElGamalEncryptor并使用公共密钥加密ECPointQ。但是实际上,我想加密AES秘密密钥,我不知道现在该做什么。

Sud*_*lla 5

让我尝试重新表述您问题的一部分,使其更加清晰,并附上一些必要的符号。你表达你的计划的方式有点难以理解。但是,正如@JamesKPolk 和@MaartenBodewes 指出的那样,支持加密的椭圆曲线加密需要一种称为 ECIES 的 IES 方案,它可以作为 ECDH 和对称加密方案(例如 AES)的组合获得。因此,让我们重新审视您一直试图与 Alice 和 Bob 一起实施的方案。

引导程序

  • Alice 和 Bob 生成他们各自的 AES 密钥,由 aSecretKey和 an 组成IV。在这个例子中,我们将使用AES256
  • Alice 和 Bob 生成他们对应的 EC 密钥对,并以某种方式共享他们的公钥,以便每个人都知道另一个公钥。
    • Alice 有 Bob 的公钥。
    • Bob 有 Alice 的公钥。

所需方案

  1. Alicem使用 AES加密纯文本消息以生成加密消息e m
    • 在这发生之前,Alice 生成一个 AES 密钥,其中包含SecretKey用于加密的密钥和IV向量。在接下来的代码示例中,我们将此元组称为AESPair.
  2. AliceSecretKey (SK) || IV使用 ECIES 使用 Bobs 公钥加密消息以获得e sk||iv
    • AliceSharedSecret使用 Alice 的私钥和 Bob 的公钥生成。我们称之为 SSK 1
    • Bob 可以SharedSecet使用 Bob 的私钥和 Alice 的公钥生成 。我们称之为 SSK 2
    • 此时 SSK 1 ==SSK 2。您可以在IES的解密部分找到原因。
  3. Alice 将加密的文本e m和加密的秘密密钥和必要的 IV 参数(e sk||iv)发送给 Bob。
  4. Bob用他的私钥解密包含 AES 秘密和 IV ( e sk||iv )的加密消息以获得(SK || IV)
  5. Bob用在步骤 4 中获得的密钥解密密文e m以接收 Alice 发送的原始消息 ie m
  6. 完毕

代码

辅助函数

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;
}
Run Code Online (Sandbox Code Playgroud)

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;
  }
}
Run Code Online (Sandbox Code Playgroud)

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");
  }

}
Run Code Online (Sandbox Code Playgroud)

AESPair.java是 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;
  }
}
Run Code Online (Sandbox Code Playgroud)

现在我们有了我们需要的部分,让我们把所需的方案放在一起作为测试。

@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));
}
Run Code Online (Sandbox Code Playgroud)

这是测试的运行结果:

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
Run Code Online (Sandbox Code Playgroud)

测试用例代码说明

  1. 生成 AES256 密钥和 IV Alice
  2. "plain text from alice to bob"Alice 使用在步骤 1 中生成的密钥对纯文本消息进行加密。
  3. 一个新的字节数组是通过连接key || IVAlice 的密钥创建的。这是应该加密并通过 ECIES 发送给 Bob 的消息。
  4. Alice 和 Bob 生成他们的椭圆曲线密钥对,我们假设他们知道彼此的公钥。密钥生成发生在ECC.generateKeyPair()方法中。
  5. Alice 使用 Bob 的公钥和 Alice 的私钥生成Shared Secret一个对称的SecretKey
  6. Alice 使用步骤 5 中的共享密钥对步骤 3 中的消息进行加密,这将创建需要发送给 Bob 的加密消息。
  7. Bob 接收消息(步骤 6 和步骤 2)并使用 Bob 的私钥和 Alice 的公钥计算共享密钥。
  8. Bob 使用在步骤 7 中构造的密钥来解密从 Alice 收到的加密消息。
  9. 现在 Bob 知道 Alice 在步骤 2 中用来加密原始纯文本消息的 AES 密钥。解密后获得的消息是 a byte[],它被转换为AESPair创建SecretKeyIV必要的对象。
  10. Bob 对步骤 2 中的加密消息进行解密并恢复原始消息 "plain text from alice to bob"

希望这可以帮助。如果您想要一些说明,请告诉我。