java - 未在 Java 中使用 ECDH KeyAgreement 生成正确的 AES key 大小

标签 java encryption bouncycastle

我希望能够根据 ECDH key 协议(protocol)生成正确的 AES key 。但是,当我生成 key 时,我得到的 key 长度无效,通常为 66 位。错误如下:

java.security.InvalidKeyException: Key length not 128/192/256 bits.
Encrypted cipher text: null
    at org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineInit(Unknown Source)
    at javax.crypto.Cipher.init(Cipher.java:1346)
    at javax.crypto.Cipher.init(Cipher.java:1282)
    at Test.encryptString(Test.java:99)
    at Test.main(Test.java:44)

这是生成此异常的相关代码:

public static byte[] iv = new SecureRandom().generateSeed(16);

public static void main(String[] args) {
    String plainText = "Look mah, I'm a message!";
    System.out.println("Original plaintext message: " + plainText);

    // Initialize two key pairs
    KeyPair keyPairA = generateECKeys();
    KeyPair keyPairB = generateECKeys();

    // Create two AES secret keys to encrypt/decrypt the message
    SecretKey secretKeyA = generateSharedSecret(keyPairA.getPrivate(),
            keyPairB.getPublic());
    System.out.println(bytesToHex(secretKeyA.getEncoded()));
    SecretKey secretKeyB = generateSharedSecret(keyPairB.getPrivate(),
            keyPairA.getPublic());
    System.out.println(bytesToHex(secretKeyB.getEncoded()));

    // Encrypt the message using 'secretKeyA'
    String cipherText = encryptString(secretKeyA, plainText);
    System.out.println("Encrypted cipher text: " + cipherText);

    // Decrypt the message using 'secretKeyB'
    String decryptedPlainText = decryptString(secretKeyB, cipherText);
    System.out.println("Decrypted cipher text: " + decryptedPlainText);
}

public static KeyPair generateECKeys() {
    try {
        ECNamedCurveParameterSpec parameterSpec = ECNamedCurveTable.getParameterSpec("secp521r1");
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(
                "ECDH", "BC");

        keyPairGenerator.initialize(parameterSpec);

        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        System.out.println("Private key length: "
                + keyPair.getPrivate().getEncoded().length);
        System.out.println("Public key length: "
                + keyPair.getPublic().getEncoded().length);
        return keyPair;
    } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException
            | NoSuchProviderException e) {
        e.printStackTrace();
        return null;
    }
}

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

        SecretKey key = keyAgreement.generateSecret("AES");
        System.out.println("Shared key length: " + key.getEncoded().length);
        return key;
    } catch (InvalidKeyException | NoSuchAlgorithmException
            | NoSuchProviderException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
        return null;
    }
}

public static String encryptString(SecretKey key, String plainText) {
    try {
        IvParameterSpec ivSpec = new IvParameterSpec(iv);
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");
        byte[] plainTextBytes = plainText.getBytes("UTF-8");
        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 bytesToHex(cipherText);
    } catch (NoSuchAlgorithmException | NoSuchProviderException
            | NoSuchPaddingException | InvalidKeyException
            | InvalidAlgorithmParameterException
            | UnsupportedEncodingException | ShortBufferException
            | IllegalBlockSizeException | BadPaddingException e) {
        e.printStackTrace();
        return null;
    }
}

public static String decryptString(SecretKey key, String cipherText) {
    try {
        Key decryptionKey = new SecretKeySpec(key.getEncoded(),
                key.getAlgorithm());
        IvParameterSpec ivSpec = new IvParameterSpec(iv);
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");
        byte[] cipherTextBytes = hexToBytes(cipherText);
        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 new String(plainText, "UTF-8");
    } catch (NoSuchAlgorithmException | NoSuchProviderException
            | NoSuchPaddingException | InvalidKeyException
            | InvalidAlgorithmParameterException
            | IllegalBlockSizeException | BadPaddingException
            | ShortBufferException | UnsupportedEncodingException e) {
        e.printStackTrace();
        return null;
    }
}

public static String bytesToHex(byte[] data, int length) {
    String digits = "0123456789ABCDEF";
    StringBuffer buffer = new StringBuffer();

    for (int i = 0; i != length; i++) {
        int v = data[i] & 0xff;

        buffer.append(digits.charAt(v >> 4));
        buffer.append(digits.charAt(v & 0xf));
    }

    return buffer.toString();
}

public static String bytesToHex(byte[] data) {
    return bytesToHex(data, data.length);
}

public static byte[] hexToBytes(String string) {
    int length = string.length();
    byte[] data = new byte[length / 2];
    for (int i = 0; i < length; i += 2) {
        data[i / 2] = (byte) ((Character.digit(string.charAt(i), 16) << 4) + Character
                .digit(string.charAt(i + 1), 16));
    }
    return data;
}

我有一种感觉,我没有正确生成 ECDH key (可能是由于我选择的命名曲线),但除此之外,我很困惑。谁能看到我可能做错了什么?

编辑:几乎忘记了输出的其余部分:

Original plaintext message: Look mah, I'm a message!
Private key length: 1106
Public key length: 158
Private key length: 1107
Public key length: 158
Shared key length: 66
0147A5780737C5C0D7457C503D4036AC7BBED53D5536A32D6BE8713E6DB4FE0A549AF20514C223D630426292A8EDB512EBD50726A130FFA4AEE96A0EC2A6F9D4C3A0
Shared key length: 66
0147A5780737C5C0D7457C503D4036AC7BBED53D5536A32D6BE8713E6DB4FE0A549AF20514C223D630426292A8EDB512EBD50726A130FFA4AEE96A0EC2A6F9D4C3A0

最佳答案

我对此进行了调查,我认为这是 BC 提供商的一个错误,或者可能是两个错误。

如果您选择基于 KDF 的算法并使用 OID 来识别生成 key 的 AES 算法,则可以使示例正常工作:

        ...
        KeyAgreement keyAgreement = KeyAgreement.getInstance(X9ObjectIdentifiers.dhSinglePass_stdDH_sha1kdf_scheme.getId(), "BC");
        ...
        SecretKey key = keyAgreement.generateSecret(NISTObjectIdentifiers.id_aes128_CBC.getId());
        ...

或者,您可以通过将原始 66 字节 SecretKey 截断为 16(取前导字节,即 0 到 15)来解决此问题,因为这是它应该做的事情,但没有这样做。

我们将看看如何在 BC 解决这个问题。

关于java - 未在 Java 中使用 ECDH KeyAgreement 生成正确的 AES key 大小,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20260097/

相关文章:

java - 引入String来定义类型

java - 向 Spring Data Repository 添加自定义功能的问题

javascript - 在 JavaScript 中加密大文件并高效存储在 S3 中

java - Android KeyStore 系统 - 保存 key 对?

java - BouncyCaSTLe:从证书签名请求中提取公钥信息

java - SpongycaSTLe 提供程序无法在 Android 上运行

java - 用 BouncycaSTLe jar 替换 JCE

java - 来自给定 wsdl 文件的 "java form generator"

php - 使用河豚加密

java - 如何更改 Apache thrift 帧大小