简体   繁体   中英

Not Generating Proper AES Key Size with ECDH KeyAgreement in Java

I'd like to be able to generate proper AES keys from an ECDH key agreement. However, when I generate the secret key, I get a key with an invalid length, usually 66 bits. Here's the error:

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)

And here is the relevant code that generates this exception:

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

I have a feeling that I'm not generating the ECDH keys properly (possibly due to the named curve that I chose), but otherwise, I'm stumped. Can anyone see where I may be doing something wrong?

EDIT: Almost forgot the rest of the output:

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

I've investigated this and I think it's a bug in the BC provider, or maybe two.

You can get your example to work if you choose a KDF-based algorithm and use an OID to identify the AES algorithm for the generated key:

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

Alternatively you could work around this by just truncating the original 66-byte SecretKey down to 16 (take the leading bytes, ie 0 thru 15), as this is what it should be doing but isn't.

We'll see what we can do about sorting this out in BC.

new org.bouncycastle.jce.provider.BouncyCastleProvider()替换"BC" ,并删除NoSuchProviderException

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM