如何从字符串/字节数组或其他容器中加载私钥/公钥

12

是否可以将RSA私钥/公钥存储在源代码中,例如在byte[]string或任何其他container中,并使用此密钥进行加密/解密?

从文件解码的函数将类似于:

void Decode(const string& filename, BufferedTransformation& bt)
{
    // http://www.cryptopp.com/docs/ref/class_file_source.html
    FileSource file(filename.c_str(), true /*pumpAll*/);

    file.TransferTo(bt);
    bt.MessageEnd();
}

从文件中加载密钥,这不是我想要的。

我知道这肯定是可能的,因为我可以使用AutoSeededRandomPool创建密钥。

我只是不知道如何使用现有的密钥。

也许我在文档中忽略了这部分内容。


其实,我现在只是想知道是否选择了合适的源/汇(例如ArraySink/ArraySource而不是FileSource/FileSink),而不是像我回答的那样对密钥材料进行编码/解码。你能澄清一下吗? - softwariness
@softwariness,你的回答真的很好,但是确实更多地涉及选择合适的源/汇 :D 我不知道ArraySink,这可能会有所帮助。如果你在你的回答中也能包含一些关于汇的内容,我会接受它。 - deW1
现在的答案已经更新,其中包括一个示例,展示了如何通过内存缓冲区往返传递密钥材料,以及另一个示例,展示了使用StringSink/StringSource将密钥材料存储在std::string中,从而避免了一些缓冲区管理问题。 - softwariness
@softwariness 很好的东西,谢谢 :) - deW1
@deW1 - 你有兴趣使用哪些C++容器? - jww
@jww,你现在已经很好地涵盖了我需要的那些内容 :) - deW1
4个回答

20

可能会对您感兴趣的是Crypto++ Keys and FormatsCrypto++ RSA Cryptography页面。

如果您正在生成RSA参数,可以像这样进行:

AutoSeededRandomPool rng;

InvertibleRSAFunction params;
params.GenerateRandomWithKeySize(rng, 2048);
你可以使用InvertibleRSAFunctionDEREncodeBERDecode方法来分别编码和解码所有参数:
{
    FileSink output("rsaparams.dat");
    params.DEREncode(output);
}

InvertibleRSAFunction params2;
{
    FileSource input("rsaparams.dat", true);
    params2.BERDecode(input);
}

若要单独编码/解码私钥和公钥材料,请在RSA::PrivateKeyRSA::PublicKey对象本身上使用DEREncodeBERDecode方法:

// Initialize keys from generated params
RSA::PrivateKey rsaPrivate(params);
RSA::PublicKey rsaPublic(params);

// Write keys to file
{
    FileSink output("rsaprivate.dat");
    rsaPrivate.DEREncode(output);
}
{
    FileSink output("rsapublic.dat");
    rsaPublic.DEREncode(output);
}

// Read keys from file into new objects
RSA::PrivateKey rsaPrivate2;
RSA::PublicKey rsaPublic2;
{
    FileSource input("rsaprivate.dat", true);
    rsaPrivate2.BERDecode(input);
}
{
    FileSource input("rsapublic.dat", true);
    rsaPublic2.BERDecode(input);
}

FileSourceFileSink只是您可以使用的示例源和汇对象。编码/解码例程接受BufferedTransformation对象作为参数,因此您可以使用任何其他适当实现该接口的实现。

例如,ArraySink可用于将数据写入您提供的内存缓冲区,而StringSource(也称为ArraySource可用于从缓冲区中读取。

以下是一些代码示例,展示如何使用ArraySinkArraySource通过std :: vector <byte>往返传输私钥材料:

RSA::PrivateKey rsaPrivate(params);
std::vector<byte> buffer(8192 /* buffer size */);

ArraySink arraySink(&buffer[0], buffer.size());
rsaPrivate.DEREncode(arraySink);

// Initialize variable with the encoded key material
// (excluding unwritten bytes at the end of our buffer object)
std::vector<byte> rsaPrivateMaterial(
    &buffer[0],
    &buffer[0] + arraySink.TotalPutLength());

RSA::PrivateKey rsaPrivate2;
ArraySource arraySource(
    &rsaPrivateMaterial[0],
    rsaPrivateMaterial.size(),
    true);
rsaPrivate2.BERDecode(arraySource);

(另请参见@jww的答案,该答案通过使用ByteQueue避免了固定大小的缓冲区,提供了一个示例)。

另一个例子使用std::string存储密钥材料,并使用StringSink类将其写入,这避免了部分缓冲区管理(字符串会自动调整大小以匹配编码的数据量)。请注意,即使它在std::string对象中,这仍然是二进制数据。

RSA::PrivateKey rsaPrivate(params);

std::string rsaPrivateMaterial;
StringSink stringSink(rsaPrivateMaterial);
rsaPrivate.DEREncode(stringSink);

RSA::PrivateKey rsaPrivate2;
StringSource stringSource(rsaPrivateMaterial, true);
rsaPrivate2.BERDecode(stringSource);

如果您想自行控制格式,可以使用InvertibleRSAFunction对象或密钥对象的方法来提取单个参数(如上面所示的"Crypto++ RSA Cryptography"链接),并使用这些参数来提取存储在自己格式中的值:

const Integer& n = params.GetModulus();
const Integer& p = params.GetPrime1();
const Integer& q = params.GetPrime2();
const Integer& d = params.GetPrivateExponent();
const Integer& e = params.GetPublicExponent();

当从文件或容器中读取这些内容时,可以使用相应的setter方法(SetModulus(), SetPrime1()等)将它们恢复为新的InvertibleRSAFunctionRSA::*Key实例。


4
如何从字符串/字节数组或任何其他容器中加载私钥/公钥?
Crypto++库内置支持std:strings。但其他C++容器会更棘手。ArraySource已添加到Crypto++ 5.6,但它只是StringSource的typedef。
如果您正在使用敏感材料,则还应考虑使用SecByteBlock。当析构函数运行时,它将擦除或清零敏感材料。
字符串和StringSource(load)
string spki = ...;
StringSource ss(spki, true /*pumpAll*/);

RSA::PublicKey publicKey;
publicKey.Load(ss);

向量和数组源(加载)
vector<byte> spki = ...;
ArraySource as(&spki[0], spki.length(), true /*pumpAll*/);

RSA::PublicKey publicKey;
publicKey.Load(as);

字符串和StringSink(保存)
string spki;
StringSink ss(spki);

RSA::PublicKey publicKey(...);
publicKey.Save(ss);

向量(保存)
请参见下面的代码。
以下是将数据保存到和从 std::vector 加载的示例。由于不能轻松创建 VectorSink,因此必须使用中间的 ByteQueue 进行保存。
AutoSeededRandomPool prng;
RSA::PrivateKey pk1, pk2;

pk1.Initialize(prng, 1024);
ByteQueue queue;
pk1.Save(queue);

vector<byte> spki;
spki.resize(queue.MaxRetrievable());

ArraySink as1(&spki[0], spki.size());
queue.CopyTo(as1);

ArraySource as2(&spki[0], spki.size(), true);
pk2.Load(as2);

bool valid = pk2.Validate(prng, 3);
if(valid)
    cout << "Validated private key" << endl;
else
    cout << "Failed to validate private key" << endl;

我们没有明确的 VectorSink,也不能轻易地创建一个,因为它隐含地期望 traits_type::char_type。例如:

using CryptoPP::StringSinkTemplate;
typedef StringSinkTemplate< std::vector<byte> > VectorSink;

In file included from cryptopp-test.cpp:65:
In file included from /usr/local/include/cryptopp/files.h:5:
/usr/local/include/cryptopp/filters.h:590:22: error: no member named
      'traits_type' in 'std::vector<unsigned char, std::allocator<unsigned char>
      >'
        typedef typename T::traits_type::char_type char_type;
                         ~~~^
cryptopp-test.cpp:243:20: note: in instantiation of template class
      'CryptoPP::StringSinkTemplate<std::vector<unsigned char,
      std::allocator<unsigned char> > >' requested here
        VectorSink vs(spki);

你可以创建VectorSourceVectorSink,但需要一些工作。你可以通过查看filters.hfilters.cpp中的StringSourceStringSink源代码来了解所需的工作量。

这一行不应该是publicKey.Save(spki);而是publicKey.Save(ss);,因为Save()需要一个缓冲转换。 - deW1
same should apply for Load() - deW1
@deW1 - 是的,已经整理好了。谢谢(对此我很抱歉)。 - jww
我创建了一个VectorSink的最小实现:http://stackoverflow.com/questions/39350337/saving-cryptopp-objects-to-stdvector - psyched

0
我该如何从字符串/字节数组或任何其他容器中加载私钥/公钥?
//Create Cryptopp StringSource From Std::string
std::string PublicKeyString = "<Your key as std::string value>";
CryptoPP::StringSource PKeyStringSource(PublicKeyString, true);
CryptoPP::RSA::PublicKey publicKey;
publicKey.Load(PKeyStringSource);

虽然我不确定cryptopp是否支持CryptoPP :: StringSource的本机容器。但我相信,将容器存储为std :: vector >应该可以在这里发挥作用。


0
如果您按照以下方式创建DSA密钥,您将得到两个文件,一个包含私钥,另一个包含公钥。
void CreateDsaKeys(std::string folder)
{
    AutoSeededRandomPool rng;
    // Generate Private Key
    DSA::PrivateKey privateKey;
    privateKey.GenerateRandomWithKeySize(rng, 1024);

    // Generate Public Key
    DSA::PublicKey publicKey;
    publicKey.AssignFrom(privateKey);
    if (!privateKey.Validate(rng, 3) || !publicKey.Validate(rng, 3))
    {
       throw runtime_error("DSA key generation failed");
    }
    std::string publicPath = folder + "/publickey.txt";
    std::string privatePath = folder + "/privatekey.txt";
    SaveHexPublicKey(publicPath, publicKey);
    SaveHexPrivateKey(privatePath, privateKey);
}

将这两个文件的内容复制到您的源代码中,并将它们放入字符串中:

std::string publickey("308201B73082012C...F752BB791");

std::string privatekey("3082014C0201003...0B8E805D83E9708");

然后,您可以使用HexDecoder将字符串转换为字节,并使用这些字节创建公钥和私钥:

bool LoadDsaKeysFromStringsAndTest()
{
    AutoSeededRandomPool rng;
    HexDecoder decoderPublic;
    decoderPublic.Put((byte*)publickey.data(), publickey.size());
    decoderPublic.MessageEnd();
    HexDecoder decoderPrivate;
    decoderPrivate.Put((byte*)privatekey.data(), privatekey.size());
    decoderPrivate.MessageEnd();
    DSA::PublicKey publicKey;
    publicKey.Load(decoderPublic);
    DSA::PrivateKey privateKey;
    privateKey.Load(decoderPrivate);
    string message = "DSA Signature";
    string signature;
    try {
        DSA::Signer signer( privateKey );
        StringSource ss1( message, true,
            new SignerFilter( rng, signer,
                new StringSink( signature )
            ) // SignerFilter
        ); // StringSource

        bool result = false;
        DSA::Verifier verifier1( publicKey );
        StringSource ss(message+signature, true,
                new SignatureVerificationFilter(verifier1,
                        new ArraySink((uint8_t*)&result, sizeof(result)),
                        SignatureVerificationFilter::PUT_RESULT | SignatureVerificationFilter::SIGNATURE_AT_END)
                );
        return result;
    }
    catch(const CryptoPP::Exception& e)
    {
        std::cerr << e.what() << std::endl;
    }
    return false;
}

这些是保存密钥所需的其他例程。
void Save(const string& filename, const BufferedTransformation& bt)
{
    FileSink file(filename.c_str());
    bt.CopyTo(file);
    file.MessageEnd();
}

void SaveHex(const string& filename, const BufferedTransformation& bt)
{
    HexEncoder encoder;
    bt.CopyTo(encoder);
    encoder.MessageEnd();
    Save(filename, encoder);
}

void SaveHexPrivateKey(const string& filename, const PrivateKey& key)
{
    ByteQueue queue;
    key.Save(queue);
    SaveHex(filename, queue);
}

void SaveHexPublicKey(const string& filename, const PublicKey& key)
{
    ByteQueue queue;
    key.Save(queue);
    SaveHex(filename, queue);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接