web-dev-qa-db-fra.com

Chaîne hexadécimale à tableau d'octets en C

Existe-t-il une fonction C standard qui convertit de chaîne hexadécimale en octet tableau ?
Je ne veux pas écrire ma propre fonction.

49
Szere Dyeri

Autant que je sache, il n'y a pas de fonction standard pour le faire, mais c'est simple à réaliser de la manière suivante:

#include <stdio.h>

int main(int argc, char **argv) {
    const char hexstring[] = "DEadbeef10203040b00b1e50", *pos = hexstring;
    unsigned char val[12];

     /* WARNING: no sanitization or error-checking whatsoever */
    for (size_t count = 0; count < sizeof val/sizeof *val; count++) {
        sscanf(pos, "%2hhx", &val[count]);
        pos += 2;
    }

    printf("0x");
    for(size_t count = 0; count < sizeof val/sizeof *val; count++)
        printf("%02x", val[count]);
    printf("\n");

    return 0;
}

Modifier

Comme Al l'a souligné, dans le cas d'un nombre impair de chiffres hexadécimaux dans la chaîne, vous devez vous assurer de la préfixer avec un 0 de départ. Par exemple, la chaîne "f00f5" sera évaluée par {0xf0, 0x0f, 0x05} à tort par l'exemple ci-dessus, le {0x0f, 0x00, 0xf5} approprié.

A modifié l'exemple un peu pour répondre au commentaire de @MassimoCallegari

57
Michael Foukarakis

J'ai trouvé cette question par Google pour la même chose. Je n'aime pas l'idée d'appeler sscanf () ou strtol () car cela donne l'impression d'être excessif. J'ai écrit une fonction rapide qui ne confirme pas que le texte est bien la présentation hexadécimale d'un flux d'octets, mais gérera un nombre impair de chiffres hexadécimaux:

uint8_t tallymarker_hextobin(const char * str, uint8_t * bytes, size_t blen)
{
   uint8_t  pos;
   uint8_t  idx0;
   uint8_t  idx1;

   // mapping of ASCII characters to hex values
   const uint8_t hashmap[] =
   {
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //  !"#$%&'
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ()*+,-./
     0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // 01234567
     0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 89:;<=>?
     0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, // @ABCDEFG
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // HIJKLMNO
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // PQRSTUVW
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // XYZ[\]^_
     0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, // `abcdefg
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // hijklmno
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pqrstuvw
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // xyz{|}~.
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00  // ........
   };

   bzero(bytes, blen);
   for (pos = 0; ((pos < (blen*2)) && (pos < strlen(str))); pos += 2)
   {
      idx0 = (uint8_t)str[pos+0];
      idx1 = (uint8_t)str[pos+1];
      bytes[pos/2] = (uint8_t)(hashmap[idx0] << 4) | hashmap[idx1];
   };

   return(0);
}
10
David M. Syzdek

Pour les chaînes courtes, strtol, strtoll et strtoimax fonctionneront parfaitement (notez que le troisième argument est la base à utiliser pour le traitement de la chaîne ... définissez-le sur 16). Si votre saisie est plus longue que number-of-bits-in-the-longest-integer-type/4, vous aurez besoin de l’une des méthodes les plus souples suggérées par d’autres réponses.

7
dmckee

Outre les excellentes réponses ci-dessus, j’ai pensé écrire une fonction en C qui n’utilise aucune bibliothèque et qui protège contre les mauvaises chaînes.

uint8_t* datahex(char* string) {

    if(string == NULL) 
       return NULL;

    size_t slength = strlen(string);
    if((slength % 2) != 0) // must be even
       return NULL;

    size_t dlength = slength / 2;

    uint8_t* data = malloc(dlength);
    memset(data, 0, dlength);

    size_t index = 0;
    while (index < slength) {
        char c = string[index];
        int value = 0;
        if(c >= '0' && c <= '9')
          value = (c - '0');
        else if (c >= 'A' && c <= 'F') 
          value = (10 + (c - 'A'));
        else if (c >= 'a' && c <= 'f')
          value = (10 + (c - 'a'));
        else {
          free(data);
          return NULL;
        }

        data[(index/2)] += value << (((index + 1) % 2) * 4);

        index++;
    }

    return data;
}

Explication: 

une. index/2 | La division entre les nombres entiers arrondira la valeur, donc 0/2 = 0, 1/2 = 0, 2/2 = 1, 3/2 = 0, etc. Ainsi, pour chaque chaîne de 2 caractères, nous ajoutons la valeur à 1 octet de données. .

b. (index + 1)% 2 | Nous voulons que les nombres impairs donnent 1 et même 0, car le premier chiffre d'une chaîne hexagonale est le plus significatif et doit être multiplié par 16. Ainsi, pour l'index 0 => 0 + 1% 2 = 1, l'index 1 => 1 + 1% 2 = 0 etc.

c. << 4 | Le décalage de 4 est multiplié par 16. Exemple: b00000001 << 4 = b00010000

5
Mike M

Une version complète du post de Michael Foukarakis (puisque je n'ai pas encore la "réputation" pour ajouter un commentaire à ce post):

#include <stdio.h>
#include <string.h>

void print(unsigned char *byte_array, int byte_array_size)
{
    int i = 0;
    printf("0x");
    for(; i < byte_array_size; i++)
    {
        printf("%02x", byte_array[i]);
    }
    printf("\n");
}

int convert(const char *hex_str, unsigned char *byte_array, int byte_array_max)
{
    int hex_str_len = strlen(hex_str);
    int i = 0, j = 0;

    // The output array size is half the hex_str length (rounded up)
    int byte_array_size = (hex_str_len+1)/2;

    if (byte_array_size > byte_array_max)
    {
        // Too big for the output array
        return -1;
    }

    if (hex_str_len % 2 == 1)
    {
        // hex_str is an odd length, so assume an implicit "0" prefix
        if (sscanf(&(hex_str[0]), "%1hhx", &(byte_array[0])) != 1)
        {
            return -1;
        }

        i = j = 1;
    }

    for (; i < hex_str_len; i+=2, j++)
    {
        if (sscanf(&(hex_str[i]), "%2hhx", &(byte_array[j])) != 1)
        {
            return -1;
        }
    }

    return byte_array_size;
}

void main()
{
    char *examples[] = { "", "5", "D", "5D", "5Df", "deadbeef10203040b00b1e50", "02invalid55" };
    unsigned char byte_array[128];
    int i = 0;

    for (; i < sizeof(examples)/sizeof(char *); i++)
    {
        int size = convert(examples[i], byte_array, 128);
        if (size < 0)
        {
            printf("Failed to convert '%s'\n", examples[i]);
        }
        else if (size == 0)
        {
            printf("Nothing to convert for '%s'\n", examples[i]);
        }
        else
        {
            print(byte_array, size);
        }
    }
}
3
SMH

Après quelques modifications dans le code de user411313, voici ce qui fonctionne pour moi:

#include <stdio.h>
#include <stdint.h> 
#include <string.h>

int main ()
{
    char *hexstring = "deadbeef10203040b00b1e50";
    int i;
    unsigned int bytearray[12];
    uint8_t str_len = strlen(hexstring);

    for (i = 0; i < (str_len / 2); i++) {
        sscanf(hexstring + 2*i, "%02x", &bytearray[i]);
        printf("bytearray %d: %02x\n", i, bytearray[i]);
    }

    return 0;
}
3
mgalela

hextools.h

#ifndef HEX_TOOLS_H
#define HEX_TOOLS_H

char *bin2hex(unsigned char*, int);

unsigned char *hex2bin(const char*);

#endif // HEX_TOOLS_H

hextools.c

#include <stdlib.h>

char *bin2hex(unsigned char *p, int len)
{
    char *hex = malloc(((2*len) + 1));
    char *r = hex;

    while(len && p)
    {
        (*r) = ((*p) & 0xF0) >> 4;
        (*r) = ((*r) <= 9 ? '0' + (*r) : 'A' - 10 + (*r));
        r++;
        (*r) = ((*p) & 0x0F);
        (*r) = ((*r) <= 9 ? '0' + (*r) : 'A' - 10 + (*r));
        r++;
        p++;
        len--;
    }
    *r = '\0';

    return hex;
}

unsigned char *hex2bin(const char *str)
{
    int len, h;
    unsigned char *result, *err, *p, c;

    err = malloc(1);
    *err = 0;

    if (!str)
        return err;

    if (!*str)
        return err;

    len = 0;
    p = (unsigned char*) str;
    while (*p++)
        len++;

    result = malloc((len/2)+1);
    h = !(len%2) * 4;
    p = result;
    *p = 0;

    c = *str;
    while(c)
    {
        if(('0' <= c) && (c <= '9'))
            *p += (c - '0') << h;
        else if(('A' <= c) && (c <= 'F'))
            *p += (c - 'A' + 10) << h;
        else if(('a' <= c) && (c <= 'f'))
            *p += (c - 'a' + 10) << h;
        else
            return err;

        str++;
        c = *str;

        if (h)
            h = 0;
        else
        {
            h = 4;
            p++;
            *p = 0;
        }
    }

    return result;
}

principal c

#include <stdio.h>
#include "hextools.h"

int main(void)
{
    unsigned char s[] = { 0xa0, 0xf9, 0xc3, 0xde, 0x44 };

    char *hex = bin2hex(s, sizeof s);
    puts(hex);

    unsigned char *bin;
    bin = hex2bin(hex);

    puts(bin2hex(bin, 5));

    size_t k;
    for(k=0; k<5; k++)
        printf("%02X", bin[k]);

    putchar('\n');

    return 0;
}
2
hutheano

Voici la solution que j'ai écrite pour des raisons de performances:

void hex2bin(const char* in, size_t len, unsigned char* out) {

  static const unsigned char TBL[] = {
     0,   1,   2,   3,   4,   5,   6,   7,   8,   9,  58,  59,  60,  61,
    62,  63,  64,  10,  11,  12,  13,  14,  15,  71,  72,  73,  74,  75,
    76,  77,  78,  79,  80,  81,  82,  83,  84,  85,  86,  87,  88,  89,
    90,  91,  92,  93,  94,  95,  96,  10,  11,  12,  13,  14,  15
  };

  static const unsigned char *LOOKUP = TBL - 48;

  const char* end = in + len;

  while(in < end) *(out++) = LOOKUP[*(in++)] << 4 | LOOKUP[*(in++)];

}

Exemple:

unsigned char seckey[32];

hex2bin("351aaaec0070d13d350afae2bc43b68c7e590268889869dde489f2f7988f3fee", 64, seckey);

/*
  seckey = {
     53,  26, 170, 236,   0, 112, 209,  61,  53,  10, 250, 226, 188,  67, 182, 140, 
    126,  89,   2, 104, 136, 152, 105, 221, 228, 137, 242, 247, 152, 143,  63, 238
  };
*/

Si vous n'avez pas besoin de prendre en charge les minuscules:

static const unsigned char TBL[] = {
     0,   1,   2,   3,   4,   5,   6,   7,   8,   9,  58,  59,
    60,  61,  62,  63,  64,  10,  11,  12,  13,  14,  15
};
1
flcoder

Non, mais il est relativement facile d’atteindre sscanf dans une boucle.

1
char *hexstring = "deadbeef10203040b00b1e50", *pos = hexstring;
unsigned char val[12];
while( *pos )
{
  if( !((pos-hexstring)&1) )
    sscanf(pos,"%02x",&val[(pos-hexstring)>>1]);
  ++pos;
}

sizeof (val)/sizeof (val [0]) est redondant!

1
user411313
    In main()
    {
printf("enter string :\n");
    fgets(buf, 200, stdin);
unsigned char str_len = strlen(buf);
k=0;
unsigned char bytearray[100];
     for(j=0;j<str_len-1;j=j+2)
        { bytearray[k++]=converttohex(&buffer[j]);   
                printf(" %02X",bytearray[k-1]);
        }

    }

    Use this 

    int converttohex(char * val)
        {
        unsigned char temp = toupper(*val);
        unsigned char fin=0;
        if(temp>64)
        temp=10+(temp-65);

        else
        temp=temp-48;

        fin=(temp<<4)&0xf0;

        temp = toupper(*(val+1));

            if(temp>64)
            temp=10+(temp-65);

            else
            temp=temp-48;

        fin=fin|(temp & 0x0f);


           return fin;
        }
1
Pradeep jayaram

Voici que HexToBin et BinToHex sont relativement propres et lisibles .

typedef unsigned char ByteData;
ByteData HexChar (char c)
{
    if ('0' <= c && c <= '9') return (ByteData)(c - '0');
    if ('A' <= c && c <= 'F') return (ByteData)(c - 'A' + 10);
    if ('a' <= c && c <= 'f') return (ByteData)(c - 'a' + 10);
    return (ByteData)(-1);
}

ssize_t HexToBin (const char* s, ByteData * buff, ssize_t length)
{
    ssize_t result = 0;
    if (!s || !buff || length <= 0) return -2;

    while (*s)
    {
        ByteData nib1 = HexChar(*s++);
        if ((signed)nib1 < 0) return -3;
        ByteData nib2 = HexChar(*s++);
        if ((signed)nib2 < 0) return -4;

        ByteData bin = (nib1 << 4) + nib2;

        if (length-- <= 0) return -5;
        *buff++ = bin;
        ++result;
    }
    return result;
}

void BinToHex (const ByteData * buff, ssize_t length, char * output, ssize_t outLength)
{
    char binHex[] = "0123456789ABCDEF";

    if (!output || outLength < 4) return (void)(-6);
    *output = '\0';

    if (!buff || length <= 0 || outLength <= 2 * length)
    {
        memcpy(output, "ERR", 4);
        return (void)(-7);
    }

    for (; length > 0; --length, outLength -= 2)
    {
        ByteData byte = *buff++;

        *output++ = binHex[(byte >> 4) & 0x0F];
        *output++ = binHex[byte & 0x0F];
    }
    if (outLength-- <= 0) return (void)(-8);
    *output++ = '\0';
}
1
Henri Socha

Ceci est une fonction modifiée à partir d'une question similaire, modifiée selon la suggestion de https://stackoverflow.com/a/18267932/700597 .

Cette fonction convertira une chaîne hexadécimale - PAS précédée de "0x" - avec un nombre pair de caractères pour le nombre d'octets spécifié. Il renverra -1 s'il rencontre un caractère non valide ou si la chaîne hexagonale a une longueur impaire, et 0 en cas de succès.

//convert hexstring to len bytes of data
//returns 0 on success, -1 on error
//data is a buffer of at least len bytes
//hexstring is upper or lower case hexadecimal, NOT prepended with "0x"
int hex2data(unsigned char *data, const unsigned char *hexstring, unsigned int len)
{
    unsigned const char *pos = hexstring;
    char *endptr;
    size_t count = 0;

    if ((hexstring[0] == '\0') || (strlen(hexstring) % 2)) {
        //hexstring contains no data
        //or hexstring has an odd length
        return -1;
    }

    for(count = 0; count < len; count++) {
        char buf[5] = {'0', 'x', pos[0], pos[1], 0};
        data[count] = strtol(buf, &endptr, 0);
        pos += 2 * sizeof(char);

        if (endptr[0] != '\0') {
            //non-hexadecimal character encountered
            return -1;
        }
    }

    return 0;
}
1
runeks

Voici ma version:

/* Convert a hex char digit to its integer value. */
int hexDigitToInt(char digit) {
    digit = tolower(digit);
    if ('0' <= digit && digit <= '9') //if it's decimal
        return (int)(digit - '0');
    else if ('a' <= digit && digit <= 'f') //if it's abcdef
        return (int)(digit - ('a' - 10));
    else
        return -1; //value not in [0-9][a-f] range
}

/* Decode a hex string. */
char *decodeHexString(const char *hexStr) {
    char* decoded = malloc(strlen(hexStr)/2+1);
    char* hexStrPtr = (char *)hexStr;
    char* decodedPtr = decoded;

    while (*hexStrPtr != '\0') { /* Step through hexStr, two chars at a time. */
        *decodedPtr = 16 * hexDigitToInt(*hexStrPtr) + hexDigitToInt(*(hexStrPtr+1));
        hexStrPtr += 2;
        decodedPtr++;
    }

    *decodedPtr = '\0'; /* final null char */
    return decoded;
}
0
Geremia

La meilleure façon que je connaisse:

int hex2bin_by_zibri(char *source_str, char *dest_buffer)
{
  char *line = source_str;
  char *data = line;
  int offset;
  int read_byte;
  int data_len = 0;

  while (sscanf(data, " %02x%n", &read_byte, &offset) == 1) {
    dest_buffer[data_len++] = read_byte;
    data += offset;
  }
  return data_len;
}

La fonction renvoie le nombre d'octets convertis enregistrés dans dest_buffer. La chaîne d'entrée peut contenir des espaces et des lettres en majuscules.

"01 02 03 04 ab Cd eF ordures AB"

traduit en dest_buffer contenant 01 02 03 04 ab cd ef

et aussi "01020304abCdeFgarbageAB"

traduit comme avant.

L'analyse s'arrête à la première "erreur".

0
Zibri

Pourrait-il être plus simple?!

uint8_t hex(char ch) {
    uint8_t r = (ch > 57) ? (ch - 55) : (ch - 48);
    return r & 0x0F;
}

int to_byte_array(const char *in, size_t in_size, uint8_t *out) {
    int count = 0;
    if (in_size % 2) {
        while (*in && out) {
            *out = hex(*in++);
            if (!*in)
                return count;
            *out = (*out << 4) | hex(*in++);
            *out++;
            count++;
        }
        return count;
    } else {
        while (*in && out) {
            *out++ = (hex(*in++) << 4) | hex(*in++);
            count++;
        }
        return count;
    }
}

int main() {
    char hex_in[] = "deadbeef10203040b00b1e50";
    uint8_t out[32];
    int res = to_byte_array(hex_in, sizeof(hex_in) - 1, out);

    for (size_t i = 0; i < res; i++)
        printf("%02x ", out[i]);

    printf("\n");
    system("pause");
    return 0;
}
0
IMAN4K

Essayez le code suivant:

static unsigned char ascii2byte(char *val)
{
    unsigned char temp = *val;

    if(temp > 0x60) temp -= 39;  // convert chars a-f
    temp -= 48;  // convert chars 0-9
    temp *= 16;

    temp += *(val+1);
    if(*(val+1) > 0x60) temp -= 39;  // convert chars a-f
    temp -= 48;  // convert chars 0-9   

    return temp;

}
0
Vladimir