0

I am encrypting a message using RSA.

Everything works fine if I try to encrypt/decrypt the message locally, however, when converting the string to void constant* in order to use the socket::send method like so,

int sendRes = send(socket, tempMessage.c_str(), tempMessage.size() + 1, 0);

the following happens,

My original string, tempMessage

"_��JE<D�p�-d�R�U���`}�+�;���|[�t�Ԕ���^;D�Mn��?ÕW>�-�6���Pr��[͉dZ%�"

is truncated to this, when using tempMessage.c_str()

"_��JE<D�p�-d�R�U���`}�+�;���|[�"

I do know this is because, during encryption, there is a \0 symbol that gets appended in between once or several times, terminating thus everything there when using c_str().

I also know it happens before using send(), I put it here so that you know why I am using c_str() in the first place.

It's there a way to bypass this? or a workaround?

I have seen this previous answer but did not help with my situation and could not find any other solution out there.

Thanks in advance for the help!

  • Yes, there is a workaround, don't use c strings. – eXCore Mar 15 '21 at 18:47
  • Could you elaborate how would you do it for this particular example? thanks – Edgar Hernandez Mar 15 '21 at 18:49
  • 6
    the `send()` system call does not bother about `\0`. I guess `tempMessage` is fully sent (with an additional `\0`), but on the `recv()` side you decide to stop at the first `\0` met, which is not necessarily the last one. – prog-fh Mar 15 '21 at 18:50
  • https://en.cppreference.com/w/cpp/string/basic_string/data – eXCore Mar 15 '21 at 18:50
  • Yep i know send() does not bother about '\0', my question is how to get the right void constant pointer to be used for send since having c_str() will be terminating my string once it has a `\0` – Edgar Hernandez Mar 15 '21 at 18:52
  • `c_str()` or `data()` will do what you want (since C++11 both are nul terminated) since the amount of bytes sent is controlled by `size()+1`. – prog-fh Mar 15 '21 at 18:53
  • Yeah have tried both and neither worked, they are still stopping and cutting the whole string whenever there is a '\0'. – Edgar Hernandez Mar 15 '21 at 18:55
  • Show your recv end? – eXCore Mar 15 '21 at 18:55
  • 2
    How are you certain the cutting does not happen on the `recv()` side? – prog-fh Mar 15 '21 at 18:55
  • Check the size of `tempMessage`. You may be constructing it with just a `const char *` pointer, at which point only the text up to the first null character would be copied. – François Andrieux Mar 15 '21 at 18:56
  • @prog-fh i am using cout beforehand – Edgar Hernandez Mar 15 '21 at 18:56
  • `cout` will probably cut your `char*` at null terminator tho – eXCore Mar 15 '21 at 18:58
  • 3
    @EdgarHernandez If you pass a `char*` to `operator<<`, it will be treated as a null-terminated string. Pass a properly sized `std::string` instead, or at least use `cout.write()` instead. Either way, encryption operates on binary bytes, not text characters, so you really shouldn't be using `std::string` for encruption at all, but if you must, you have to take the byte length into account properly. That also means sending and receiving the actual byte length with `send()` and `recv()`, not relying on a null terminator at all. – Remy Lebeau Mar 15 '21 at 18:58
  • Ah OK, so your question has nothing to do with `send()`. It's just about building a string containing `\0` in the middle. build it with address+size, not just address. – prog-fh Mar 15 '21 at 18:59
  • @RemyLebeau I have also used written it to a log file using `ofstream`, would this behave the same as `cout`? – Edgar Hernandez Mar 15 '21 at 19:01
  • @EdgarHernandez yes, `ofstream` would behave the same as `cout`. For non-textual data, open the `ofstream` in binary mode and use `write()` to write binary data to it – Remy Lebeau Mar 15 '21 at 19:01
  • @RemyLebeau could you elaborate on how would you take byte length into account to use with send()? – Edgar Hernandez Mar 15 '21 at 19:03
  • 1
    if you use `.c_str()` or `data()` to print to a stream, then the only information provided is the beginning of the C-like string; thus the printing will stop at the first nul-char met. But using this exact same address **and** the size information with `send()` will consider all the chars. – prog-fh Mar 15 '21 at 19:04
  • 1
    @EdgarHernandez Send the length of the string, then send your string. – eXCore Mar 15 '21 at 19:05
  • @prog-fh will give it a go! – Edgar Hernandez Mar 15 '21 at 19:25
  • @EdgarHernandez -- The bottom line is this -- don't use *any* function that relies on null-termination. Read up on every function you're using to send, read, print, and do whatever else you're doing with the data in its entirety, and ensure you are not using those functions in a way where it is relying on null-termination. Only functions that specify a length argument in some way are the ones you should be using. – PaulMcKenzie Mar 15 '21 at 19:29
  • What is the purpose of the "+1" here: `tempMessage.size() + 1`?! If you're thinking you need to send the terminating zero byte, you've got a big issue. Zero bytes are legal in the string and so you can't also use a zero to mark the end of the string. – David Schwartz Mar 15 '21 at 19:59
  • On the sending end do you check to make sure `send()` sends all the bytes? On the receiving end how do you know how long the message is? You can't look for `'\0'` (null) as this is binary data and so may naturally contain the null. You may want to send the size of the text block first so the receiving end knows that it should read a size then that will tell it how many bytes of message need to be read from the stream. – Martin York Mar 15 '21 at 21:22

2 Answers2

2

Here is a simple example showing

  • the problem with displaying c_str() on a string with nul-chars.
  • two primitives for sending/receiving strings.

The idea is to send/receive the length of the string on a fixed number of bytes before the actual chars.

/**
  g++ -std=c++17 -o prog_cpp prog_cpp.cpp \
      -pedantic -Wall -Wextra -Wconversion -Wno-sign-conversion \
      -g -O0 -UNDEBUG -fsanitize=address,undefined
**/

#include <iostream>
#include <string>
#include <stdexcept>

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

ssize_t
send_all(int sockfd,
         const void *buf,
         size_t len,
         int flags)
{
  const auto *p=static_cast<const char *>(buf);
  auto remaining=len;
  while(remaining)
  {
    const auto r=send(sockfd, p, remaining, flags);
    if(r<0)
    {
      return r;
    }
    remaining-=r;
    p+=r;
  }
  return len-remaining;
}

ssize_t
recv_all(int sockfd,
         void *buf,
         size_t len,
         int flags)
{
  char *p=static_cast<char *>(buf);
  auto remaining=len;
  while(remaining)
  {
    const auto r=recv(sockfd, p, remaining, flags);
    if(r<0)
    {
      return r;
    }
    if(r==0) // EOF
    {
      break;
    }
    remaining-=r;
    p+=r;
  }
  return len-remaining;
}

void
send_string(int sockfd,
            const std::string &str,
            int flags)
{
  const auto length=htonl(std::int32_t(size(str)));
  if(send_all(sockfd, &length, sizeof(length), flags)!=sizeof(length))
  {
    throw std::runtime_error{"cannot send length"};
  }
  if(send_all(sockfd, data(str), size(str), flags)!=
     static_cast<ssize_t>(size(str)))
  {
    throw std::runtime_error{"cannot send string"};
  }
}

std::string
recv_string(int sockfd,
            int flags)
{
  auto str=std::string{};
  auto length=std::int32_t{};
  const auto r=recv_all(sockfd, &length, sizeof(length), flags);
  if(r==0) // EOF
  {
    return str;
  }
  if(r!=sizeof(length))
  {
    throw std::runtime_error{"cannot receive length"};
  }
  str.resize(ntohl(length));
  if(recv_all(sockfd, data(str), size(str), flags)!=
     static_cast<ssize_t>(size(str)))
  {
    throw std::runtime_error{"cannot receive string"};
  }
  return str;
}

int
main()
{
  auto msg=std::string{};
  msg+="there is a nul-char...";
  msg+='\0';
  msg+="in the middle!";
  std::cout << "as a string <" << msg << ">\n";
  std::cout << "as a const char * <" << msg.c_str() << ">\n";
  std::cout << "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n";
  int fd[2];
  if(socketpair(PF_LOCAL, SOCK_STREAM, 0, fd)<0)
  {
    throw std::runtime_error{"socketpair() failure"};
  }
  for(auto i=0; i<5; ++i) // very few to fit in internal buffer
  {
    send_string(fd[1], "message number "+std::to_string(i+1), 0);
  }
  close(fd[1]);
  for(;;)
  {
    const auto s=recv_string(fd[0], 0);
    if(empty(s))
    {
      break;
    }
    std::cout << '[' << s << "]\n";
  }
  close(fd[0]);
  return 0;
}
/**
as a string <there is a nul-char...in the middle!>
as a const char * <there is a nul-char...>
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[message number 1]
[message number 2]
[message number 3]
[message number 4]
[message number 5]
**/
prog-fh
  • 13,492
  • 1
  • 15
  • 30
0

In the end, it was an easier fix.

Since all messages are encrypted following the same type, the length of the bits will always be the same for the encrypted part.

In my case, this was 256, meaning that I can now send-receive without any further issues since I now know the real string length.

If this would not have been the case, I would have gone with @prog-fh answer