Nyberg-Rueppel
Documentation |
#include <cryptopp/gfpcrypt.h>
|
Nyberg-Rueppel is a signature scheme based on ElGamal signatures hardened against attacks in the original ElGamal scheme. The signature scheme is standardized in P1363.
A version of Nyberg-Rueppel over elliptic curves is available at Elliptic Curve Nyberg-Rueppel.
Constructor
Both the NR<SHA256>::PublicKey
and NR<SHA256>::PrivateKey
are typedef'd from template classes which accept no parameters. See gfpcrypt.h
for details.
Sample Programs
Below are sample programs that show how to use NR classes. Additional details of key generation, validation, loading, and saving are available in Keys and Formats.
Key Generation
The default modulus size for NR is 1024-bits. Below is sample code to generate a NR key using a 2048-bit modulus.
#include "cryptlib.h" #include "gfpcrypt.h" #include "osrng.h" #include "sha.h" #include <stdexcept> int main(int argc, char* argv[]) { using namespace CryptoPP; AutoSeededRandomPool rng; // Generate Private Key NR<SHA256>::PrivateKey privateKey; privateKey.GenerateRandomWithKeySize(rng, 2048); // Generate Public Key NR<SHA256>::PublicKey publicKey; publicKey.AssignFrom(privateKey); if (!privateKey.Validate(rng, 3) || !publicKey.Validate(rng, 3)) { throw std::runtime_error("NR key generation failed"); } return 0; }
Overriding Defaults
The default modulus size for NR is 1024-bits. The default subgroup generator size is 224-bits when using a modulus size of 2048-bits. Also see DL_GroupParameters_GFP::GenerateRandom
. The example below generates a 2048-bit NR key with a 256-bit subgroup size, and then prints the hex encoded key to standard output. To avoid the 2048-bit modulus/224-bit subgroup order pair you have to use NameValuePairs and a GenerateRandom
overload.
#include "cryptlib.h" #include "gfpcrypt.h" #include "files.h" #include "osrng.h" #include "hex.h" #include <iostream> int main(int argc, char* argv[]) { using namespace CryptoPP; AutoSeededRandomPool prng; AlgorithmParameters params = MakeParameters (Name::ModulusSize(), 2048) (Name::SubgroupOrderSize(), 256); NR<SHA256>::PrivateKey privateKey; privateKey.GenerateRandom(prng, params); std::cout << "Test key: " << std::endl;; HexEncoder hex(new FileSink(std::cout)); privateKey.Save(hex); std::cout << std::endl;; return 0; }
The result of running the program is shown below.
308202610201003082023906072A8648CE3804013082022C0282010100A2399DC2121B5BFCC8275F 3C2745F138842EF70014FDC90D64694201DB22636F561AF6187247D32BE4351F538BE981628449B3 66C5245882F2EF3D49E8BA7829E69D9B9396E1808054C9749ED250D862A8DF3F7088156FE0AC1509 65C65454BA3438559926E1256765BB099998CDCD6A7736E79C1E3174CBCE1577991A793ED74A43B6 A1EF0B8C5F03000C54005BB72F97BECA6086B30918086EE4A874AF7BB5C392A5229BD435ACB5133A 157296BB98620D6DD95178EFFAA093563827B6204C623CD40922E309E873513A71EC25A0A2C9C9A0 8B2CD82D5C0EF8EB5F47E99EBFB7947307F000112BB7544D4BDEBC1C5FC6736AEB13C166DFF855C6 D9E3EFA4AF022100BD10A6137A7D9DCC1BE40812298F4303C6051A8E748A9DACA7FD11797C0F11FD 0282010004AC8F3AF54E139B0D4493D9BD961EBA89D76B2CBD27DE1107EAA3431E7293E7EEADD7DC FF6F25646734401AF14A59DE05D080B9C34AE382D28AB9F5765A5895CC7EDBD7E35FB5491DAA04FD EA6CF34404548185C5BA1ACF79C400F630E47C0C7DD087EB353A0312908C063424BC7D9CFD0ADE72 F90236D768468CABF7FC62B854FB72C58DB4A0F82BEC9812BD305BA4DF00CC1B94CCA6F0D870F639 37B6037BE9B108B394B87A896B131068344BA2B18EA539C2315CB1152E0A2CEE35D56393776107C6 F29411669FE6521F2911C47B1E3CCC9075C82A488EB0CA0556660624ED887B9645375B73640BA58A 8C25A17253AF978528C019592C5876B9E0DEC520041F021D03375DBE8607B330ECD7B501FE8E24A4 9554D5B16EAC9E467E0A51AEFB
Loading and Saving
The program below extends the key generation example by serializing the NR keys in PKCS#8 and X.509 format. In the code below, the encoded keys (encodedPublicKey
and encodedPrivateKey
) exist in memory. The keys could be persisted to disk by using a FileSink
rather than a StringSink
.
The code below uses a StringSink
and string
to hold the private key. Though convenient, the practice is not a very good idea - see Keys and Formats for details.
AutoSeededRandomPool rng; // Generate Public and Private Keys NR<SHA256>::PrivateKey privateKey = ...; NR<SHA256>::PublicKey publicKey = ...; ... // DER Encoded Keys string encodedPublicKey, encodedPrivateKey; // Serialize in PKCS#8 and X.509 format publicKey.Save( StringSink(encodedPublicKey).Ref() ); privateKey.Save( StringSink(encodedPrivateKey).Ref() ); // Decode NR keys NR<SHA256>::PrivateKey decodedPrivateKey; decodedPrivateKey.Load( StringStore(encodedPrivateKey).Ref() ); NR<SHA256>::PublicKey decodedpublicKey; decodedPublicKey.Load( StringStore(encodedPublicKey).Ref() );
Signing and Verification
Building on the previous samples, the next sample signs and verifies a message. Signing is accomplished using a SignerFilter
, while verifications are performed with a SignatureVerificationFilter
. The SignatureVerificationFilter
is constructed using the THROW_EXCEPTION
flag, so an exception handler for SignatureVerificationFailed
should be in place.
// Generate or Load the Public and Private Keys NR<SHA256>::PrivateKey privateKey; NR<SHA256>::PublicKey publicKey; ... string message = "NR Signature"; string signature; NR<SHA256>::Signer signer( privateKey ); StringSource ss1( message, true, new SignerFilter( rng, signer, new StringSink( signature ) ) // SignerFilter ); // StringSource NR<SHA256>::Verifier verifier( publicKey ); StringSource ss2( message+signature, true, new SignatureVerificationFilter( verifier, NULL, THROW_EXCEPTION /* SIGNATURE_AT_END */ ) ); std::cout << "Verified signature on message" << std::endl;;
In the example above, the filter receives a concatenation of message+signature
. If the signature is inserted first, SIGNATURE_AT_BEGIN
should be specified as an additional flags
value.
... NR<SHA256>::Verifier verifier( publicKey ); StringSource ss( signature+message, true, new SignatureVerificationFilter( verifier, NULL, THROW_EXCEPTION | SIGNATURE_AT_BEGIN ) );
The next sample verifies the signature without throwing an exception. This sample uses the PUT_RESULT
flag. The SignatureVerificationFilter
will place the result into its attached transformation. In the sample below, the result is piped into the bool
value result
. To facilitate the pipeline, the variable is wrapped in an ArraySink.
There are three points to observe below. First, it makes not sense to specify both PUT_RESULT
and THROW_EXCEPTION
as a flag. Second, a StringSink
cannot be used since the boolean variable does not derive from std::basic_string
. See the StringSink entry for details. Finally, the only flags
is PUT_RESULT
, so the signature must be presented last (SIGNATURE_AT_BEGIN
is not specified).
... NR<SHA256>::Verifier verifier( publicKey ); bool result = false; StringSource ss( message+signature, true, new SignatureVerificationFilter( verifier, new ArraySink( (byte*)&result, sizeof(result ) ), PUT_RESULT | SIGNATURE_AT_END ) ); if( true == result ) { std::cout << "Verified signature on message" << std::endl;; }
The final piece of code uses a little known sink called a Redirector. The Redirector
does not own its attached BufferedTransformation
, so the attached object is not deleted (as a consequence of the behavior, the Redirector
takes a reference and not a pointer). It is useful when an intermediate result is required from an object that would otherwise be participating in pipelining.
... NR<SHA256>::Verifier verifier( publicKey ); SignatureVerificationFilter svf( verifier /* SIGNATURE_AT_END */ ); // SignatureVerificationFilter StringSource ss( message+signature, true, new Redirector( svf ) ); // StringSource if( true == svf.GetLastResult() ) { std::cout << "Verified signature on message" << std::endl;; }
Precomputed Hashes
Sometimes you may want to sign a precomputed hash. The code below allows you to sign a precomputed hash by copying the input to the hash function to the output of the hash function. Effectively it is an identity function. Also see Sign precomputed hash with ECDSA or DSA on Stack Overflow.
Generally speaking signing a precomputed hash is a bad idea, especially if you don't compute the hash yourself. You don't want to disgorge creating the message digest from applying the secret key. You should try to avoid doing so.
$ cat test.cxx #include "cryptlib.h" #include "gfpcrypt.h" #include "secblock.h" #include "osrng.h" #include "hex.h" #include <iostream> #include <string> using namespace CryptoPP; template <unsigned int HASH_SIZE = 32> class IdentityHash : public HashTransformation { public: CRYPTOPP_CONSTANT(DIGESTSIZE = HASH_SIZE); static const char * StaticAlgorithmName() { return "IdentityHash"; } IdentityHash() : m_digest(HASH_SIZE), m_idx(0) {} virtual unsigned int DigestSize() const { return DIGESTSIZE; } virtual void Update(const byte *input, size_t length) { size_t s = STDMIN(STDMIN<size_t>(DIGESTSIZE, length), DIGESTSIZE - m_idx); if (s) ::memcpy(&m_digest[m_idx], input, s); m_idx += s; } virtual void TruncatedFinal(byte *digest, size_t digestSize) { if (m_idx != DIGESTSIZE) throw Exception(Exception::OTHER_ERROR, "Input size must be " + IntToString(DIGESTSIZE)); ThrowIfInvalidTruncatedSize(digestSize); if (digest) ::memcpy(digest, m_digest, digestSize); m_idx = 0; } private: SecByteBlock m_digest; size_t m_idx; }; int main(int argc, char* argv[]) { AutoSeededRandomPool prng; // NR 2048-bit modulus use a 224-bit subgroup by default // The hash should be at least 224-bits, like SHA-224. NR<SHA256>::PrivateKey privateKey; privateKey.Initialize(prng, 2048); std::string message; message.resize(IdentityHash<224/8>::DIGESTSIZE); ::memset(&message[0], 0xAA, message.size()); NR<SHA256>::Signer signer(privateKey); std::string signature; StringSource ss(message, true, new SignerFilter(prng, signer, new HexEncoder(new StringSink(signature)) ) // SignerFilter ); // StringSource std::cout << "Signature: " << signature << std::endl;; return 0; }
Running the code produces output similar to:
$ ./test.exe Signature: 00E2E08D7A6061361CE07491AD2EAACC82BFFE4E1F51D81B78AD0C3E53020DB00D305 3D97595FD0676D007788FD90A2600910DBE79A94D71EDE4
Downloads
No downloads available.