使用Java中的ECDH KeyAgreement不生成正确的AES密钥大小

时间:2013-11-28 07:26:02

标签: java encryption bouncycastle

我希望能够从AES密钥协议生成正确的ECDH密钥。但是,当我生成密钥时,我得到一个长度无效的密钥,通常为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键(可能是由于我选择的命名曲线),但除此之外,我很难过。任何人都可以看到我可能做错了什么吗?

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

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

2 个答案:

答案 0 :(得分:1)

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

如果您选择基于KDF的算法并使用OID识别生成密钥的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),因为这是应该做的但不是。

我们将看到我们可以做些什么来解决这个问题。

答案 1 :(得分:0)

"BC"替换为new org.bouncycastle.jce.provider.BouncyCastleProvider(),然后移除NoSuchProviderException