dalek-cryptography / x25519-dalek

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

example code for Transform ed25519 secret/public keys into x25519 secret/public keys #67

Closed gcxfd closed 3 years ago

gcxfd commented 3 years ago

I saw Transform ed25519 secret/public keys into x25519 secret/public keys , but I'm not familiar with cryptography , I don't kown how to do this .

is there any example code for do this ?

isislovecruft commented 3 years ago

Hi!

We do not recommend you do this. If you can at all escape from doing this, you absolutely should. You should never be reusing keys for both authentication and encryption. You especially should not be doing this without a very strong understanding of potential ramifications in the protocol you're working on. I'm sorry, I feel like I'm being a jerk here, but we've already spelled it out pretty explicitly how to accomplish this with our public API, and also that we don't support people doing it. I'm refusing to provide copy/paste code to further facilitate potentially bad practices.

gcxfd commented 3 years ago

https://github.com/hyperledger/ursa/blob/92d752100e6c8afde48e3406eaa585e1cb02b954/libursa/src/signatures/ed25519.rs#L38

I found code there

#[cfg(any(feature = "x25519", feature = "x25519_asm"))]
impl Ed25519Sha512 {
    /// Creates a curve25519 key from an ed25519 public key.
    ///
    /// Used to derive the public key for DH key exchange.
    ///
    /// # Example
    /// ```
    /// use ursa::signatures::ed25519::Ed25519Sha512;
    /// use ursa::signatures::SignatureScheme;
    ///
    /// let (pk, sk) = Ed25519Sha512::new().keypair(None).unwrap();
    /// let curve_pk = Ed25519Sha512::ver_key_to_key_exchange(&pk).unwrap();
    /// let curve_sk = Ed25519Sha512::sign_key_to_key_exchange(&sk).unwrap();
    /// ```
    pub fn ver_key_to_key_exchange(pk: &PublicKey) -> Result<PublicKey, CryptoError> {
        use curve25519_dalek::edwards::CompressedEdwardsY;

        // Verify it's a valid public key
        PK::from_bytes(&pk[..]).map_err(|e| CryptoError::ParseError(e.to_string()))?;
        // PublicKey is a CompressedEdwardsY in dalek. So we decompress it to get the
        // EdwardsPoint which can then be used convert to the Montgomery Form.
        let cey = CompressedEdwardsY::from_slice(&pk[..]);
        match cey.decompress() {
            Some(ep) => Ok(PublicKey(ep.to_montgomery().as_bytes().to_vec())),
            None => Err(CryptoError::ParseError(format!(
                "Invalid public key provided. Cannot convert to key exchange key"
            ))),
        }
    }

    /// Creates a curve25519 key from an ed25519 private key.
    ///
    /// Used to derive the private key for DH key exchange.
    ///
    /// # Example
    /// ```
    /// use ursa::signatures::ed25519::Ed25519Sha512;
    /// use ursa::signatures::SignatureScheme;
    ///
    /// let (pk, sk) = Ed25519Sha512::new().keypair(None).unwrap();
    /// let curve_pk = Ed25519Sha512::ver_key_to_key_exchange(&pk).unwrap();
    /// let curve_sk = Ed25519Sha512::sign_key_to_key_exchange(&sk).unwrap();
    /// ```
    pub fn sign_key_to_key_exchange(sk: &PrivateKey) -> Result<PrivateKey, CryptoError> {
        // Length is normally 64 but we only need the secret from the first half
        if sk.len() < 32 {
            return Err(CryptoError::ParseError(format!(
                "Invalid private key provided"
            )));
        }
        // hash secret
        let hash = sha2::Sha512::digest(&sk[..32]);
        let mut output = [0u8; 32];
        output.copy_from_slice(&hash[..32]);
        // clamp result
        let secret = x25519_dalek::StaticSecret::from(output);
        Ok(PrivateKey(secret.to_bytes().to_vec()))
    }
}
markg85 commented 2 years ago

Hi,

I'm giving it a shot here because the opinion of @isislovecruft seems quite fixed which makes me wonder what the correct approach should be.

In a normal flow with asynchronous encryption you have:

I'm probably missing steps, but if you agree that - in general terms - this is how it's conceptually working then let's go for that :)

Now ed25519 and x25519 are a bit different. ed25519 is used for the signing/verifying parts. x25519 is used for the encryption parts (or more specifically for secret sharing which should be a symmetric key .. you get the point)

That means in case of the (ed/x)25519 you don't have 1 "master" keypair. You'd have:

I perfectly get the mindset of key reusing to generally be considered bad practice in encryption land. But... given these "25519-variation", isn't reusing actually a good thing? If you reuse you can verify that a given ed25519 key was used to generate the x25519 shared secret, as the public key of both should "match" (be convertible).

Or to put it differently, how would you get the same logic as above (1 key pair for signing and encryption) using the 25519-variations? Only way i see for that is key reuse tbh. Without reuse you have 2 totally unrelated keypairs to manage.

Looking forward to your thoughts on this :)

TheButlah commented 1 year ago

We do not recommend you do this. If you can at all escape from doing this, you absolutely should. You should never be reusing keys for both authentication and encryption.

Does this advice still apply in light of the Thormarker paper that libsodium and the did:key spec reference?

Disclaimer: I have 0 encryption experience. Asking to avoid making a mistake - I want to use crypto_box to do as little custom crypto as possible. But I also need to sign messages, and have the keys as a public identifier. If I use a did:key via ed25519, the spec specifies a way to resolve the identifier to both a ed25519 and x25519 key (but unsure if its a good idea given your warning). Having a 32 byte key instead of 64 byte key would be important for performance reasons when its the public identifier of the account.

markg85 commented 1 year ago

The answer you're going to get @TheButlah (if any) is entirely going to depend on that persons perspective of security.

Let me express myself politely by saying that security researchers and people who want to have every potential security hole mitigated will probably always say key re-use is bad. And in that spirit, "reusing" ed25519 into a x25519 is probably seen as equally bad too.

Other people see the possibility of creating a x25519 out of a ed25519 as a very handy to have convenience feature of these keys.

It entirely depends on your case. For me, and i'm not a security researcher at all, i'd weigh this case in the following uses.

You could be in a situation where you have just 1 key and just have no need for the ephemeral nature of x25519. So you have a bit of a flow like: ed25519 -> x25519 -> <- x25519 <- ed25519 then by all means i'd reuse the heck out of it for sure :)

Or alternatively you could have a flow where you need to encrypt something every time against a new key. In that mechanism your ed25519 are your "master" keys and you should have a ephemeral x25519 every time.

I'm also still figuring all of this out over the years.

tarcieri commented 1 year ago

It's safe to use the same keys for both Ed25519 and X25519: https://eprint.iacr.org/2021/509.pdf

I would personally not consider it a great idea to convert from Edwards to Montgomery form:

tarcieri commented 1 year ago

Also, as it were, curve25519-dalek's implementation of Montgomery scalar multiplication converts back to Edwards form.

So when you start with an Ed25519 public key, convert it to Montgomery form, and then do D-H, curve25519-dalek is converting back to Edwards form first then back to Montgomery form, meaning you're effectively round-tripping from Edwards -> Montgomery -> Edwards -> Montgomery just to do D-H.