将十六进制字符串转换为字节数组

54

如何将一个可变长度的十六进制字符串(例如"01A1")转换为包含该数据的字节数组。

即将此转换为:

std::string = "01A1";

变成这样

char* hexArray;
int hexLength;

或者这个

std::vector<char> hexArray;

我希望将其写入文件,并使用hexdump -C命令,以便获取包含01A1的二进制数据。


16
@alexvii 这并不是这个问题的答案。 - dhavenith
2
您可以将std::streams设置为十六进制模式,以便以十六进制格式读写数字。 - πάντα ῥεῖ
@makulik 我尝试使用流和std::hex,但是无法使其正常工作。你能给我一个例子吗?谢谢。 - oracal
我认为不需要任何ASCII减法,只需使用C API将其转换为字符数组,除非我理解问题有误。我在下面的答案中指出了API http://stackoverflow.com/a/17273020/986760。 - fkl
根据您对另一个答案的评论,我认为您需要在问题中添加一些内容,以说明当输入字符数为奇数时应该发生什么。缺失的0应该添加到字符串的开头还是结尾? - Zan Lynx
@oracal 请看我的答案,使用stringstream方法。 - TheoretiCAL
23个回答

49

此实现使用内置的strtol()函数来处理从文本到字节的实际转换,但适用于任何偶数长度的十六进制字符串。

std::vector<char> HexToBytes(const std::string& hex) {
  std::vector<char> bytes;

  for (unsigned int i = 0; i < hex.length(); i += 2) {
    std::string byteString = hex.substr(i, 2);
    char byte = (char) strtol(byteString.c_str(), NULL, 16);
    bytes.push_back(byte);
  }

  return bytes;
}

1
你可以在十六进制字符串长度为奇数时,在前面添加'0'。 - user482963

44
这应该可以运行:
int char2int(char input)
{
  if(input >= '0' && input <= '9')
    return input - '0';
  if(input >= 'A' && input <= 'F')
    return input - 'A' + 10;
  if(input >= 'a' && input <= 'f')
    return input - 'a' + 10;
  throw std::invalid_argument("Invalid input string");
}

// This function assumes src to be a zero terminated sanitized string with
// an even number of [0-9a-f] characters, and target to be sufficiently large
void hex2bin(const char* src, char* target)
{
  while(*src && src[1])
  {
    *(target++) = char2int(*src)*16 + char2int(src[1]);
    src += 2;
  }
}

根据您使用的特定平台,可能还有标准实现。


@NielsKeurentjes,使用c_str()有什么问题吗?为什么我们要手动将ASCII的'A'转换为十六进制A并放入目标char *中。你所做的是正确的。我只是不明白为什么在有标准API可以将字符串转换为字符数组时还要手动执行此操作。 - fkl
3
@fayyazkl,你误解了问题 - 这是关于将可读的4字符字符串“01A1”转换为内存中的2个字节(1和161)。因此,显然需要进行ASCII转换。 - Niels Keurentjes
@Christophe 因为 while 循环检查 *src && src[1],所以它会解析 AF,然后在 src[1] 上遇到一个尾随的零,停止转换。在这方面,它类似于 atoi 的行为 - 它会停止处理损坏的输入。 - Niels Keurentjes
1
@NielsKeurentjes 我知道我正在回复几年前发布的答案,但我只是想感谢您的解决方案!这正是我所需要的,我之前使用了另一种在网上找到的方法,但它有几个问题,并且没有完全按照我的期望工作。再次感谢!:) - Coder1337
1
喜欢这个解决方案。作为一个小观察,需要注意的是,char2input可以稍微更有效率一些,特别是如果被频繁使用。请注意,任何有效的字符都将大于等于'0',因此最好先测试第二个字符,如下所示:int char2int(char input) { if (input <= '9' && input >= '0') return input - '0'; if (input <= 'F' && input >= 'A') return input - 'A' + 10; if (input <= 'f' && input >= 'a') return input - 'a' + 10; } 或按顺序测试a、A和0。 - BenV136
显示剩余6条评论

14

你可以使用boost库:

#include <boost/algorithm/hex.hpp>

char bytes[60] = {0}; 
std::string hash = boost::algorithm::unhex(std::string("313233343536373839")); 
std::copy(hash.begin(), hash.end(), bytes);

11

出于好玩,我很好奇是否可以在编译时进行这种转换。它没有很多错误检查,并且在VS2015中完成,该版本还不支持C ++14的constexpr函数(因此HexCharToInt看起来像这样)。它接受一个c字符串数组,将一对字符转换为单个字节,并将这些字节扩展为用于初始化提供的T类型的统一初始化列表模板参数。 T可以被替换为类似std :: array的东西,以自动返回数组。

#include <cstdint>
#include <initializer_list>
#include <stdexcept>
#include <utility>

/* Quick and dirty conversion from a single character to its hex equivelent */
constexpr std::uint8_t HexCharToInt(char Input)
{
    return
    ((Input >= 'a') && (Input <= 'f'))
    ? (Input - 87)
    : ((Input >= 'A') && (Input <= 'F'))
    ? (Input - 55)
    : ((Input >= '0') && (Input <= '9'))
    ? (Input - 48)
    : throw std::exception{};
}

/* Position the characters into the appropriate nibble */
constexpr std::uint8_t HexChar(char High, char Low)
{
    return (HexCharToInt(High) << 4) | (HexCharToInt(Low));
}

/* Adapter that performs sets of 2 characters into a single byte and combine the results into a uniform initialization list used to initialize T */
template <typename T, std::size_t Length, std::size_t ... Index>
constexpr T HexString(const char (&Input)[Length], const std::index_sequence<Index...>&)
{
    return T{HexChar(Input[(Index * 2)], Input[((Index * 2) + 1)])...};
}

/* Entry function */
template <typename T, std::size_t Length>
constexpr T HexString(const char (&Input)[Length])
{
    return HexString<T>(Input, std::make_index_sequence<(Length / 2)>{});
}

constexpr auto Y = KS::Utility::HexString<std::array<std::uint8_t, 3>>("ABCDEF");

2
太棒了!我想要一种从字符串文字初始化数组的方法,这几乎就是我需要的。 - Martin Bonner supports Monica

6
你说“可变长度”,那么你指的是多长呢?对于适合放入无符号长整型的十六进制字符串,我经常使用C函数strtoul进行转换。为了让它转换十六进制,将基数值设置为16即可。代码可能如下所示:
#include <cstdlib>
std::string str = "01a1";
unsigned long val = strtoul(str.c_str(), 0, 16);

5
如果你想使用OpenSSL来实现它,我发现一个巧妙的技巧:
BIGNUM *input = BN_new();
int input_length = BN_hex2bn(&input, argv[2]);
input_length = (input_length + 1) / 2; // BN_hex2bn() returns number of hex digits
unsigned char *input_buffer = (unsigned char*)malloc(input_length);
retval = BN_bn2bin(input, input_buffer);

请确保将字符串中的任何前导'0x'去除。


2
请确保使用 BN_free。 - Erik Aronesty

5
这可以通过 stringstream 实现,你只需要将值存储在中间数字类型(如 int)中即可:
  std::string test = "01A1"; // assuming this is an even length string
  char bytes[test.length()/2];
  stringstream converter;
  for(int i = 0; i < test.length(); i+=2)
  {
      converter << std::hex << test.substr(i,2);
      int byte;
      converter >> byte;
      bytes[i/2] = byte & 0xFF;
      converter.str(std::string());
      converter.clear();
  }

3

有人提到可以使用sscanf来实现这个目的,但是没有具体说明如何操作。下面是具体实现方法。使用sscanf的好处在于它适用于古老版本的C和C++甚至大多数微控制器上嵌入式C或C++。

在本例中,将十六进制字符串转换为字节后,得到的ASCII文本是“Hello there!”,然后打印出来。

#include <stdio.h>
int main ()
{
    char hexdata[] = "48656c6c6f20746865726521";
    char bytedata[20]{};
    for(int j = 0; j < sizeof(hexdata) / 2; j++) {
        sscanf(hexdata + j * 2, "%02hhX", bytedata + j);
    }
    printf ("%s -> %s\n", hexdata, bytedata);
    return 0;
}

2
#include <iostream>
#include <sstream>
#include <vector>

int main() {
    std::string s("313233");
    char delim = ',';
    int len = s.size();
    for(int i = 2; i < len; i += 3, ++len) s.insert(i, 1, delim);
    std::istringstream is(s);
    std::ostringstream os;
    is >> std::hex;
    int n;
    while (is >> n) {
        char c = (char)n;
        os << std::string(&c, 1);
        if(is.peek() == delim) is.ignore();
    }

    // std::string form
    std::string byte_string = os.str();
    std::cout << byte_string << std::endl;
    printf("%s\n", byte_string.c_str());

    // std::vector form
    std::vector<char> byte_vector(byte_string.begin(), byte_string.end());
    byte_vector.push_back('\0'); // needed for a c-string
    printf("%s\n", byte_vector.data());
}

输出结果是:
123
123
123

'1' == 0x31, etc.


2

使用gcc 4.7编译的C++11变量(小端格式):

    #include <string>
    #include <vector>

    std::vector<uint8_t> decodeHex(const std::string & source)
    {
        if ( std::string::npos != source.find_first_not_of("0123456789ABCDEFabcdef") )
        {
            // you can throw exception here
            return {};
        }

        union
        {
            uint64_t binary;
            char byte[8];
        } value{};

        auto size = source.size(), offset = (size % 16);
        std::vector<uint8_t> binary{};
        binary.reserve((size + 1) / 2);

        if ( offset )
        {
            value.binary = std::stoull(source.substr(0, offset), nullptr, 16);

            for ( auto index = (offset + 1) / 2; index--; )
            {
                binary.emplace_back(value.byte[index]);
            }
        }

        for ( ; offset < size; offset += 16 )
        {
            value.binary = std::stoull(source.substr(offset, 16), nullptr, 16);
            for ( auto index = 8; index--; )
            {
                binary.emplace_back(value.byte[index]);
            }
        }

        return binary;
    }

Crypto++变种(使用gcc 4.7):

#include <string>
#include <vector>

#include <crypto++/filters.h>
#include <crypto++/hex.h>

std::vector<unsigned char> decodeHex(const std::string & source)
{
    std::string hexCode;
    CryptoPP::StringSource(
              source, true,
              new CryptoPP::HexDecoder(new CryptoPP::StringSink(hexCode)));

    return std::vector<unsigned char>(hexCode.begin(), hexCode.end());
}

请注意,第一种方法比第二种方法快两倍,并且同时适用于奇数和偶数个nibble("a56ac" 的结果为 {0x0a, 0x56, 0xac})。Crypto++ 如果有奇数个nibbel,则会丢弃最后一个("a56ac" 的结果为 {0xa5, 0x6a}),并且会静默跳过无效的十六进制字符("a5sac" 的结果为 {0xa5, 0xac})。

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