dalek-cryptography / x25519-dalek

X25519 elliptic curve Diffie-Hellman key exchange in pure-Rust, using curve25519-dalek.
BSD 3-Clause "New" or "Revised" License
326 stars 132 forks source link

`x25519_dalek::PublicKey::from` generates a different public key compared to OpenSSL when using the same private key #90

Closed alwitt closed 1 year ago

alwitt commented 2 years ago

I am testing this crate (v1.2.0) using a ED25519 key pair generated by OpenSSL 1.1.1o FIPS 3 May 2022, and checking for correct DH shared secret generation against a x25519_dalek::EphemeralSecret based key pair.

The issue I am seeing is that starting from the private key generated by OpenSSL, the pubic key generated by PublicKey::from for that private key is different from what OpenSSL generated. Am I using the APIs correctly? What is the correct way to use existing key pairs?

// Parse existing private key
let alice_priv_key = pkcs8::KeypairBytes::from_pkcs8_pem(PRIV_KEY).unwrap();
// Parse existing public key
let alice_pub_key = pkcs8::PublicKeyBytes::from_public_key_pem(PUB_KEY).unwrap();

// Define Alice's ed25519 pair
let alice_secret = StaticSecret::from(alice_priv_key.secret_key);
let alice_public = PublicKey::from(&alice_secret);

let parsed_existing_public = PublicKey::from(alice_pub_key.to_bytes());
// This should not fail
assert_eq!(alice_public.to_bytes()[..], parsed_existing_public.to_bytes()[..]);

The complete test code

#[cfg(test)]
mod tests {
    fn init() {
        let _ = env_logger::builder().is_test(true).try_init();
    }

    /* Creating the ED25519 key pairs
    $ openssl genpkey -algorithm ED25519 > private.pem
    $ openssl pkey -outform PEM -pubout -in private.pem > public.pem
    */

    static PUB_KEY: &str = "-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEADOuQ5lqOanpHOtLV2gqqcYuYkJrdpNueHJ7M9ejY3M0=
-----END PUBLIC KEY-----";

    static PRIV_KEY: &str = "-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEINLDN5YuEgo6Z5G+ww0kTv33KyQrPN1O+vdeet74+cVm
-----END PRIVATE KEY-----";

    mod test_ed25519_dh {
        use ed25519::pkcs8;
        use ed25519::pkcs8::{DecodePrivateKey, DecodePublicKey, EncodePublicKey};
        use rand_core::OsRng;
        use x25519_dalek::StaticSecret;
        use x25519_dalek::{EphemeralSecret, PublicKey};

        #[test]
        fn test_ed25519_dh() {
            super::init();

            // Parse existing private key
            let mut alice_priv_key = pkcs8::KeypairBytes::from_pkcs8_pem(super::PRIV_KEY).unwrap();
            // Parse existing public key
            let alice_pub_key = pkcs8::PublicKeyBytes::from_public_key_pem(super::PUB_KEY).unwrap();

            // Define Alice's ed25519 pair
            let alice_secret = StaticSecret::from(alice_priv_key.secret_key);
            let alice_public = PublicKey::from(&alice_secret);

            // Dump the generated public key to stdout
            alice_priv_key.public_key = Some(alice_public.to_bytes());
            let alice_public_other_form = pkcs8::PublicKeyBytes::try_from(&alice_priv_key).unwrap();
            log::debug!(
                "Alice generated public key as PEM:\n{}",
                alice_public_other_form
                    .to_public_key_pem(base64ct::LineEnding::LF)
                    .unwrap()
            );
            /* This is not what OpenSSL generated, shown in PUB_KEY
               -----BEGIN PUBLIC KEY-----
               MCowBQYDK2VwAyEAUSd2eTv0CdgCpnccB3re+kxjo9miMQV9Di9SFHc/PQ4=
               -----END PUBLIC KEY-----
            */

            let parsed_existing_public = PublicKey::from(alice_pub_key.to_bytes());
            // This should not fail
            assert_eq!(
                alice_public.to_bytes()[..],
                parsed_existing_public.to_bytes()[..]
            );

            // Create Bob's ed25519 pair
            let bob_secret = EphemeralSecret::new(OsRng);
            let bob_public = PublicKey::from(&bob_secret);

            // Define the shared secrets
            let alice_shared_secret = alice_secret.diffie_hellman(&bob_public);
            let bob_shared_secret = bob_secret.diffie_hellman(&alice_public);

            assert_eq!(
                alice_shared_secret.as_bytes()[..],
                bob_shared_secret.as_bytes()[..]
            );
        }
    }
}
shampoofactory commented 1 year ago

I'm no cryptographer, but should we not be testing with X25519 key pairs as opposed to Ed25519?

$ openssl genpkey -algorithm X25519 -out prv.pem
$ openssl pkey -in prv.pem -pubout -out pub.pem

Quote: 'The public key representations are related but not the same. They cannot be used interchangeably without additional processing.' Source: https://crypto.stackexchange.com/questions/76156/public-key-generation-for-ed25519-vs-x25519 Author: Frank Denis

tarcieri commented 1 year ago

Yeah, it seems like the code example in the OP is using ed25519::pkcs8, which is for the Edwards form of Curve25519.

X25519 uses the Montgomery form. It's possible to convert from the Edwards form to the Montgomery form, but they are distinct.

alwitt commented 1 year ago

@tarcieri thank you for clarifying. I am glad to see there are activities in this repo again.