Search code examples
c++opensslhexaes

Need more performant hex2bytes bytes2hex encoding/decoding


I write an encryption library which uses AES256 CBC based on openssl (libcrypto.so). My library is written in C++ (C++11, actually) Profiling with valgrind shows, that the encryption/decryption is very fast, but my encoding/decoding of the resulting bytes to a hex string is slow.

When encrypting, encode_bytes2hex needs 50% of the total program runtime. When decrypting decode_hex2bytes needs even 75% of the total runtime. I want at least have decode_hex2bytes to be as fast as encode_bytes2hex. But it would also be nice to decrease their runtime by factor 10 or so. I expect these "little" functions to use only insignificant parts of the total runtime.

Here are my implementations:

std::string encode_bytes2hex(const std::vector<unsigned char>& rData){
  //encode as hex 
  std::stringstream ss;
  ss << std::hex << std::setfill('0');
  for (auto& c : rData)
  {
      ss << std::setw(2) << static_cast<int>(c);
  }
  return ss.str();
}

std::vector<unsigned char> decode_hex2bytes(const std::string& rHex){
  std::vector<unsigned char> oBytes(rHex.length()/2);
  std::stringstream ssConv;
  for(size_t n = 0; n < rHex.length(); n+=2)
  {
      ssConv << std::hex << rHex.substr(n,2);
      int byte;
      ssConv >> byte;
      oBytes[n/2] = byte & 0xFF;
      ssConv.str(std::string());
      ssConv.clear();
  }
  return oBytes;
}

I guess removing std::stringstream somehow could speed-up everything a lot. But how?


Solution

  • Thanks to the answers of @Toby Speight and @Pepijn Kramer I could create much faster functions. This is my solution for C++11

    constexpr char to_hex1(unsigned char c) {
        return "0123456789abcdef"[c/16];
    };
    
    constexpr char to_hex2(unsigned char c) {
        return "0123456789abcdef"[c%16];
    };
    
    std::string encode_bytes2hex(const std::vector<unsigned char>& rData)
    {
        std::string result;
        result.reserve(rData.size() * 2);
    
        for (auto c: rData) {
            result.push_back(to_hex1(c));
            result.push_back(to_hex2(c));
        }
        return result;
    }
    
    constexpr unsigned char from_hex(char nC) {
    
        return (nC >= '0' && nC <= '9') ? (nC - '0') : 
                ((nC >= 'a' && nC <= 'f') ? (nC - 'a' +10u) : 
                (nC >= 'A' && nC <= 'F') ? (nC - 'A' +10u) : 
                nC 
                );
    };
    
    std::vector<unsigned char> decode_hex2bytes(const std::string& rHex)
    {
        if (rHex.size() % 2) {
             throw std::invalid_argument("hex string of odd length");
        }
        std::vector<unsigned char> oResult;
        oResult.reserve(rHex.size()/2);
    
        for (std::size_t i = 0;  i < rHex.size();  i += 2) {
            oResult.push_back(from_hex(rHex[i]) * 16u + from_hex(rHex[i+1]));
        }
        return oResult;
    }
    

    I had to write from_hex in a disgusting way, because C++11 does only allow constexpr functions with nothing than a return statement. For C++14 and newer it is possible to write cleaner code. But I must say, even with this code these functions are much much much slower than the AES encryption/decryption routines in openssl itsself. (In my problem I encrypt a string, then encode the bytes to hex).

    Including the -O3 parameter for gcc I could speed up my decryption throughput from approx 80k per second to 475k per second. This is a 6x speed-up. The encryption is now up from 175k to 475k which is a speed-up of approx 3x.