提问者:小点点

InvertableRSA Function将Diffie-Hellman密钥加密为const字节*失败


在我们开始之前,我有一个服务器和一个客户端。我希望向客户端发送一个加密字符串,其中包含服务器的Diffie-Hellman公共静态密钥和公共临时密钥。为此,我使用服务器的私有RSA密钥发送加密字符串,客户端使用服务器的公共RSA密钥解密。

现在我需要这样做的原因是因为服务器是唯一具有公钥/私钥对的服务器。这很好,因为使用一个密钥对加密仍然可以切断针对Diffie-Hellman的MITM攻击的一侧,对于我的要求,这是可以的。

将静态和临时密钥转换为十六进制编码的字符串并通过套接字发送给我的私钥加密阶段带来了问题。

我的服务器正在做:

DH2 dhA(dh);
   SecByteBlock sprivA(dhA.StaticPrivateKeyLength()), spubA(
            dhA.StaticPublicKeyLength());
   SecByteBlock eprivA(dhA.EphemeralPrivateKeyLength()), epubA(
            dhA.EphemeralPublicKeyLength());

   dhA.GenerateStaticKeyPair(rnd, sprivA, spubA);
   dhA.GenerateEphemeralKeyPair(rnd, eprivA, epubA);

   string sendBuf, recvBuf;
   string saEncoded, eaEncoded, encoding;

   cout << "spubA: " << (char*) spubA.data() << endl << "epubA: "
            << (char*) epubA.data() << endl;

   SecByteBlock nil;
   nil.CleanNew(HMAC< SHA256 >::DEFAULT_KEYLENGTH);

   HMAC< SHA256 > hmac;
   hmac.SetKey(nil.data(), nil.size());

   HashFilter filter(hmac, new HexEncoder(new StringSink(encoding)));

   filter.Put(spubA.data(), spubA.size());
   filter.MessageEnd();

   saEncoded = encoding;
   encoding = "";

   filter.Put(epubA.data(), epubA.size());
   filter.MessageEnd();

   eaEncoded = encoding;
   encoding = "";

//   StringSource saSource(spubA, sizeof(spubA), true,
//          new HexEncoder(new StringSink(saEncoded)));
//
//   StringSource eaSource(epubA, sizeof(epubA), true,
//          new HexEncoder(new StringSink(eaEncoded)));
//
   sendBuf = saEncoded + " " + eaEncoded;

   cout << "Send Buffer: " << sendBuf << endl;
   SendMsg(sendBuf, tdata);

其中SendMsg()包含加密过程。

此时失败:

void SendMsg( string sendBuf, struct ThreadData * tdata )
{
   AutoSeededRandomPool rng;
   Integer m, c, r;
   stringstream ss;

   try
   {
      // Encode the message as an Integer
      m = Integer((const byte *) sendBuf.c_str(), sendBuf.size());

      //Encrypt
      c = tdata->privateKey.CalculateInverse(rng, m);  //HERE!

带有错误消息:

InvertibleRSAFunction: computational error during private key operation

Diffie-Hellman secition中目前没有注释掉的代码是从这里获取的。注释代码的问题在于,当客户端收到十六进制编码的字符串时,它已经丢失了数据,无法就共享秘密达成一致。但它确实打通了套接字。

这方面的一个例子可以显示:

Server:
    spubA: &a�|՜D2�tu�cJ����B�R�8�*i�x?N���p��Q�����K��+O �"��P:k�d|3�����6Z
    epubA: 4v������M�E�`l�K��[dN�|Q^r�-ż�����A~D�>4$�9���"v�*:Y��s�O���J��ow�M�߬�C�9n�;���Z�D�6lp�V��oowZ��WSv��",��A3��XL��8��
    Send Buffer: 2661DC7CD59C4432AF747584634AF69BE60298429C52C738 3476CBCCFAA3B0A14DBE45E3606CC84B171DAC1CCE5B644E

Client:
    Recovered: 2661DC7CD59C4432AF747584634AF69BE60298429C52C738 3476CBCCFAA3B0A14DBE45E3606CC84B171DAC1CCE5B644E
    SA: 2661DC7CD59C4432AF747584634AF69BE60298429C52C738
    EA: 3476CBCCFAA3B0A14DBE45E3606CC84B171DAC1CCE5B644E
    Decoded SA: &a�|՜D2�tu�cJ����B�R�8
    Decoded EA: 4v������M�E�`l�K��[dN

关于这个实例,我在客户端尝试执行以下操作:

// Get spubA and epubA from server
      recovered = recoverMsg(serverKey, sockServer);

      //Calculate shared secret.
      string sa, ea;
      ss.str(recovered);
      ss >> sa >> ea;
      ss.str("");
      ss.clear();

      cout << "SA: " << sa << endl << "EA: " << ea << endl;

      string decodedSA, decodedEA;
      StringSource decodeSA(sa, true,
               new HexDecoder(new StringSink(decodedSA)));
      StringSource decodeEA(ea, true,
               new HexDecoder(new StringSink(decodedEA)));

      cout << "Decoded SA: " << decodedSA << endl;
      cout << "Decoded EA: " << decodedEA << endl;

      SecByteBlock spubA((const byte*) decodedSA.data(), decodedSA.size());

      if ( spubA.size() < dhB.StaticPublicKeyLength() ) spubA.CleanGrow(
               dhB.StaticPublicKeyLength());
      else spubA.resize(dhB.StaticPublicKeyLength());

      SecByteBlock epubA((const byte*) decodedEA.data(), decodedEA.size());

      if ( epubA.size() < dhB.EphemeralPublicKeyLength() ) epubA.CleanGrow(
               dhB.EphemeralPublicKeyLength());
      else epubA.resize(dhB.EphemeralPublicKeyLength());

但我还是得到了同样的结果。

有人知道如何使用服务器的私钥加密它并正确通过套接字发送吗?


共2个答案

匿名用户

我实际上解决了我所有的问题,在通过网络发送之前,我只对所有内容进行Base64编码。我还在每次发送后添加了一个接收,在每次接收后添加了一个发送,以确保服务器和客户端正确同步在一起,以避免时间问题。不需要所有那些“HexEncode”的废话。只要你喜欢把它变成一个字符串,把它发送到这些函数中,我们就开始了。

string RecoverMsg( struct ThreadData * tdata )
{
   try
   {
      Integer c = 0, r = 0, m = 0;
      size_t req = 0, bytes = 0;
      AutoSeededRandomPool rng;
      string recovered = "", ack = "", decodedCipher = "";
      byte byteBuf[ 2000 ];
      memset(byteBuf, 0, sizeof(byteBuf));

      // Retrieve message from socket
      cout << "Waiting to receive a message from client " << tdata->tid << endl;

      bytes = tdata->sockSource.Receive(byteBuf, sizeof(byteBuf));
      cout << "Bytes Read: " << bytes << endl;

      cout << "Encoded Cipher Received: " << byteBuf << endl;

      decodedCipher;
      StringSource(byteBuf, sizeof(byteBuf), true,
               new Base64Decoder(new StringSink(decodedCipher)));

      c = Integer(decodedCipher.c_str());

      // Decrypt
      r = tdata->privateKey.CalculateInverse(rng, c);
      cout << "r: " << r << endl;

      // Round trip the message
      req = r.MinEncodedSize();
      recovered.resize(req);
      r.Encode((byte *) recovered.data(), recovered.size());

      cout << "Recovered: " << recovered << endl;

      ack = "ACK";
      bytes = tdata->sockSource.Send((const byte*) ack.c_str(), ack.size());

      return recovered;
   }
   catch ( Exception& e )
   {
      cerr << "caught Exception..." << endl;
      cerr << e.what() << endl;
      tdata->sockSource.ShutDown(SHUT_RDWR);
   }
}

void SendMsg( string sendBuf, struct ThreadData * tdata )
{
   try
   {
      AutoSeededRandomPool rng;
      stringstream ss("");
      string cipher = "", encodedCipher = "";
      Integer m = 0, c = 0, r = 0;
      size_t bytes = 0;
      byte ack[ 10 ];
      memset(ack, 0, sizeof(ack));

      // Treat the message as a big endian array
      m = Integer((const byte *) sendBuf.c_str(), sendBuf.size());
      cout << "m: " << m << endl;

      // Encrypt
      c = tdata->privateKey.CalculateInverse(rng, m);

      ss << c;
      cipher = ss.str();
      ss.str("");
      ss.clear();

      // Base64 encode the cipher
      encodedCipher;
      StringSource(cipher, cipher.size(),
               new Base64Encoder(new StringSink(encodedCipher)));

      cout << "Encoded Cipher Sent: " << encodedCipher << endl;

      // Send the cipher
      bytes = tdata->sockSource.Send((const byte*) encodedCipher.c_str(),
               encodedCipher.size());
      cout << "Bytes Written: " << bytes << endl;

      bytes = tdata->sockSource.Receive(ack, sizeof(ack));
   }
   catch ( Exception& e )
   {
      cerr << "caught Exception..." << endl;
      cerr << e.what() << endl;
      tdata->sockSource.ShutDown(SHUT_RDWR);
   }
}

匿名用户

嗯,关于HexEncode后丢失的数据,这肯定是由nn(spubA)引起的,因为sizeof()返回的是指向spubA的指针的大小,而不是字符串的大小。要获得字符串的长度,您可以使用spubA. long()函数,或者简单地使用CryptoPP的最佳方法,IMHO,就像您对客户端所做的那样,只需不指定大小:

StringSource saSource(spubA, true,
      new HexEncoder(new StringSink(saEncoded)));

现在关于你的其余代码,看起来你正在尝试执行你自己的教科书RSA,既然你开始将缓冲区转换为整数,请不要煮你自己的RSA!它注定要失败,RSA教科书很弱!

因此,利用CryptoPP功能并使用良好的填充方案执行RSA加密,例如使用加密器和过滤器的OAEP,如CryptoPP的wiki所示:

RSAES_OAEP_SHA_Encryptor enc(key);
StringSource(inputString, true,
    new PK_EncryptorFilter(rng, enc,
        new HexEncoder(
            new StringSink(output), false)));

cout << output << endl;

现在这里你必须小心的一点是使用正确的密钥,因为你想使用私钥加密,你必须初始化你的密钥如下:

RSA::PublicKey key;
key.Initialize(privateKey.GetModulus(), privateKey.GetPrivateExponent();

就是这样,应该已经可以工作了。要解密,只需使用:

RSA::PrivateKey clientkey;
clientkey.SetModulus(publicKey.GetModulus());
clientkey.SetPrivateExponent(publicKey.GetPublicExponent());

RSAES_OAEP_SHA_Decryptor d(privateKey);
StringSource(decodedInput, true,
    new PK_DecryptorFilter(rng, d,
        new HexEncoder(
            new StringSink(output), false //lowercase
        )));

cout << output << endl;

但是,有一个但是:当你用私钥加密时,不要用私钥初始化来解密,因为初始化会尝试分解模数,如果不知道至少公共指数和私有指数,它就无法做到这一点。

现在,我必须承认,我还没有测试使用私钥和RSAOAEP进行加密/解密,如果我最新的代码片段失败了(也许RSAES_OAEP_SHA_Decryptor不能使用未定义质数的私钥…我在盲目输入,没有编译器),那么你将不得不酝酿自己的RSA,就像你现在正在尝试的那样。如果你需要更多的帮助,请在下面评论,我明天会尝试生成我的编译器,看看它是如何工作的。

但是请注意,RSA教科书非常薄弱,因此您应该使用OAEP填充数据。