2 pontos por GN⁺ 2023-10-24 | 1 comentários | Compartilhar no WhatsApp
  • A codificação Base64 converte dados binários em texto ASCII, reduzindo a chance de os dados serem interpretados incorretamente durante o armazenamento ou a transmissão
  • Como não é criptografia, mas sim uma mudança na forma de representação, os dados codificados podem ser facilmente revertidos para o texto ou arquivo original
  • Como 64 caracteres podem ser representados com 6 bits, cada caractere Base64 contém 6 bits de dados, e 3 bytes (24 bits) se transformam em quatro caracteres Base64
  • É útil em ambientes como Data URLs em HTML, transmissão de binários por e-mail e redes ou URLs centradas em texto, onde binário bruto pode causar problemas
  • Várias linguagens e ferramentas, como Ruby, C#, PHP, JavaScript e o comando base64 no terminal, oferecem recursos de codificação e decodificação

O que o Base64 transforma

  • A codificação Base64 converte dados binários em texto, mais especificamente em texto ASCII
  • O resultado usa apenas os 64 caracteres abaixo
    • A-Z
    • a-z
    • 0-9
    • +
    • /
  • Esse conjunto é usado como um conjunto seguro de caracteres para evitar situações em que caracteres como <, > e \n possam ser interpretados incorretamente por computadores ou programas antigos
  • Se "Ruby on Rails" for codificado em Base64, o resultado será UnVieSBvbiBSYWlscw==
  • Base64 não é criptografia
    • Os dados codificados podem ser facilmente revertidos ao texto original
    • Ele não oculta os dados, apenas muda sua representação

Quando usar Base64

  • Data URLs permitem inserir diretamente em HTML dados de arquivos, como imagens, e nesse caso usam texto codificado em Base64
  • O formato de exemplo é data:[<mime type>][;charset=<charset>][;base64],<encoded data>
  • Em e-mails, o Base64 tem sido usado para transportar dados binários com segurança, mesmo em ambientes onde o servidor pode alterar quebras de linha
  • Ao inserir dados de imagem diretamente no código HTML, a codificação é necessária para que caracteres como < e > não sejam interpretados como tags
  • Também pode ser usado ao armazenar ou transmitir dados binários por redes projetadas para processar texto ou dados US-ASCII
  • O Base64 também pode ser usado para transmitir dados que contêm caracteres difíceis de colocar em URLs
  • Codificações da família Base permitem tratar objetos com editores de texto, o que leva ao seu uso em várias aplicações

Algoritmo de codificação

  • A codificação Base64 segue a sequência abaixo
    • Converte o texto em uma representação binária
    • Divide os bits em grupos de 6
    • Converte cada grupo de 6 bits em um número decimal de 0 a 63
    • Troca esse número pelo caractere correspondente no alfabeto Base64
  • Se faltarem bits no grupo final, = ou == podem ser adicionados como padding
  • São necessários 6 bits para representar 64 caracteres
    • 2^6 = 64
    • Um dígito Base64 representa 6 bits de dados
  • Um byte tem 8 bits, e o múltiplo comum mais próximo entre 8 e 6 é 24
    • 24 bits equivalem a 3 bytes
    • 24 bits são representados por quatro dígitos Base64 de 6 bits

Exemplo de codificação de “Akshay”

  • Se "Akshay" for convertido para números ASCII e depois para binário, o resultado será o seguinte
    • 01000001 01101011 01110011 01101000 01100001 01111001
  • Dividindo isso em grupos de 6 bits, temos
    • 010000 010110 101101 110011 011010 000110 000101 111001
  • Convertendo cada grupo para decimal, obtemos os valores abaixo
    • 16 22 45 51 26 6 5 57
  • Convertendo para o alfabeto Base64, obtemos os caracteres abaixo
    • Q W t z a G F 5
  • Portanto, a representação em Base64 de "Akshay" é QWtzaGF5
  • Da mesma forma, arquivos como imagens, PDFs, textos e vídeos também podem ser convertidos em binário e depois codificados em Base64 para serem armazenados ou transmitidos como texto ASCII

Uso em linguagens e ferramentas

  • Ruby lida com codificação e decodificação por meio do módulo Base64
    • Base64.encode64("Ruby on Rails")
    • Base64.decode64(encoded)
  • Em C#, a string é convertida em um array de bytes e depois codificada com Convert.ToBase64String, enquanto a decodificação é feita com System.Convert.FromBase64String
  • PHP oferece as funções de nível superior base64_encode e base64_decode
  • Em JavaScript, a codificação é feita com btoa() e a decodificação com atob()
  • No terminal, também é possível codificar e decodificar com o comando base64
    • echo "akshay" | base64 gera YWtzaGF5Cg==
    • echo "YWtzaGF5Cg==" | base64 -d gera akshay

1 comentários

 
GN⁺ 2023-10-24
Opiniões no Hacker News
  • Obrigado por enfatizar que aqui não se trata de criptografar texto. Muitos desenvolvedores juniores acabam aprendendo tarde demais a diferença entre criptografia, que exige um valor secreto para ser revertida; hashing, que não pode ser revertido; e codificação, que sempre pode ser revertida facilmente
    Também vale saber que, mesmo que a saída pareça aleatória, a entropia é a mesma da entrada. Ou seja, não se deve codificar uma senha em Base64 para tentar torná-la mais forte

    • Isso é quase uma implicância e não tem muita relação com o ponto principal, mas codificar uma senha em Base64 pode torná-la mais forte. A força de uma senha não é apenas uma questão de entropia, embora alta entropia seja o método mais eficaz
      Se a senha foi gerada de forma completamente aleatória, a codificação em Base64 não tem efeito nenhum. Mas, se a senha foi criada com um esquema de baixa entropia, como palavras de dicionário ou regras fáceis de memorizar, o atacante precisará configurar um cracker de senhas inteligente para também considerar a regra de codificação Base64, o que acrescenta algo como uma operação a mais por tentativa
      Claro, não se deve usar esse tipo de esquema de senha. Senhas no estilo “correct horse battery staple” me parecem suficientes
    • Acho que quem estudou Ciência da Computação conhece essas diferenças. Mesmo alguém interessado em programação consegue aprender esses conceitos em uma tarde
    • Um ponto relacionado que sempre vale enfatizar é que um hash não é necessariamente criptograficamente seguro
      Hashing tem muitos objetivos além de segurança, e por isso também há várias bibliotecas de hash. Se você vai usar hash para fins de segurança ou criptografia, precisa usar um hash projetado para esse propósito. Hashes CRC são rápidos, mas não são uma boa opção para senhas de usuários
  • Uma coisa interessante sobre Base64 é que, se você começa com qualquer string e a codifica repetidamente, o começo do resultado converge cada vez mais para um ponto fixo. Dá para verificar até com Bash
    Descobri isso por acaso há mais de 10 anos e tuitei como se fosse uma cifra [1]; alguém escreveu um post de blog sobre o tema e também o publicou aqui, mas quase não houve discussão [2]. Quando outra pessoa postou no Reddit /r/compsci, houve uma discussão produtiva por lá corrigindo o post do blog [3]. O blog já saiu do ar, mas há uma cópia no Internet Archive [4]
    [1] https://twitter.com/p4bl0/status/298900842076045312
    [2] https://news.ycombinator.com/item?id=5181256
    [3] https://www.reddit.com/r/compsci/comments/18234a/the_base64_...
    [4] https://web.archive.org/web/20130315082932/http://fmota.eu/b...

  • Ao codificar com Bash, é preciso usar a opção -n: $ echo -n "abcde" |base64
    Sem -n, o echo adiciona um caractere de nova linha ao fim da string, e esse caractere também é codificado

  • Também existe base64URL, que codifica usando outros caracteres ASCII seguros para URLs. Alguns desenvolvedores chamam BASE64URL simplesmente de base64, o que pode causar problemas para quem não sabe disso
    https://datatracker.ietf.org/doc/html/rfc4648#section-5

    • O problema do base64url é que ~ e . não são letras, então, ao dar dois cliques em um valor codificado, ele não é selecionado por inteiro. Isso cria um atrito desnecessário em muitos casos de copiar e colar
      A codificação Base62 (0-9A-Za-z) é quase tão eficiente quanto base64url e mantém a segurança para URLs, além de ser mais fácil de copiar e colar. Se você quiser reduzir ambiguidades para leitura humana, pode descer para Base58, mas, em geral, quando se está usando uma codificação BaseXX, o texto já é longo o bastante para que copiar e colar seja o normal, então isso não é um grande problema
      https://en.wikipedia.org/wiki/Base62
    • Base64url normalmente também omite o padding
      Como uma string Base64 com padding sempre tem comprimento múltiplo de 4, quando se recebe uma string cujo comprimento não é múltiplo de 4 dá para saber quanto padding deveria haver originalmente e também determinar como decodificar os últimos 3 bytes
      Por isso, fico um pouco confuso sobre por que o padding == era necessário em Base64 desde o início
  • Sempre que surge o assunto de conversão de bases, faço uma divulgação descarada do meu conversor de bases arbitrárias: https://convert.zamicol.com
    O base64 em “useful alphabets” é a base “natural”, que faz divisões repetidas pela base, e o método de conversão por “baldes” da RFC fica em extras

  • Se você codificou algo e uma pessoa vai ter que digitar manualmente, recomendo https://en.wikipedia.org/wiki/Base32
    Poucas coisas são tão irritantes quanto ficar em dúvida, por causa de uma fonte ruim, se é l ou 1, ou o, O ou 0

  • Para ser um pouco mais rigoroso, é mais correto dizer que Base64 codifica dados binários em um subconjunto de ASCII, não no conjunto completo de caracteres ASCII
    ASCII tem 128 pontos de código; desses, 95 são caracteres imprimíveis e 33 são caracteres de controle, mas Base64 usa apenas 64 deles, ou 65 se incluir o padding

  • O texto não explicou em detalhes o objetivo do padding = / ==, nem mostrou com exemplos como tratar dados que não se dividem exatamente em grupos de 6 bits
    Acho que entendi por alto, mas queria saber com certeza. Seria bom se alguém pudesse responder de forma curta e completa quando se usa =, quando se usa ==, se eles são sempre adicionados ou se há casos em que não são, como exatamente são tratados os bits restantes de uma string como "5byte" e se há algo a considerar na decodificação

    • As duas perguntas estão conectadas
      Um caractere Base64 representa 6 bits, então um bloco de 3 bytes de dados corresponde a um bloco de 4 caracteres codificados em Base64. Por isso é conveniente processar dados Base64 de 4 em 4 caracteres
      = é o padding que, conforme necessário, adiciona 0, 1 ou 2 caracteres para ajustar o comprimento da string codificada a um múltiplo de 4. Por exemplo, "543210" vira "543210==", "6543210" vira "6543210=", e "76543210" não precisa de padding. Nunca há caso em que sejam necessários 3 = de padding, porque até 1 byte de dados exige no mínimo 2 caracteres Base64
      Os bits restantes podem ser preenchidos com 0, e o decodificador consegue descartá-los ao perceber que não há bits suficientes para formar 1 byte completo. Na maioria dos casos modernos, o padding é mais uma convenção do que algo estritamente necessário. O artigo da Wikipedia é bem detalhado: https://en.wikipedia.org/wiki/Base64
    • O padding só é necessário ao concatenar dados codificados ou ao fazer streaming, isto é, quando caracteres de padding aparecem no meio de um fluxo codificado
      Caracteres de padding no fim de um stream, arquivo ou string podem ser inferidos pelo comprimento já processado, então, a rigor, não são obrigatórios
      Dito isso, a forma de lidar com padding é bastante sutil, e essas diferenças deram origem a variações de implementação interessantes: https://eprint.iacr.org/2022/361.pdf
    • Segundo o texto, um dígito Base64 representa 6 bits de dados. Um byte tem 8 bits, e o múltiplo comum mais próximo de 8 e 6 é 24; portanto, 24 bits, ou seja, 3 bytes, podem ser expressos como 4 dígitos Base64 de 6 bits
      No fim das contas, é uma codificação em unidades de 24 bits. Quando os dados terminam, a parte restante dos 24 bits é preenchida com =, não com A, porque A, como dado, significa 000000. Eu também li tudo duas vezes para entender
  • Meu shader codificador Base64 está aqui: https://github.com/Rezmason/excel_97_egg/blob/main/glsl/base...
    Reduzi para cerca de 13 linhas de GLSL: https://github.com/Rezmason/excel_97_egg/blob/main/glsl/base...
    Ele é usado no Cursed Mode de um projeto paralelo, onde renderiza um framebuffer WebGL como um BMP de 640x480 pixels em cores indexadas codificado em Base64, cerca de 15 vezes por segundo: https://rezmason.github.io/excel_97_egg/?cursed=1

  • Quando você começa a se aprofundar, há detalhes adicionais interessantes, e as variações desses detalhes são surpreendentemente numerosas
    Se o comprimento dos dados de entrada não for exatamente múltiplo de 3 bytes, são usados 2 ou 3 caracteres Base64 para codificar o último 1 byte ou 2 bytes. Como um caractere Base64 tem 6 bits, acaba-se usando 12 bits ou 18 bits para representar 8 bits ou 16 bits, e com isso surgem 4 bits ou 2 bits extras que não codificam nada
    A RFC exige que o codificador defina esses bits como 0, mas diz apenas que o decodificador pode rejeitar entradas em que esses bits não sejam 0. Na prática, quase nenhuma implementação rejeita isso por padrão e, até onde sei, apenas Ruby, Rust e Go podem ser configurados para falhar nesse tipo de entrada. Python tem a opção validate, mas ela não verifica esses bits
    Outra grande diferença é o tratamento de espaços em branco e caracteres que não são Base64. Uma quantidade surpreendente de implementações, incluindo Python, simplesmente ignora em silêncio caracteres arbitrários na entrada. Isso vira um problema se você escolher o alfabeto errado; por exemplo, em Python, base64.standard_b64decode(base64.urlsafe_b64encode(b'\xFF\xFE\xFD\xFC')) não gera erro e retorna silenciosamente uma saída incorreta
    Também é curioso que o codificador Base64 do Ruby insira quebras de linha a cada 60 caracteres. Fora PEM, não há uma codificação padrão que exija linhas tão curtas, e PEM exige exatamente linhas de 64 caracteres, então é uma escolha bem peculiar
    Escrevi um texto resumindo as diferenças entre linguagens de programação e algumas bibliotecas JavaScript [1], e também estou trabalhando para adicionar um Base64 melhor ao JS [2]
    [1] https://gist.github.com/bakkot/16cae276209da91b652c2cb3f612a...
    [2] https://github.com/tc39/proposal-arraybuffer-base64