Codificação Base64 Em C

Introdução

A codificação base64 é muito importante no transporte de dados binários em um meio de transmissão que trabalha somente com caracteres texto. Um exemplo disso é o padrão MIME que usa essa codificação para transferir arquivos por e-mail usando o protocolo SMTP.

Nesse post iremos aprender mais sobre essa codificação e como fazer tanto um codificador quanto um decodificador usando a linguagem C.

Uso da codificação base64

Essa codificação surgiu da necessidade de transferir e armazenar dados na forma textual, evitando que dados binários sejam manipulados diretamente. Alguns protocolos de transmissão operam somente em cima de dados textuais e quando é preciso algum dado binário ser transmitido, a codificação base64 fornece um jeito simples de transformar essa cadeia binária em texto.

O alfabeto da codificação base64

Para codificar em base64 precisamos primeiramente de um alfabeto. Esse alfabeto contém todos os símbolos que serão usados na codificação. A RFC 3548 [1] define esse alfabeto como:

 Table 1: The Base 64 Alphabet

      Value Encoding  Value Encoding  Value Encoding  Value Encoding
          0 A            17 R            34 i            51 z
          1 B            18 S            35 j            52 0
          2 C            19 T            36 k            53 1
          3 D            20 U            37 l            54 2
          4 E            21 V            38 m            55 3
          5 F            22 W            39 n            56 4
          6 G            23 X            40 o            57 5
          7 H            24 Y            41 p            58 6
          8 I            25 Z            42 q            59 7
          9 J            26 a            43 r            60 8
         10 K            27 b            44 s            61 9
         11 L            28 c            45 t            62 +
         12 M            29 d            46 u            63 /
         13 N            30 e            47 v
         14 O            31 f            48 w         (pad) =
         15 P            32 g            49 x
         16 Q            33 h            50 y

Como podemos ver, o alfabeto contém 65 caracteres compatíveis com quase todo tipo de codificação existente (UTF-8, ISO-8859-1, etc). O normal é dizer 64 caracteres, mas em algumas implementações, o caractere ‘=’ (igual) é usado como padding (preenchimento).

Como codificar em base64

O processo de codificação consiste em ler grupos de três bytes da fonte e gerar quatro bytes com 6 bits ativos. Os 24 bits (3 bytes) lidos são agrupados em sequência e para cada conjunto de 6 bits, obtemos um número que deve ser mapeado no alfabeto para gerar um caractere codificado.

Como exemplo vamos codificar a string “ceu”:

+------------+-------------------+-----------------+
|Caracteres  | Código ASCII(10)  | Código ASCII(2) |
+------------+-------------------+-----------------+
|     c      |        99         |     01100011    |
+------------+-------------------+-----------------+
|     e      |        101        |     01100101    |
+------------+-------------------+-----------------+
|     u      |        117        |     01110101    |
+------------+-------------------+-----------------+

Formando uma única cadeia binária com os 24 bits, temos:

011000110110010101110101

Separando em grupos de 6 bits e já mapeando os números no alfabeto base64:

+-------------+--------------+--------------------+
| 6 bits (2)  | 6 bits (10)  | Elemento na tabela |
+-------------+--------------+--------------------+
|   011000    |      24      |          Y         |
+-------------+--------------+--------------------+
|   110110    |      54      |          2         |
+-------------+--------------+--------------------+
|   010101    |      21      |          V         |
+-------------+--------------+--------------------+
|   110101    |      53      |          1         |
+-------------+--------------+--------------------+

Portanto a string “ceu” é codificada como “Y2V1” em base64.

Operações de baixo nível

Para obtermos os “bytes com 6 bits” da cadeia de 24 bits precisaremos usar as operações de baixo nível da linguagem C, como shift’s e máscaras. Vamos deduzir quais operações devemos usar observando a codificação passo a passo da string “ceu”.

Processo de Codificação

b1, b2 e b3 são respectivamente ‘c’, ‘e’ e ‘u’ em binário.

+----------+-----------+----------+
|   b1     |    b2     |    b3    |
+----------+-----------+----------+
|01100011  | 01100101  | 01110101 |
+----------+-----------+----------+

Iremos ver como encontrar os bytes da codificação: c1, c2, c3 e c4.

1) Primeiro byte codificado

Para gerar o primeiro byte codificado precisamos somente dos 6 bits mais significativos de b1:

01100011 & 11111100

Em C:

c1 = b1 & 0xFC

2) Segundo byte codificado

O segundo byte é a junção dos dois últimos bits de b1 mais os quatros bits mais significativos
de b2:

(01100011 & 00000011) << 4 | (01100101 & 11110000) >> 4

Em C:

c2 = ( b1 & 0x03) << 4 | ( b2 & 0xF0 ) >> 4

Os dois bits de b1 devem ir para a posição 6 e 5 enquanto os 4 bits mais significativos de b2 devem ir para as posições menos significativas do byte codificado.

3) Terceiro byte codificado

Esse byte é formado pelos 4 bits menos significativos de b2 mais os 2 bits mais significativos de b3:

(01100101 & 00001111) << 2 | (01110101 & 11000000) >> 6

Em C:

c3 = ( b2 & 0x0F ) << 2 | ( b3 & 0xC0 ) >> 6

O deslocamento para esquerda é para colocar os bits de b2 nas posições mais significativas. O deslocamento para a direita é para colocar os bits 8 e 7 de b3 nas posições 2 e 1.

4) Quarto byte codificado

Esse byte nada mais é que os seis bits menos significativos de b3:

(01110101 & 00111111)

Em C:

c4 = ( b3 & 0x3F )

Encontrados os 4 bytes desejados, basta agora imprimir o caractere indexado por cada um deles.

Problemas encontrados na codificação

Se o tamanho total da entrada não for múltiplo de 3 então alguns casos especiais surgem. Por exemplo, se a entrada de dados tem tamanho menor que 24 bits, então devemos completar com zeros para que sempre tenhamos 24 bits. Um outro caso diz respeito aos últimos bytes da entrada. Nessa situação temos três casos possíveis:

  1. Se há 3 bytes para serem lidos. Nesse caso, temos os 24 bits completos e por isso não precisamos usar o preenchimento.
  2. Se há 2 bytes para serem lidos. Esses 16 bits irão gerar 3 caracteres codificados e o quarto caractere será o ‘=’.
  3. Se há 1 byte para ser lido. Esses byte é usado para gerar 2 caracteres codificados e depois acrescentamos dois ‘=’s.

O motivo de usarmos o preenchimento é para que a saída seja sempre múltipla de 4. Isso não é obrigatório, mas facilita muito o processo de decodificação de vários arquivos concatenados.

Mais à frente veremos uma versão de decodificação que não precisa de preenchimento.

Código em C para codificar em base64

O código abaixo está simples e foi feito somente para fins didáticos. Ele aceita dados na entrada padrão e gera a codificação base64 correspondente na saída padrão. Instruções sobre compilação e de uso estão no código fonte.

/*
 * [bb64e.c]
 * Codificador simples de base64.
 *
 * [Autor]
 * Marcos Paulo Ferreira (Daemonio)
 * undefinido gmail com
 * daemoniolabs.wordpress.com
 *
 * [Como Compilar]
 * $ gcc -o bb64e bb64e.c
 *
 * [Como Usar]
 * Esse programa so le a entrada padrao,
 * portanto os arquivos devem ser passados por
 * pipes ou por redirecionamento.
 *
 * = Alguns usos =
 * $ ./bb64e < /etc/passwd
 * $ echo -n 'ceu' | ./bb64e
 *
 * Seg Set  5 18:23:47 BRT 2011
 * Sex Set  9 17:22:54 BRT 2011
 */
#include <stdio.h>
#include <stdint.h> /* for uint8_t */

/* Alfabeto. */
const char b64all[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
                      "abcdefghijklmnopqrstuvwxyz"
                      "0123456789"
                      "+/" ;

int main(int argc, char **argv) {
    int n, count ;
    uint8_t b[3], c[4] ;

    /* zera o contador de caracteres
     * da linha. */
    count = 0;

    /* zera o vetor de leitura. */
    b[0]=0 ; b[1]=0 ; b[2]=0 ;

    while( (n=fread(b, 1, 3, stdin)) > 0 ) {
        /* primeiro byte da codificacao. */
        c[0] = b64all[((b[0] & 0xFC) >> 2)] ;
        /* segundo byte da codificacao. */
        c[1] = b64all[(b[0] & 0x03) << 4 | (b[1] & 0xF0) >> 4];
        /* terceiro byte da codificacao. */
        c[2] = b64all[(b[1] & 0x0F) << 2 | (b[2] & 0xC0) >> 6];
        /* quarto byte da codificacao. */
        c[3] = b64all[b[2] & 0x3F] ;

        /* Mostra os dois primeiros bytes. Eles
         * existirao independentemente do valor de n.*/
        putchar(c[0]) ;
        putchar(c[1]) ;

        /* Padding para n < 3. */
        if ( n == 1 ) {
            putchar('=') ;
            putchar('=') ;
        } else if ( n == 2 ) {
            putchar(c[2]) ;
            putchar('=') ;
        } else {
            /* sem padding. */
            putchar(c[2]) ;
            putchar(c[3]) ;
        }

        /* linhas com 76 caracteres no total. */
        if ( count == 18 ) {
            count = 0 ;
            putchar('\n') ;
        } else {
            count++ ;
        }
        /* zerando */
        b[0]=0 ;
        b[1]=0 ;
        b[2]=0 ;
    }
    if ( count ) putchar('\n') ;

    return 0;
}

Baixe o código em:

https://daemoniolabs.wordpress.com/wp-content/uploads/2011/09/bb64e-c.docx

Vamos testar o código:

$ gcc -o bb64e bb64.c
$ cat /etc/passwd | ./bb64e | head
cm9vdDp4OjA6MDpyb290Oi9yb290Oi9iaW4vYmFzaApiaW46eDoxOjE6YmluOi9iaW46L3NiaW4v
bm9sb2dpbgpkYWVtb246eDoyOjI6ZGFlbW9uOi9zYmluOi9zYmluL25vbG9naW4KYWRtOng6Mzo0
OmFkbTovdmFyL2FkbTovc2Jpbi9ub2xvZ2luCmxwOng6NDo3OmxwOi92YXIvc3Bvb2wvbHBkOi9z
YmluL25vbG9naW4Kc3luYzp4OjU6MDpzeW5jOi9zYmluOi9iaW4vc3luYwpzaHV0ZG93bjp4OjY6
MDpzaHV0ZG93bjovc2Jpbjovc2Jpbi9zaHV0ZG93bgpoYWx0Ong6NzowOmhhbHQ6L3NiaW46L3Ni
aW4vaGFsdAptYWlsOng6ODoxMjptYWlsOi92YXIvc3Bvb2wvbWFpbDovc2Jpbi9ub2xvZ2luCnV1
Y3A6eDoxMDoxNDp1dWNwOi92YXIvc3Bvb2wvdXVjcDovc2Jpbi9ub2xvZ2luCm9wZXJhdG9yOng6
MTE6MDpvcGVyYXRvcjovcm9vdDovc2Jpbi9ub2xvZ2luCmdhbWVzOng6MTI6MTAwOmdhbWVzOi91
c3IvZ2FtZXM6L3NiaW4vbm9sb2dpbgpnb3BoZXI6eDoxMzozMDpnb3BoZXI6L3Zhci9nb3BoZXI6
L3NiaW4vbm9sb2dpbgpmdHA6eDoxNDo1MDpGVFAgVXNlcjovdmFyL2Z0cDovc2Jpbi9ub2xvZ2lu

# Vamos usar o decodificador padrão do linux (programa base64) para
# verificarmos se a codificação foi correta:

$ cat /etc/passwd | ./bb64e | base64 -d | head
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin

Decodificação em base64

A decodificação consiste em obter os 3 bytes originais através dos 4 bytes codificados. Esse processo é simples e pode ser feito somente por deslocamentos e operações OR. Se o caractere de preenchimento não foi usado, então temos que obter os bytes originais usando menos de 4 bytes codificados. Será esse processo que veremos aqui.

+--------+---------+---------+--------+
|  c1    |   c2    |   c3    |   c4   |
+--------+---------+---------+--------+
|011000  | 110110  | 010101  | 110101 |
+--------+---------+---------+--------+

Veremos como obter os bytes originais: b1, b2 e b3.

1) Primeiro byte original

O b1 é formado pelos 6 bits de c1 mais os 2 bits mais significativos de c2. Deslocando c2 para à esquerda duas vezes, c2 4 vezes para à direita e fazendo um OR entre os dois resultados, encontramos b1.

Em C:

b1 = (c1 << 2) | (c2 >> 4)

2) Segundo byte original

b2 é formado pelos 4 bits menos significativos de c2 mais os 4 mais significativos de c3. Se deslocarmos c2 4 vezes para à esquerda, c3 2 vezes para à direita e fazermos um OR entre os dois resultados, obtemos b2.

Em C:

b2 = (c2 << 4) | (c3 >> 2)

3) Terceiro byte original

O último byte é obtido com os 2 bits menos significativos de c3 mais o c4, totalizando os 8 bits.

Em C:

b3 = (c3 << 6) | c4

Código em C para decodificar em base64

Eu poderia escrever um código aqui, mas acabei achando um ótimo na referência[2]. O código está simples e realiza tudo o que foi explicado anteriormente.

/* Copyright (c) 2011 the authors listed at the following URL, and/or
the authors of referenced articles or incorporated external code:
http://en.literateprograms.org/Base64_(C)?action=history&offset=20070707014726

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Retrieved from: http://en.literateprograms.org/Base64_(C)?oldid=10650
*/

#include <stdio.h>

#include <string.h>

char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

int nbytes[] = { 3, 1, 1, 2 };

void xlate(unsigned char *in, int phase)
{
    unsigned char out[3];
    out[0] = in[0] << 2 | in[1] >> 4;
    out[1] = in[1] << 4 | in[2] >> 2;
    out[2] = in[2] << 6 | in[3] >> 0;
    fwrite(out, nbytes[phase], 1, stdout);

}

int main()
{
    int c, phase;
    unsigned char in[4];
    char *p;

    phase = 0;
    while((c = getchar()) != EOF)    {
        if(c == '=')    {
	    xlate(in,phase);
	    break;
	}
	p = strchr(b64, c);
	if(p)    {
	    in[phase] = p - b64;
	    phase = (phase + 1) % 4;
	    if(phase == 0)    {
	        xlate(in,phase);
	        in[0]=in[1]=in[2]=in[3]=0;
	    }
	}
    }
    return 0;
}

Baixe o código em:

http://en.literateprograms.org/Special:Downloadcode/Base64_%28C%29

Testando (salve o código como bb64d.c):

$ gcc -o bb64d base64.c

# Mais uma vez utilizando o base64 nativo do linux:

$ base64 /etc/passwd | ./bb64d | head
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin

Conclusão

O algoritmo base64 é ideal para transformar dados binários em texto. Seu uso é bastante explorado nas trocas de arquivos, especialmente entre e-mails.

Referências

[1] The Base16, Base32, and Base64 Data Encodings, RFC 3548 (Acessado em: Setembro/2011)
http://tools.ietf.org/html/rfc3548

[2]Base64 (Acessado em: Setembro/2011)
http://en.literateprograms.org/Base64_%28C%29

[3] Base64 (Acessado em: Setembro/2011)
http://en.wikipedia.org/wiki/Base64

9 pensamentos sobre “Codificação Base64 Em C

  1. Só lembrando que em muitas distribuições linux há o comando base64 que codifica e decodifica em base 64:

    Codificar a string ceu:
    $ echo -n ‘ceu’ | base64

    Decodificar a string ceu:
    $ echo -n ‘ceu’ | base64 | base64 -d

    Para mais detalhes:
    $ main base64

    Daemonio.
    t+

  2. Pingback: Conversão de base numérica | Daemonio Labs

  3. alquem consegue desvendar este codigo: TE9BRLORyBSTOJPVFNUWFQgSU5GT1JNQVRJT04gR.
    Espero que consigam desvendá-lo, pois eu preciso mesmo dele.
    Obrigado pela atenção

  4. Cara, sei que faz muito tempo que você postou essa matéria de codificação com base 64 mas meus parabéns !
    Ficou incrível o texto e com isso agora eu consigo pensar em algo para meu projeto!
    Não vou copiar o seu código pois vai contra meus principios, mas com certeza me deu uma base enorme para o que eu quero fazer!
    Sério mesmo… Obrigado amigo!

  5. usando como exemplo a palavra “ceu”, eu cheguei 011000110110010101110101 = Y2V1

    Mas para pegar o original eu não tenho que converter o Y2V1(01011001001100100101011000110001) para binario novamente? e seguir a regra 6 do primeiro e 2 do segundo etc?

    Seria assim? 010110 – 010011 – 001001 – 010110 – 001100 – 01

    ex.: 010110(c1) + 11(c2) => 01011011

    Não estou conseguindo chegar no resultado se puder dar uma dica.

    obrigado

    • É isso mesmo, mas na decodificação tem sobreposição de bits. Você pega 6 do primeiro e monta o primeiro byte. Depois pega 2 ainda do primeiro mais 4 do segundo, e por ai vai. Dado a string inicial não pode faltar bits.

      Outro detalhe é que pega os bits mais significativos (os que estão mais à esquerda). No seu exemplo ficaria:

      1) Dado Y2V1

      Jogue o “Y” na tabela e obtenha: 24, jogue o “2” e obtenha o 54, etc. A sequência correspondente será: 24 54 21 53

      Esses números em binários (concatenados) retornam a mesma string oriunda da codificação de “ceu”, que é: 011000110110010101110101

      2) Separando: 011000-110110-010101-110101

      b1 = 4bits(c1) + 2bits(c2) = 01100011 = 99 (dec) = c (ascii)

      você joga o 99 na tabela e obtém o c. Veja que os 2bits(c2) são os mais à esquerda (11) e não os mais à direita (10)

      Os outros bytes são similares, porém sempre lembrar da sobreposição de bits para obter o resultado correto.

      edit: troquei direita/esquerda

      Abraços

Deixe um comentário