Por que preciso do 'b' para codificar uma string com Base64?

258

Seguindo este exemplo de python , codifico uma string como Base64 com:

>>> import base64
>>> encoded = base64.b64encode(b'data to be encoded')
>>> encoded
b'ZGF0YSB0byBiZSBlbmNvZGVk'

Mas, se eu deixar de fora a liderança b:

>>> encoded = base64.b64encode('data to be encoded')

Estou tendo o erro a seguir:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Python32\lib\base64.py", line 56, in b64encode
   raise TypeError("expected bytes, not %s" % s.__class__.__name__)
   TypeError: expected bytes, not str

Por que é isso?

dublintech
fonte
38
Na verdade, todas as perguntas que retornam "TypeError: bytes esperados, não str" têm a mesma resposta.
Lennart Regebro 18/01/12

Respostas:

274

base64 codificação leva de 8 bits de dados byte binários e codifica utiliza apenas os personagens A-Z, a-z, 0-9, +, /* para que ele possa ser transmitido através de canais que não preservam todos os 8-bits de dados, tais como e-mail.

Portanto, ele deseja uma sequência de bytes de 8 bits. Você cria aqueles no Python 3 com a b''sintaxe.

Se você remover o b, ele se tornará uma sequência. Uma sequência é uma sequência de caracteres Unicode. base64 não tem idéia do que fazer com dados Unicode, não é de 8 bits. Na verdade, não é nenhum pedaço. :-)

No seu segundo exemplo:

>>> encoded = base64.b64encode('data to be encoded')

Todos os caracteres se encaixam perfeitamente no conjunto de caracteres ASCII e, portanto, a codificação base64 é um pouco inútil. Você pode convertê-lo para ascii, com

>>> encoded = 'data to be encoded'.encode('ascii')

Ou mais simples:

>>> encoded = b'data to be encoded'

O que seria a mesma coisa neste caso.


* A maioria dos sabores base64 também pode incluir um =no final como preenchimento. Além disso, algumas variantes de base64 podem usar caracteres diferentes de +e /. Veja a tabela de resumo de variantes na Wikipedia para uma visão geral.

Lennart Regebro
fonte
174

Resposta curta

É necessário empurrar um bytes-likeobjecto ( bytes, bytearray, etc.) para o base64.b64encode()método. Aqui estão duas maneiras:

>>> data = base64.b64encode(b'data to be encoded')
>>> print(data)
b'ZGF0YSB0byBiZSBlbmNvZGVk'

Ou com uma variável:

>>> string = 'data to be encoded'
>>> data = base64.b64encode(string.encode())
>>> print(data)
b'ZGF0YSB0byBiZSBlbmNvZGVk'

Por quê?

Em Python 3, strobjectos não são matrizes de caracteres de estilo C (de modo que eles são não byte matrizes), mas em vez disso, eles são estruturas de dados que não têm qualquer codificação inerente. Você pode codificar essa sequência (ou interpretá-la) de várias maneiras. O mais comum (e o padrão no Python 3) é o utf-8, especialmente porque é compatível com o ASCII (embora, como as codificações mais usadas). É o que está acontecendo quando você pega um stringe chama o .encode()método: Python está interpretando a string em utf-8 (a codificação padrão) e fornecendo a matriz de bytes a que corresponde.

Codificação Base-64 em Python 3

Originalmente, o título da pergunta era sobre a codificação Base-64. Leia sobre as coisas da Base-64.

base64a codificação pega pedaços binários de 6 bits e os codifica usando os caracteres AZ, az, 0-9, '+', '/' e '=' (algumas codificações usam caracteres diferentes no lugar de '+' e '/') . Essa é uma codificação de caracteres baseada na construção matemática do sistema de números radix-64 ou base-64, mas eles são muito diferentes. A base-64 em matemática é um sistema numérico como binário ou decimal, e você faz essa alteração de raiz em todo o número, ou (se a raiz da qual você está convertendo for uma potência de 2 menor que 64) em pedaços da direita para esquerda.

Na base64codificação, a tradução é feita da esquerda para a direita; esses primeiros 64 caracteres são o motivo pelo qual é chamado de base64 codificação . O 65º símbolo '=' é usado para preenchimento, pois a codificação extrai pedaços de 6 bits, mas os dados que costuma codificar são bytes de 8 bits, portanto, às vezes, existem apenas dois ou 4 bits no último pedaço.

Exemplo:

>>> data = b'test'
>>> for byte in data:
...     print(format(byte, '08b'), end=" ")
...
01110100 01100101 01110011 01110100
>>>

Se você interpretar esses dados binários como um único número inteiro, é assim que você os converteria em base 10 e base 64 ( tabela para base 64 ):

base-2:  01 110100 011001 010111 001101 110100 (base-64 grouping shown)
base-10:                            1952805748
base-64:  B      0      Z      X      N      0

base64 A codificação , no entanto, agrupará novamente esses dados:

base-2:  011101  000110  010101 110011 011101 00(0000) <- pad w/zeros to make a clean 6-bit chunk
base-10:     29       6      21     51     29      0
base-64:      d       G       V      z      d      A

Portanto, 'B0ZXN0' é a versão base 64 do nosso binário, matematicamente falando. No entanto, a base64 codificação deve fazer a codificação na direção oposta (para que os dados brutos sejam convertidos em 'dGVzdA') e também possui uma regra para informar a outras aplicações quanto espaço resta no final. Isso é feito preenchendo o final com símbolos '='. Portanto, a base64codificação desses dados é 'dGVzdA ==', com dois símbolos '=' para indicar que dois pares de bits precisarão ser removidos do final quando esses dados forem decodificados para que correspondam aos dados originais.

Vamos testar isso para ver se estou sendo desonesto:

>>> encoded = base64.b64encode(data)
>>> print(encoded)
b'dGVzdA=='

Por que usar base64codificação?

Digamos que eu tenho que enviar alguns dados para alguém por email, como esses dados:

>>> data = b'\x04\x6d\x73\x67\x08\x08\x08\x20\x20\x20'
>>> print(data.decode())

>>> print(data)
b'\x04msg\x08\x08\x08   '
>>>

Existem dois problemas que plantei:

  1. Se eu tentasse enviar esse email no Unix, o email seria enviado assim que o \x04caractere fosse lido, porque é ASCII para END-OF-TRANSMISSION(Ctrl-D), para que os dados restantes fiquem de fora da transmissão.
  2. Além disso, enquanto o Python é inteligente o suficiente para escapar de todos os meus caracteres de controle malignos quando imprimo os dados diretamente, quando essa string é decodificada como ASCII, você pode ver que o 'msg' não está lá. Isso porque usei três BACKSPACEcaracteres e três SPACEcaracteres para apagar o 'msg'. Assim, mesmo se eu não tivesse o EOFpersonagem, o usuário final não seria capaz de traduzir do texto na tela para os dados reais e brutos.

Esta é apenas uma demonstração para mostrar o quão difícil pode ser simplesmente enviar dados brutos. A codificação dos dados no formato base64 fornece exatamente os mesmos dados, mas em um formato que garante a segurança do envio por mídia eletrônica, como email.

Greg Schmit
fonte
6
base64.b64encode(s.encode()).decode()não é muito pitonico quando tudo o que você quer é uma conversão de string para string. base64.encode(s)deve ser suficiente pelo menos em python3. Obrigado por uma explicação muito boa sobre seqüências de caracteres e bytes em python
MortenB
2
@MortenB Sim, é estranho, mas o lado positivo é muito claro o que está acontecendo, desde que o engenheiro esteja ciente da diferença entre matrizes de bytes e seqüências de caracteres, já que não há um único mapeamento (codificação) entre eles, como outros idiomas presumir.
Greg Schmit 22/02
3
@ MortenB A propósito, base64.encode(s)não funcionaria em Python3; você está dizendo que algo assim deveria estar disponível? Eu acho que a razão pela qual isso pode ser confuso é que, dependendo da codificação e do conteúdo da string, stalvez não tenha uma representação única como uma matriz de bytes.
Greg Schmit 22/02
Schmitt: foi apenas um exemplo de como deveria ser simples. os casos de uso mais comuns devem ser assim.
MortenB 23/02
1
@MortenB, mas b64 não se destina apenas a texto, qualquer conteúdo binário pode ser codificado em b64 (áudio, imagens, etc.). Fazê-lo funcionar como você propõe, na minha opinião, oculta ainda mais a diferença entre o texto e a matriz de bytes, dificultando a depuração. Simplesmente move a dificuldade para outro lugar.
Michael Ekoka
32

Se os dados a serem codificados contiverem caracteres "exóticos", acho que você precisará codificar em "UTF-8"

encoded = base64.b64encode (bytes('data to be encoded', "utf-8"))
Alecz
fonte
24

Se a string for Unicode, a maneira mais fácil é:

import base64                                                        

a = base64.b64encode(bytes(u'complex string: ñáéíóúÑ', "utf-8"))

# a: b'Y29tcGxleCBzdHJpbmc6IMOxw6HDqcOtw7PDusOR'

b = base64.b64decode(a).decode("utf-8", "ignore")                    

print(b)
# b :complex string: ñáéíóúÑ
alfredocambera
fonte
Realmente não é a maneira mais fácil, mas uma das maneiras mais claras, quando é importante qual codificação é usada para transmitir a string, que faz parte do "protocolo" da transmissão de dados pela base64.
xuiqzy
12

Tudo o que você precisa:

expected bytes, not str

A liderança btorna sua string binária.

Qual versão do Python você usa? 2.x ou 3.x?

Edit: Veja http://docs.python.org/release/3.0.1/whatsnew/3.0.html#text-vs-data-instead-of-unicode-vs-8-bit para obter os detalhes sangrentos das seqüências de caracteres em Python 3.x


fonte
Obrigado, estou usando, 3.x. Por que o Python deseja convertê-lo explicitamente em binário? O mesmo em Ruby seria ... requer> "base64" e depois> Base64.encode64 ( 'dados a serem codificados')
dublintech
2
@ublintech Porque o texto (unicode) é diferente dos dados brutos. Se você deseja codificar uma sequência de texto em Base64, primeiro você precisa determinar a codificação de caracteres (como UTF-8) e, em seguida, possui bytes em vez de caracteres, que podem ser codificados em um formato seguro para ASCII.
fortran
2
Isso não responde à pergunta. Ele sabe que funciona com um objeto bytes, mas não com um objeto string. A questão é o porquê .
Lennart Regebro 18/01/12
@fortran A codificação padrão da string Python3 é UTF, não sei, por que ela deve ser definida explicitamente.
Xmledeko 28/07/16
0

Isso b significa simplesmente que você está recebendo entrada como uma matriz de bytes ou bytes, não como uma string.

Atul6.Singh
fonte