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. CallGetValueNames()
on aNameValuePairs
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, callGetIntValue()
with the value name, and if the type is not int, aValueTypeMismatch
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 ¶meters) { 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 ¶ms) { 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.