简体   繁体   中英

Signing using Curve25519 in Crypto++

I am implementing Curve25519 in one of my projects. I thought I can combine this with HMAC, CMAC or another algorithm to sign and verify. The problem is that Curve25519 is not generating the same shared keys.

I don't understand very much about cryptography and I don't know if I am doing something wrong or simply that I cannot combine Curve25519 with HMAC.

Here is the test code that I prepare.

#include "xed25519.h"
using CryptoPP::x25519;
#include "donna.h"
using CryptoPP::Donna::curve25519_mult;
using CryptoPP::Donna::ed25519_sign;
using CryptoPP::Donna::ed25519_sign_open;
using CryptoPP::Donna::ed25519_publickey;

#include "filters.h"
#include "osrng.h"

#include "cryptlib.h"
#include "files.h"
#include "hex.h"
#include "sha.h"
#include "hmac.h"
using namespace std;

static const int SECRET_KEYLENGTH=32;

static const int PRIVATE_KEYLENGTH=128;
static const int PUBLIC_KEYLENGTH=32;
#include <string>
#include <iostream>

int main(int argc, char* argv[])
{    
    using namespace CryptoPP;

    AutoSeededRandomPool prng, prng2;
    byte *aux ;// new byte[PRIVATE_KEYLENGTH];
    byte privateKey[PUBLIC_KEYLENGTH];
    byte publicKey[PUBLIC_KEYLENGTH];

    // Node 1
    x25519 x(privateKey, publicKey) ; //(   const byte  y[PUBLIC_KEYLENGTH],const byte  x[SECRET_KEYLENGTH] 

    cout << "1- Generating private key " << endl;
    aux = new byte;

    x.GeneratePrivateKey(prng, privateKey);
    Integer intPrvK(privateKey, sizeof(privateKey));
    cout << "Private key created " <<  intPrvK  << endl;

    cout << "1- Generating public key " << endl;
    //void  GeneratePublicKey (RandomNumberGenerator &rng, const byte *privateKey, byte *publicKey) const
    if(curve25519_mult(privateKey, publicKey) == 0 ){
        Integer intPubK(publicKey, sizeof(publicKey));
        cout << "1- Public key created " <<  intPubK  << endl;
        //cout << "1- The new public key is " << privateKey << endl;
    }
    else
        cout << "curve25519_mult did not work " << endl;


    //Node 2
    byte privateKey2[PUBLIC_KEYLENGTH];
    byte publicKey2[PUBLIC_KEYLENGTH];
    x25519 y(privateKey2, publicKey2) ; //( const byte  y[PUBLIC_KEYLENGTH],const byte  x[SECRET_KEYLENGTH] 
    cout << "2- Generating private key " << endl;
    aux = new byte;

    y.GeneratePrivateKey(prng2, privateKey2);
    Integer intPrvK2(privateKey2, sizeof(privateKey2));
    cout << "2- Private key created " <<  intPrvK2  << endl;

    cout << "2- Generating public key " << endl;
    //void  GeneratePublicKey (RandomNumberGenerator &rng, const byte *privateKey, byte *publicKey) const
    if(curve25519_mult(privateKey2, publicKey2) == 0 ){
        Integer intPubK2(publicKey2, sizeof(publicKey2));
        cout << "2- Public key created " <<  intPubK2  << endl;
        //cout << "2- The new public key is " << privateKey2 << endl;
    }
    else
        cout << "2- curve25519_mult did not work " << endl;            

    cout << "\nGenerations of shared keys" << endl; 
    /*int curve25519_mult   (   byte    sharedKey[32],
        const byte  secretKey[32],
        const byte  othersKey[32] 
        )   */
    byte sharedKey1_2[PUBLIC_KEYLENGTH];
    byte sharedKey2_1[PUBLIC_KEYLENGTH];
    if( curve25519_mult(sharedKey1_2, privateKey, publicKey2) == 0){
        Integer intSharedKey1_2(sharedKey1_2, sizeof(sharedKey1_2));
        cout << "1- Shared key created " << intSharedKey1_2 << endl;
    }

    if( curve25519_mult(sharedKey2_1, privateKey2, publicKey) == 0){
        Integer intSharedKey2_1(sharedKey2_1, sizeof(sharedKey2_1));
        cout << "2- Shared key created " << intSharedKey2_1 << endl;
    }

    // We have two keys each.

    string plain = "\n\nHMAC Test";
    string mac, encoded;

    /*********************************\
    \*********************************/
    cout << "plain text: " << plain << endl;

    /*********************************\
    \*********************************/

    try
    {
        HMAC< SHA256 > hmac(sharedKey1_2, PUBLIC_KEYLENGTH);

        StringSource ss2(plain, true, 
            new HashFilter(hmac,
                new StringSink(mac)
            ) // HashFilter      
        ); // StringSource
    }
    catch(const CryptoPP::Exception& e)
    {
        cerr << e.what() << endl;
        exit(1);
    }

    /*********************************\
    \*********************************/

    // Pretty print
    encoded.clear();
    StringSource ss3(mac, true,
        new HexEncoder(
            new StringSink(encoded)
        ) // HexEncoder
    ); // StringSource

    cout << "hmac: " << encoded << endl;

    try
    {
        HMAC< SHA256 > hmac2(sharedKey2_1, PUBLIC_KEYLENGTH);
        const int flags = HashVerificationFilter::THROW_EXCEPTION | HashVerificationFilter::HASH_AT_END;

        StringSource(plain + mac, true, 
            new HashVerificationFilter(hmac2, NULL, flags)
        ); // StringSource

        cout << "Verified message" << endl;
    }
    catch(const CryptoPP::Exception& e)
    {
        cerr << e.what() << endl;

    }
    return 0;
}

EDIT: Here is the output of this code:

1- Generating private key 
Private key created 3951427468589058657788500055898583055730859037456996206614247149081707227760.
1- Generating public key 
1- Public key created 2713877106980505211026290261997698325438191786766062178625865092937394618368.
2- Generating private key 
2- Private key created 58089620826126204922773651760985512282935010454438059044416143831910823682427.
2- Generating public key 
2- Public key created 1185077373537344710091841384487531087158005785833397747712.

Generations of shared keys
1- Shared key created 32717475549536125870454478996763331991259932599267432219938737089203052157444.
2- Shared key created 83438083910146518364399797164490155462911710345063602550172142504835353991253.
plain text: 

HMAC Test
hmac: 27C84FED802319639DF86D36E43090666D6CB20F556778B90819087BC55C2249
HashVerificationFilter: message hash or MAC not valid

I hope that any of you can explain it to me.

Thanks in advance!!

The problem is that Curve25519 is not generating the same shared keys...

curve25519 describes the underlying field. x25519 is key agreement over the field. Each run of the protocol produces a new shared secret. That's because each time the protocol is run a new set of random parameters are used.

Here is how you perform key agreement with x25519 . x25519 is Bernstein's key agreement scheme using curve25519. The sample code below was taken from the Crypto++ wiki .

First, create some ephemeral keys:

AutoSeededRandomPool rndA, rndB;
x25519 ecdhA(rndA), ecdhB(rndB);

SecByteBlock privA(ecdhA.PrivateKeyLength());
SecByteBlock pubA(ecdhA.PublicKeyLength());
ecdhA.GenerateKeyPair(rndA, privA, pubA);

SecByteBlock privB(ecdhB.PrivateKeyLength());
SecByteBlock pubB(ecdhB.PublicKeyLength());
ecdhB.GenerateKeyPair(rndB, privB, pubB);

Second, set up shared secret buffer:

SecByteBlock sharedA(ecdhA.AgreedValueLength());
SecByteBlock sharedB(ecdhB.AgreedValueLength());

Third, perform the agreement protocol:

if(!ecdhA.Agree(sharedA, privA, pubB))
    throw std::runtime_error("Failed to reach shared secret (1)");

if(!ecdhB.Agree(sharedB, privB, pubA))
    throw std::runtime_error("Failed to reach shared secret (2)");

Finally, you can inspect the keys:

HexEncoder encoder(new FileSink(std::cout));

std::cout << "Shared secret (A): ";
StringSource(sharedA, sharedA.size(), true, new Redirector(encoder));
std::cout << std::endl;

std::cout << "Shared secret (B): ";
StringSource(sharedB, sharedB.size(), true, new Redirector(encoder));
std::cout << std::endl;

A sample output is like the following.

$ ./test.exe
Shared secret (A): B5C105BC3B685869AFBDFE64F15D27D6D0EAAA1A22F03B45B86E09FC76522450
Shared secret (B): B5C105BC3B685869AFBDFE64F15D27D6D0EAAA1A22F03B45B86E09FC76522450

The "create some ephemeral keys..." has a lot of hand waiving. You still need to send the public part of the ephemeral key to the other party. And the ephemeral public key sent to the other party should be signed so the other party knows it is authentic.


... to sign and verify...

Here is how you sign with ed25519 signature scheme. ed25519 is Bernstein's signature scheme using curve25519. The sample code below was taken from the Crypto++ wiki .

First, create your signing key:

ed25519::Signer signer;
signer.AccessPrivateKey().GenerateRandom(prng);

Second, save your signing key:

FileSink fs("private.key.bin");
signer.GetPrivateKey().Save(fs);

Third, sign a message with your private key:

AutoSeededRandomPool prng;
HexEncoder encoder(new FileSink(std::cout));

std::string message = "Yoda said, Do or do not. There is no try.";
std::string signature;

// Determine maximum signature size
size_t siglen = signer.MaxSignatureLength();
signature.resize(siglen);

// Sign, and trim signature to actual size
siglen = signer.SignMessage(prng, (const byte*)&message[0], message.size(), (byte*)&signature[0]);
signature.resize(siglen);

// Print signature to stdout
std::cout << "Signature: ";
StringSource(signature, true, new Redirector(encoder));
std::cout << "\n" << std::endl;

A sample output is like the following.

$ ./test.exe
Signature: B8EABDAA754BBCDC0B11ADE1FBA52CE39CD52FF42DE95E44CA6103652171468B63446
81DFB09F0D556EBF01BE43064D90C76711D9E1FF0FD3C41AF843DF17909

You can save your public key with the following code. Then, give your public key to others.

ed25519::Signer signer;
...

ed25519::Verifier verifier(signer);

FileSink fs("public.key.bin");
verifier.GetPublicKey().Save(fs);

... I thought I can combine this with HMAC, CMAC or another algorithm [for signing] ...

I'm not sure what to make of the scheme you are proposing, or the use of HMAC and CMAC. As @Maarten notes in the comments you don't describe what you are trying to do or state the algorithm. I'm going to leave that alone.

Usually what happens in your use case is, you arrive at a shared secret using Diffie-Hellman. Then you derive a couple of keys for a block cipher or stream cipher and a MAC. You usually do this with something like HDKF . Finally, you key the cipher and mac, and then you perform the bulk encryption.

I will hazard a proposal that once you have a shared secret with ecdhA.Agree(sharedA, privA, pubB) and/or ecdhB.Agree(sharedB, privB, pubA) , derive a key using HKDF and then use the derived key to key ChaCha20Poly1305 or XChaCha20Poly1305 (or another authenticated encryption mode cipher)

When using ChaCha20Poly1305 or XChaCha20Poly1305 , each message should (must!) get a unique nonce. Just run a counter and increment it after each message.

Finally, I got a solution thanks to the answer of jww. The problem was as I expected in the key generation, probably the code was missing the Agreed part between the keys, although I do not know for sure how to solve the problem with the previous functions.

Here there is an example of working code implementing HMAC signature with x25519 key exchange.

//g++ -g3 -ggdb -O0  Curve25519_HMAC_2.cpp -o Curve25519_HMAC_2.exe -lpthread -I/usr/local/include/cryptopp -L/usr/local/lib -l cryptopp
//g++ -DNDEBUG -g -g3 -O2 -Wall -Wextra -o Curve25519_HMAC_2  Curve25519_HMAC_2.cpp -I/usr/local/include/cryptopp -L/usr/local/lib -l cryptopp


#include "xed25519.h"
using CryptoPP::x25519;
#include "donna.h"
using CryptoPP::Donna::curve25519_mult;
using CryptoPP::Donna::ed25519_sign;
using CryptoPP::Donna::ed25519_sign_open;
using CryptoPP::Donna::ed25519_publickey;

#include "filters.h"
#include "osrng.h"

#include "cryptlib.h"
#include "files.h"
#include "hex.h"
#include "sha.h"
#include "hmac.h"
using namespace std;


static const int SECRET_KEYLENGTH=32;
static const int PRIVATE_KEYLENGTH=128;
static const int PUBLIC_KEYLENGTH=32;
#include <string>
#include <iostream>

int main(int argc, char* argv[])
{

    using namespace CryptoPP;


    AutoSeededRandomPool rndA, rndB;
    x25519 ecdhA(rndA), ecdhB(rndB);

    SecByteBlock privA(ecdhA.PrivateKeyLength());
    SecByteBlock pubA(ecdhA.PublicKeyLength());
    ecdhA.GenerateKeyPair(rndA, privA, pubA);

    SecByteBlock privB(ecdhB.PrivateKeyLength());
    SecByteBlock pubB(ecdhB.PublicKeyLength());
    ecdhB.GenerateKeyPair(rndB, privB, pubB);

    SecByteBlock sharedA(ecdhA.AgreedValueLength());
    SecByteBlock sharedB(ecdhB.AgreedValueLength());

    if(!ecdhA.Agree(sharedA, privA, pubB))
         throw std::runtime_error("Failed to reach shared secret (1)");

    if(!ecdhB.Agree(sharedB, privB, pubA))
        throw std::runtime_error("Failed to reach shared secret (2)");

    HexEncoder encoder(new FileSink(std::cout));

    std::cout << "Shared secret (A): ";
    StringSource(sharedA, sharedA.size(), true, new Redirector(encoder));
    std::cout << std::endl;

    std::cout << "Shared secret (B): ";
    StringSource(sharedB, sharedB.size(), true, new Redirector(encoder));
    std::cout << std::endl;



    // We have two keys each.


    string plain = "\n\nHMAC Test";
    string mac, encoded;

    /*********************************\
    \*********************************/
    cout << "plain text: " << plain << endl;

    /*********************************\
    \*********************************/

    try
    {
        HMAC< SHA256 > hmac(sharedA, sharedA.size());

        StringSource ss2(plain, true, 
            new HashFilter(hmac,
                new StringSink(mac)
            ) // HashFilter      
        ); // StringSource
    }
    catch(const CryptoPP::Exception& e)
    {
        cerr << e.what() << endl;
        exit(1);
    }

    /*********************************\
    \*********************************/

    // Pretty print
    encoded.clear();
    StringSource ss3(mac, true,
        new HexEncoder(
            new StringSink(encoded)
        ) // HexEncoder
    ); // StringSource

    cout << "hmac: " << encoded << endl;


    try
    {
        HMAC< SHA256 > hmac2(sharedB, sharedB.size());
        const int flags = HashVerificationFilter::THROW_EXCEPTION | HashVerificationFilter::HASH_AT_END;

        StringSource(plain + mac, true, 
            new HashVerificationFilter(hmac2, NULL, flags)
        ); // StringSource

        cout << "Verified message" << endl;
    }
    catch(const CryptoPP::Exception& e)
    {
        cerr << e.what() << endl;

    }

    return 0;
}

And here is the output:

Shared secret (A): 284FE14022541BD8939C40249A3805DB6C4548B01FF0826253E6FAC53C489D46
Shared secret (B): 284FE14022541BD8939C40249A3805DB6C4548B01FF0826253E6FAC53C489D46
plain text: 

HMAC Test
hmac: BCFEE5E6CCA6EB9818D961DA22545CE9989E799430AA54E9EDBEF35A244D4C77
Verified message

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