NameValuePairs

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

NameValuePairs are used to safely pass a variable number of arbitrarily typed arguments to functions and to read values from keys and crypto parameters. The class allows the library to provide common interfaces when unifying disparate schemes with different security parameters. Sometimes, NameValuePairs is the only way to setup an object to a desired state.

You can usually store just about any plain old datatype in a NameValuePairs, including int's and double's. There is support for both std::string and byte arrays by way of ConstByteArrayParameter .

The ConstByteArrayParameter class has a very sharp edge when you need to retrieve more than one array from an object. Be sure to visit the topic below if you are retrieving more than one array.

File Comments

To obtain an object that implements NameValuePairs for the purpose of parameter passing, use the MakeParameters() function. According to the comments in algparam.h:

A NameValuePairs object containing an arbitrary number of name value pairs may be constructed by repeatedly using operator() on the object returned by MakeParameters, for example:

AlgorithmParameters parameters = MakeParameters(name1, value1)(name2, value2)(name3, value3);

MakeParameters(name1, value1)(name2, value2)... probably looks a little unusual. It is an acquired taste, and it works because the class provides an overload for operator(). The head notes go on to say:

To get a value from NameValuePairs, you need to know the name and the type of the value. Call GetValueNames() on a NameValuePairs object to obtain a list of value names that it supports. Then look at the Name namespace documentation to see what the type of each value is, or alternatively, call GetIntValue() with the value name, and if the type is not int, a ValueTypeMismatch exception will be thrown and you can get the actual type from the exception object.

If you encounter a function that requires a NameValuePairs object but you don't have any parameters, then use g_nullNameValuePairs.

For a list of names recognized by the library, see the Name Namespace Reference; or see argnames.h. You can use both an argname and a quoted C-string for the name parameter. For example, both Name::Salt() and "Salt" will retrieve the salt value from a name/value pair.

Initialization

Class objects that take NameValuePairs can be initialized after construction with Initialize or IsolatedInitialize. They are virtual functions provided by BufferedTransformation. The one line comment with them is Initialize or reinitialize this object. The difference between Initialize or IsolatedInitialize appears to lie in signal propagation to attached transformations. Initialize appears to effect the object and its attached transformations; while IsolatedInitialize effects only the object itself. (See more on behavior below in Initialize vs IsolatedInitialize).

Filters should use IsolatedInitialize. Classes like HexEncoder, HexDecoder, Base64Encoder and Base64Decoder provides an override for IsolatedInitialize (but not Initialize). For example, the HexEncoder constructor is shown below:

HexEncoder(BufferedTransformation *attachment = NULL, bool uppercase = true, int outputGroupSize = 0,
           const std::string &separator = ":", const std::string &terminator = "")
    : SimpleProxyFilter(new BaseN_Encoder(new Grouper), attachment)
{
    IsolatedInitialize(MakeParameters(Name::Uppercase(), uppercase)
                                     (Name::GroupSize(), outputGroupSize)
                                     (Name::Separator(), ConstByteArrayParameter(separator))
                                     (Name::Terminator(), ConstByteArrayParameter(terminator)));
}

If a library user calls IsolatedInitialize with different parameters, then the IsolatedInitialize implementation uses a CombinedNameValuePairs to blend the caller's parameter's with the library's default parameters. Again, from the the HexEncoder:

void HexEncoder::IsolatedInitialize(const NameValuePairs &parameters)
{
	bool uppercase = parameters.GetValueWithDefault(Name::Uppercase(), true);
	m_filter->Initialize(CombinedNameValuePairs(
		parameters,
		MakeParameters(Name::EncodingLookupArray(), uppercase ? &s_vecUpper[0] : &s_vecLower[0], false)
                              (Name::Log2Base(), 4, true)));
}

The parameters are the caller's parameters; while the value from MakeParameters are the library's default parameters. When the object needs a setting, CombinedNameValuePairs consults the caller's parameters first and uses them if present. If not present, then the library uses the built-in defaults. In the case of HexEncoder, the default provided by the library is just an uppercase or lowercase alphabet.

Initialize vs IsolatedInitialize

The behavior of Initialize or IsolatedInitialize can be tested with a simple program that encodes a block of bytes.

string encoded;
Base64Encoder encoder(NULL, true, 100);
encoder.Attach(new StringSink( encoded ));

AlgorithmParameters params = MakeParameters(Pad(), false);
encoder.Initialize(params);

ArraySource as(raw, sizeof(raw), true, new Redirector(encoder));
cout << encoded << endl;

Note the program uses Initialize. Running the program results in:

$ ./cryptopp-test.exe
StringSink: OutputStringPointer not specified

Its hard to say what the exact state of the object is other than we broke something related to the attached StringSink's referenced string.

If the program is modified to use IsolatedInitialize, then running the program results in:

$ ./cryptopp-test.exe
/+7dzLuqmYh3ZlVEMyIRAP/u3cy7qpmId2ZVRDMiEQD/7t3Mu6qZiHdmVUQzIhEA/+7dzLuq
mYh3ZlVEMyIRAA

In the program above, the filter provided the default alphabet, the line length was reset to 72, the padding was modified, and the attached transformation was not modified.

Sample Programs

The following are two sample programs that exercise NameValuePairs. The first retrieves the names available from a parameter object and then prints a couple of values; the second disables padding on an encoder.

Get Names and Values

GetNameValues allows you to retrieve the names used for AlgorithmParameters. Note: not all objects expose this method.

using CryptoPP::Name::Pad;
using CryptoPP::Name::InputStreamPointer;
using CryptoPP::Name::OutputStreamPointer;

AlgorithmParameters p = MakeParameters(Pad(), false)
                                      (InputStreamPointer(), NULL)
                                      (OutputStreamPointer(), NULL);

const string& names = p.GetValueNames();
cout << names << endl;

if(names.find("Pad", 0) != string::npos)
{
    bool value;
    p.GetValue("Pad", value);
            
    cout << "Pad: " << (value ? "true" : "false") << endl;
}

if(names.find("Foo", 0) == string::npos)
{
    cout << "Foo: not found" << endl;
}

The example above will produce the following output:

Pad;InputStreamPointer;OutputStreamPointer;
Pad: false
Foo: not found

Padding and Encoders

The following program disables padding and line breaks on a Base64Encoder. You have to use NameValuePairs in this case because there is no way to disable padding through a constructor.

using CryptoPP::Name::Pad;
using CryptoPP::Name::InsertLineBreaks;

byte raw[] = {
    0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x99, 0x88,
    0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00,
    0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x99, 0x88,
    0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00,
    0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x99, 0x88,
    0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00,
    0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x99, 0x88,
    0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00 };

string encoded, hexed;
Base64Encoder encoder;

AlgorithmParameters params = MakeParameters(Pad(), false)(InsertLineBreaks(), false);
encoder.IsolatedInitialize(params);
encoder.Attach(new StringSink( encoded ));

ArraySource as(raw, sizeof(raw), true, new Redirector(encoder));
cout << encoded << endl;

StringSource ss(encoded, true, new Base64Decoder(new HexEncoder(new StringSink(hexed))));
cout << hexed << endl;

In the code above, the Base64Encoder constructor sets up a standard object. Then the parameters are tuned for this particular use. In this case, the use is (1) no padding and (2) no line breaks.

The call to IsolatedInitialize initializes or reinitializes the object. Under the covers, Base64Encoder's override of IsolatedInitialize uses CombinedNameValuePairs to blend the library's default parameters with the callers's parameters. That means parameters like the encoding alphabet will always be present so a caller does not have to specify it again (see base64.cpp).

A run of the program produces the following. The padding is not added to the tail of the encoded data (which would be ...VEMyIRAA==).

$ ./cryptopp-test.exe
/+7dzLuqmYh3ZlVEMyIRAP/u3cy7qpmId2ZVRDMiEQD/7t3Mu6qZiHdmVUQzIhEA/+7dzLuqmYh3ZlVEMyIRAA
FFEEDDCCBBAA99887766554433221100FFEEDDCCBBAA99887766554433221100FFEEDDCCBBAA99887766554433221100FFEEDDCCBBAA99887766554433221100

CFB Mode and Feedback

Crypto++ uses the full blocksize for the feedback size by default. Other libraries sometimes use a different size and it leads to interoperability issues. For example, Mcrypt and .Net use a smaller feedback size. Using less than the full block size for the feedback size can reduce the security in some modes of operation. If given a choice, you should probably prefer libraries like Mcrypt and .Net use the full block size rather than a smaller feedback size.

Here's how to reduce the feedback size in Crypto++ (taken from CFB Mode):

SecByteBlock key(AES::DEFAULT_KEYLENGTH), iv(AES::BLOCKSIZE);
...

AlgorithmParameters params = MakeParameters
                                 (Name::FeedbackSize(), 1 /*8-bits*/)
                                 (Name::IV(), ConstByteArrayParameter(iv, iv.size()));

string plain = "CFB Mode Test", cipher;

try
{
   CFB_Mode< AES >::Encryption enc;
   enc.SetKey( key, key.size(), params );

   StringSource ss( plain, true, 
      new StreamTransformationFilter( enc,
         new StringSink( cipher )
      ) // StreamTransformationFilter      
   ); // StringSource
}
catch( CryptoPP::Exception& ex )
{
   cerr << ex.what() << endl;
   exit(1);
}

DSA 2048-bit modulus

DSA allows modulus sizes of 1024, 2048 and 3072 bits. When 2048-bit modulus is used a 224-bit subgroup order is used by default. To use a 2048-bit modulus and 256-bit subgroup order perform the following.

#include "cryptlib.h"
#include "osrng.h"
#include "dsa.h"

#include <iostream>
#include <cstdint>

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

    AlgorithmParameters params = MakeParameters
        (Name::ModulusSize(), 2048)
        (Name::SubgroupOrderSize(), 256);

    DSA::PrivateKey privateKey;
    privateKey.GenerateRandom(prng, params);

    return 0;
}

Non-Name Parameters

The previous examples used existing Name::X to pass parameters. For example, Name::ModulusSize() and Name::SubgroupOrderSize(). It is also possible to use a C-string to pass parameters. For example, the following is the head of Integer::GenerateRandomNoThrow. Notice C-strings are used for "Min", "Max", "BitLength" and "EquivalentTo".

bool Integer::GenerateRandomNoThrow(RandomNumberGenerator &i_rng, const NameValuePairs &params)
{
    Integer min = params.GetValueWithDefault("Min", Integer::Zero());
    Integer max;
    if (!params.GetValue("Max", max))
    {
        int bitLength;
        if (params.GetIntValue("BitLength", bitLength))
            max = Integer::Power2(bitLength);
        else
            throw InvalidArgument("Integer: missing Max argument");
    }
    if (min > max)
        throw InvalidArgument("Integer: Min must be no greater than Max");

    Integer equiv = params.GetValueWithDefault("EquivalentTo", Integer::Zero());
    Integer mod = params.GetValueWithDefault("Mod", Integer::One());

    if (equiv.IsNegative() || equiv >= mod)
        throw InvalidArgument("Integer: invalid EquivalentTo and/or Mod argument");

    Integer::RandomNumberType rnType = params.GetValueWithDefault("RandomNumberType", Integer::ANY);
    ...
}

ConstByteArrayParameter

The ConstByteArrayParameter class has a very sharp edge when you need to retrieve more than one array from an object. The problem is, there is one scratch variable in NameValuePairs so you cannot reference two different values.

The code below works fine because it is retrieving one ConstByteArrayParameter. The "single array" pattern repeats itself throughout the library:

template <class T>
size_t PKCS5_PBKDF1<T>::DeriveKey(byte *derived, size_t derivedLen,
    const byte *secret, size_t secretLen, const NameValuePairs& params) const
{
    byte purpose = (byte)params.GetIntValueWithDefault("Purpose", 0);
    unsigned int iterations = (unsigned int)params.GetIntValueWithDefault("Iterations", 1);

    double timeInSeconds = 0.0f;
    (void)params.GetValue("TimeInSeconds", timeInSeconds);

    ConstByteArrayParameter salt;
    (void)params.GetValue(Name::Salt(), salt);

    return DeriveKey(derived, derivedLen, purpose, secret, secretLen,
                 salt.begin(), salt.size(), iterations, timeInSeconds);
}

However, the HKDF class needed two ConstByteArrayParameter, and it produced incorrect results:

template <class T>
size_t HKDF<T>::DeriveKey(byte *derived, size_t derivedLen,
    const byte *secret, size_t secretLen, const NameValuePairs& params) const
{
    ConstByteArrayParameter salt;
    (void)params.GetValue(Name::Salt(), salt);

    ConstByteArrayParameter info;
    (void)params.GetValue("Info", info);

    return DeriveKey(derived, derivedLen, secret, secretLen,
                 salt.begin(), salt.size(), info.begin(), info.size());
}

The problem was not a simple "salt and info point to the same array". Because std::string backed the storage, we held a restricted pointer in two different places. The compiler punted and destroyed the underlying std::string while we were using it. Dragons flew out of our nose because of undefined behavior.

The fix was to copy the first array when we needed two of them. Below, both arrays are copied to a SecByteBlock.

template <class T>
size_t HKDF<T>::DeriveKey(byte *derived, size_t derivedLen,
    const byte *secret, size_t secretLen, const NameValuePairs& params) const
{
    SecByteBlock salt, info;
    ConstByteArrayParameter p;

    (void)params.GetValue(Name::Salt(), p);
    salt.Assign(p.begin(), p.size());

    (void)params.GetValue("Info", p);
    info.Assign(p.begin(), p.size());

    return DeriveKey(derived, derivedLen, secret, secretLen,
                 salt.begin(), salt.size(), info.begin(), info.size());
}

The problem does not surface in the test framework because the byte arrays and char strings are implicitly copied. For example, GetDecodedDatum below copies the array and then returns it as a string:

void TestSymmetricCipher(TestData &v, const NameValuePairs &overrideParameters)
{
    std::string name = GetRequiredDatum(v, "Name");
    std::string test = GetRequiredDatum(v, "Test");
    ...
}

Compile Error

Prior to Commit fd278c2e8b5c3688... If you receive a compiler error when compiling algparam.h:

1>d:...\cryptopp\algparam.h(322): error C2061: syntax error : identifier 'buffer'
1>          d:\work\app\tools\cryptopp\algparam.h(321) : while compiling class template member function
1>              'void CryptoPP::AlgorithmParametersTemplate<T>::MoveInto(void *) const'
1>          with
1>          [
1>              T=bool
1>          ]
1>          d:\work\app\tools\cryptopp\algparam.h(329) : see reference to class template instantiation 
1>              'CryptoPP::AlgorithmParametersTemplate<T>' being compiled
1>          with
1>          [
1>              T=bool
1>          ]

Then temporarily disable new when including Crypto++ headers:

#pragma push_macro("new")
#undef new
/* #includes for Crypto++ go here */
#pragma pop_macro("new")

Thanks to Angelo Geels at Stack Overflow.