Requiring a RandomNumberGenerator during RSA Encryption and Decryption?

I am trying to encrypt a message with the public key and decrypt the cipher with the private key using crypto ++ like this in the shell:

openssl rsautl -encrypt -inkey id_rsa.pub.pem -pubin -in message -out message.enc

      

and

openssl rsautl -decrypt -inkey id_rsa.pem -in message.enc -out message.dec

      

Encryption / decryption is done in separate applications. I started with an example from https://www.cryptopp.com/wiki/RSA_Cryptography . My code:

std::string publicEncrypt(std::string const& plain) {
    auto cipher = std::string{};
    CryptoPP::RSAES_OAEP_SHA_Encryptor e(getPublicKey());
    CryptoPP::StringSource(plain, true,
        new CryptoPP::PK_EncryptorFilter(CryptoPP::NullRNG(), e,
                new CryptoPP::StringSink(cipher)));
   return cipher;
}

std::string privateDecrypt(std::string const& cipher) {
    auto decrypted = std::string{};
    CryptoPP::RSAES_OAEP_SHA_Decryptor d(getPrivateKey());
    CryptoPP::StringSource(cipher, true,
        new CryptoPP::PK_DecryptorFilter(CryptoPP::NullRNG(), d,
                new CryptoPP::StringSink(decrypted)));
    return decrypted;
}

      

My questions:

  • Why is a random number generator (RNG) necessary for the EncryptorFilter / DecryptorFilter?
  • RNG should be the same for encryption / defragmentation, right? So how do you share between processes?

Using NullRNG () as recommended by https://stackoverflow.com/users/608639/jww in Unable to do RSA Encrption / Decryption using Crypto ++ (isValidCoding false) results in

std::exception NullRNG: NullRNG should only be passed to functions that don't need to generate random bytes.

      

I think I basically missed something. Thanks for the tips and tricks.

If I use this code in a unit test with a global RNG everything works fine.

+3


source to share


1 answer


Why is a random number generator (RNG) necessary for the EncryptorFilter / DecryptorFilter?

The signature and validation classes are the setting of abstract interfaces in cryptlib.h

. Some cryptosystems use them, others don't. The class will specialize and may opt out of using a generator. Sometimes a class doesn't need a generator for one of its operations. NullRNG

can be used if not required.

The reason RSA needs the RNG during public key operations is because the message is filled. Padding is often part of the message formatting function. As @PuzzlePalace pointed out, the OAEP add-on is randomized and non-deterministic.

The reason RSA needs RNGs during private key operations is blinding. For RSA and other RSA-like schemes (like Rabin-Williams), blinding is simply a multiplication by a random value to mask the inversion with the priavte key to restore the original value. Later, after signing or decrypting, the blinding value is removed and the result of the operation remains.

Related, the reason for DSA or ECDSA would not be the need for RNGs during RFC 6979 private key operations, the deterministic use of the Digital Signature Algorithm (DSA) and the Elliptic Curve Digital Signature Algorithm (ECDSA) . Deterministic signatures do not use randomized formatting or randomized formatting k

.

Another reason why RNG is needed for public key and private key operations is for key validation checks. For example, a key can be verified to enforce a certain constraint, such as its prime or the presence of a certain Jacobi character.


RNG should be the same for encryption / decryption, right? So how do you share between processes?

No, generators can be different. The only requirements are that they create a random stream for some reasonable definition of what it means to be "random". By not splitting too many hairs, this means that the generator produces an even distribution.

More information on Crypto ++ generators can be found in RandomNumberGenerator

the wiki.


If I use this code in a unit test with a global RNG everything works fine.

One quick word of caution ... GlobalRNG

is part of the namespace Test

. It is defined in test.cpp : 115

:



NAMESPACE_BEGIN(CryptoPP)
NAMESPACE_BEGIN(Test)

ANONYMOUS_NAMESPACE_BEGIN
OFB_Mode<AES>::Encryption s_globalRNG;
NAMESPACE_END

RandomNumberGenerator & GlobalRNG()
{
    return dynamic_cast<RandomNumberGenerator&>(s_globalRNG);
}

NAMESPACE_END  // Test
NAMESPACE_END  // CryptoPP

      

GlobalRNG

is a deterministic generator and is not part of the actual library. Your code won't be able to compile in the field if you depend on it.

Use one of the other generators discussed in RandomNumberGenerator

the wiki. AutoSeededRandomPool

- a good choice.


Using NullRNG () as recommended at https://stackoverflow.com/users/608639/jww under Unable to perform RSA Encrption / Decryption using Crypto ++ (isValidCoding is false) results in

std::exception NullRNG: NullRNG should only be passed to functions that don't need to generate random bytes.

      

This information is incorrect. I need to fix this. Thank.


Interestingly, Crypto ++ picked up CVE-2015-2141 due to the Rabin-Williams blinding. The dazzling value should be a squared deduction; otherwise, an attacker can prepare special messages to reveal the private key.

Yevgeny Sidorov's full work can be found in Violating the Rabin-Williams Digital Signature Implementation in the Crypto ++ Library . This is what the new and improved inverse function looks like after fixing Sidorov's attack (from rw.cpp

):

ModularArithmetic modn(m_n), modp(m_p), modq(m_q);
Integer r, rInv;

do
{
    // Do this in a loop for people using small numbers for testing
    r.Randomize(rng, Integer::One(), m_n - Integer::One());
    // Fix for CVE-2015-2141. Thanks to Evgeny Sidorov for reporting.
    // Squaring to satisfy Jacobi requirements suggested by Jean-Pierre Munch.
    r = modn.Square(r);
    rInv = modn.MultiplicativeInverse(r);
} while (rInv.IsZero());

      

If you read section 6 of Sidorov's article, he suggests creating a random r

one and then checking the Jacobi symbol r

to ensure its quadratic deduction. If it's not QR, try a new random one r

. This sort used this method, but showed that the scheme slowed down significantly because the random r

satisfies the condition with a probability of 1/16.

However, we knew that the square r

guaranteed that we did Jacobi on the first try, since r 2 mod n was always a quadratic residue. Compression / multiplication only takes log (exp)

(not n log (n)

), so it turned out to be significantly faster compared to trial and error. Before we released the next version of the library, we switched to the squaring method.

+2


source







All Articles