ElGamal

From Crypto++ Wiki
Jump to navigation Jump to search
ElGamal
Documentation
#include <cryptopp/elgamal.h>

ElGamal is a public-key cryptosystem which is based on the Discrete Log problem and similar to Diffie-Hellman. Its one of the oldest cryptosystems available. Also see A Public-Key Cryptosystem and a Signature Scheme Based on Discrete Logarithms by Taher ElGamal.

The Crypto++ implementation of ElGamal encryption uses non-standard padding. It may not interop well with other libraries. Crypto++ does not provide ElGamal signatures. However, the library does provide the modified ElGamal signature scheme as proposed by Nyberg and Rueppel and standardized in IEEE P1363.

ElGamal's paper and the Handbook of Applied Cryptography says the private key [math]\displaystyle{ x }[/math] is selected randomly from [math]\displaystyle{ 1,...,p-1 }[/math]. Crypto++ selects [math]\displaystyle{ x }[/math] randomly from [math]\displaystyle{ 1,...,q-1 }[/math] due to Tsiounis and Yung's On the Security of EIGamal Based Encryption. Crypto++ will load and validate a key from [math]\displaystyle{ 1,...,p-1 }[/math].

In January 2018 for the Crypto++ 6.0 release, the ASN.1 format of the public and private keys changed. The old format was a legacy format used in the 1990s. The new format is a standard encoding format based on X509PublicKey and PKCS8PrivateKey. Also see Issue 567, Remove DL_PrivateKey_GFP_OldFormat and Commit a5a684d92986.

Issue 876 revealed Crypto++ 8.2 and below used OID 1.2.840.10040.4.1 (idDSA) for ElGamal encryption keys. If you need to Load a key with OID 1.3.14.7.2.1.1 (elGamal), then use Crypto++ 8.3 or above. Also see the section OID Load Bug below for a workaround.

Issue 1059 revealed Crypto++ 8.5 and below were vulnerable to message recovery due to bad interactions with other implementations. The vulnerability happened because Crypto++ would use an estimate of work factor when selecting parameters. At Crypto++ 8.6 parameter selection was changed to use subgroup order. Also see Commit bee8e8ca6658.

Saving and Loading Keys

You can save and load keys to and from disk as follows. While a PrivateKey and Decryptor are shown, the same applies to a PubicKey and Encryptor. See Keys and Formats for a detailed discussion.

ElGamalKeys::PrivateKey privateKey1;
privateKey1.GenerateRandomWithKeySize(prng, 2048);
privateKey1.Save(FileSink("elgamal.der", true /*binary*/).Ref());

ElGamalKeys::PrivateKey privateKey2;
privateKey2.Load(FileSource("elgamal.der", true /*pump*/).Ref());
privateKey2.Validate(prng, 3);

ElGamal::Decryptor decryptor(privateKey2);
// ...

Or, you can do it on a Decryptor rather than a PrivateKey because the decryptor provides access to the private key:

ElGamalKeys::PrivateKey privateKey1;
privateKey1.GenerateRandomWithKeySize(prng, 2048);
    
ElGamal::Decryptor decryptor1(privateKey1);
decryptor1.AccessKey().Save(FileSink("elgamal.der", true /*binary*/).Ref());

The keys are ASN.1 encoded, so you can dump them with something like Peter Gutmann's dumpasn1:

$ ./cryptopp-elgamal-keys.exe
Generating private key. This may take some time...
$ dumpasn1 elgamal.der 
  0 556: SEQUENCE {
  4 257:   INTEGER
       :     00 C0 8F 5A 29 88 82 8C 88 7D 00 AE 08 F0 37 AC
       :     FA F3 6B FC 4D B2 EF 5D 65 92 FD 39 98 04 C7 6D
       :     6D 74 F5 FA 84 8F 56 0C DD B4 96 B2 51 81 E3 A1
       :     75 F6 BE 82 46 67 92 F2 B3 EC 41 00 70 5C 45 BF
       :     40 A0 2C EC 15 49 AD 92 F1 3E 4D 06 E2 89 C6 5F
       :     0A 5A 88 32 3D BD 66 59 12 A1 CB 15 B1 72 FE F3
       :     2D 19 DD 07 DF A8 D6 4C B8 D0 AB 22 7C F2 79 4B
       :     6D 23 CE 40 EC FB DF B8 68 A4 8E 52 A9 9B 22 F1
       :             [ Another 129 bytes skipped ]
265   1:   INTEGER 3
268 257:   INTEGER
       :     00 BA 4D ED 20 E8 36 AC 01 F6 5C 9C DA 62 11 BB
       :     E9 71 D0 AB B7 E2 D3 61 37 E2 7B 5C B3 77 2C C9
       :     FC DE 43 70 AE AA 5A 3C 80 0A 2E B0 FA C9 18 E5
       :     1C 72 86 46 96 E9 9A 44 08 FF 43 62 95 BE D7 37
       :     F8 99 16 59 7D FA 3A 73 DD 0D C8 CA 19 B8 6D CA
       :     8D 8E 89 52 50 4E 3A 84 B3 17 BD 71 1A 1D 38 9E
       :     4A C4 04 F3 A2 1A F7 1F 34 F0 5A B9 CD B4 E2 7F
       :     8C 40 18 22 58 85 14 40 E0 BF 01 2D 52 B7 69 7B
       :             [ Another 129 bytes skipped ]
529  29:   INTEGER
       :     01 61 40 24 1F 48 00 4C 35 86 0B 9D 02 8C B8 90
       :     B1 56 CF BD A4 75 FE E2 8E 0B B3 66 08
       :   }

0 warnings, 0 errors.

Public Key Encryption

The following is a sample program that encrypts a secret under a ElGamal public key and then recovers the secret using an ElGamal private key.

////////////////////////////////////////////////
// Generate keys
AutoSeededRandomPool rng;

cout << "Generating private key. This may take some time..." << endl;

ElGamal::Decryptor decryptor;
decryptor.AccessKey().GenerateRandomWithKeySize(rng, 2048);
const ElGamalKeys::PrivateKey& privateKey = decryptor.AccessKey();

ElGamal::Encryptor encryptor(decryptor);
const PublicKey& publicKey = encryptor.AccessKey();

////////////////////////////////////////////////
// Secret to protect
static const int SECRET_SIZE = 16;
SecByteBlock plaintext( SECRET_SIZE );
memset( plaintext, 'A', SECRET_SIZE );

////////////////////////////////////////////////
// Encrypt

// Now that there is a concrete object, we can validate
assert( 0 != encryptor.FixedMaxPlaintextLength() );
assert( plaintext.size() <= encryptor.FixedMaxPlaintextLength() );

// Create cipher text space
size_t ecl = encryptor.CiphertextLength( plaintext.size() );
assert( 0 != ecl );
SecByteBlock ciphertext( ecl );

encryptor.Encrypt( rng, plaintext, plaintext.size(), ciphertext );

////////////////////////////////////////////////
// Decrypt

// Now that there is a concrete object, we can check sizes
assert( 0 != decryptor.FixedCiphertextLength() );
assert( ciphertext.size() <= decryptor.FixedCiphertextLength() );

// Create recovered text space
size_t dpl = decryptor.MaxPlaintextLength( ciphertext.size() );
assert( 0 != dpl );
SecByteBlock recovered( dpl );

DecodingResult result = decryptor.Decrypt( rng,
    ciphertext, ciphertext.size(), recovered );

// More sanity checks
assert( result.isValidCoding );
assert( result.messageLength <= decryptor.MaxPlaintextLength( ciphertext.size() ) );

// At this point, we can set the size of the recovered
//  data. Until decryption occurs (successfully), we
//  only know its maximum size
recovered.resize( result.messageLength );

// SecByteBlock is overloaded for proper results below
assert( plaintext == recovered );

// If the assert fires, we won't get this far.
if(plaintext == recovered)
    cout << "Recovered plain text" << endl;
else
    cout << "Failed to recover plain text" << endl;

Symmetric Encryption

Crypto++ ElGamal objects offer SymmetricEncrypt and SymmetricDecrypt. The functions will encrypt and decrypt arbitrary length text under a symmertic key, and then encrypt the symmetric key under the ElGamal public key.

You can find the source code for SymmetricEncrypt and SymmetricDecrypt in elgamal.h.

ElGamal Signatures

There is no implementation of ElGamal signatures as proposed by Taher ElGamal. Rather, the Crypto++ library provides the modified ElGamal signature scheme as proposed by Nyberg and Rueppel and standardized in IEEE P1363. From the Readme.txt under the Crypto++ 3.0 changes:

Renamed ElGamalSignature to NR and changed it to track IEEE P1363

If you need the original ElGamal signature scheme, then you should download it from an archive.

OID Load Bug

Crypto++ 8.2 and below used the OID for the DSA algorithm for ElGamal keys. Crypto++ 8.3 and above are OK. The problem affected loading and saving ElGamal keys. Also see Issue 876, ElGamal encryption key Load and BERDecodeErr.

If you are using Crypto++ 8.2 or below then you can load an ElGamal public key with either OID using the code below. The code requires Commits b80693d5322a, 29e3818fd222, 3d96234038b5 and a2c06c35b84f.

#include "cryptlib.h"
#include "elgamal.h"
#include "filters.h"
#include "files.h"
#include "osrng.h"
#include "base64.h"
#include "oids.h"
#include "asn.h"

#include <iostream>
#include <string>

void LoadElGamalPublicKey(CryptoPP::BufferedTransformation& bt,
                          CryptoPP::ElGamal::PublicKey& key)
{
    using namespace CryptoPP;
    Integer v1, v2, v3, v4;
    bool parametersPresent;

    BERSequenceDecoder subjectPublicKeyInfo(bt);
        BERSequenceDecoder algorithm(subjectPublicKeyInfo);            
            OID oid; // Check for old and new OID here.
            oid.BERDecode(algorithm);
            if (oid != ASN1::id_dsa() && oid != ASN1::elGamal())
                throw BERDecodeErr();

            parametersPresent = !algorithm.EndReached();
            if (parametersPresent)
            {
                BERSequenceDecoder params(algorithm);
                    v1.BERDecode(params);
                    v2.BERDecode(params);

                    parametersPresent = !params.EndReached();
                    if (parametersPresent)                                            
                        v3.BERDecode(params);
                    else
                        v3=v2, v2=(v1-1)/2;
                params.MessageEnd();
            }
        algorithm.MessageEnd();

        BERSequenceDecoder subjectPublicKey(subjectPublicKeyInfo, BIT_STRING);
            subjectPublicKey.CheckByte(0);    // unused bits
            parametersPresent = !subjectPublicKey.EndReached();
            if (parametersPresent)
                v4.BERDecode(subjectPublicKey);

        subjectPublicKey.MessageEnd();
    subjectPublicKeyInfo.MessageEnd();

    key.AccessGroupParameters().Initialize(v1, v2, v3);
    key.SetPublicElement(v4);
}

int main(void)
{
    using namespace CryptoPP;
    AutoSeededRandomPool prng;

    const std::string encodedPublicKey =
        "MHYwTwYGKw4HAgEBMEUCIQDebUvQDd9UPMmD27BJ"
        "ovZSIgWfexL0SWkfJQPMLsJvMwIgDy/kEthwO6Q+"
        "L8XHnzumnEKs+txH8QkQD+M/8u82ql0DIwACIAY6"
        "rfW+BTcRZ9QAJovgoB8DgNLJ8ocqOeF4nEBB0DHH";
    StringSource decodedPublicKey(encodedPublicKey, true, new Base64Decoder);

    ElGamal::Encryptor encryptor;
    ElGamal::PublicKey& publicKey = encryptor.AccessKey();
    LoadElGamalPublicKey(decodedPublicKey, publicKey);

    if (publicKey.Validate(prng, 3))
        std::cout << "Public key is valid" << std::endl;
    else
        std::cout << "Public key is not valid" << std::endl;

    // Key has been loaded with either OID. The in-memory key uses
    // the ElGamal OID. Now save the key with the ElGamal OID.
    publicKey.Save(FileSink("converted-key.der").Ref());

    return 0;
}

Downloads

cryptopp-elgamal.zip - Demonstrates ElGamal public key encryption.

cryptopp-elgamal-keys.zip - Demonstrates loading and saving ElGamal private keys.