¿Por qué necesito 'b' para codificar una cadena con Base64?

258

Siguiendo este ejemplo de Python , codifico una cadena como Base64 con:

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

Pero, si dejo de lado el líder b:

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

Obtuve el siguiente error:

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 qué es esto?

dublintech
fuente
38
En realidad, todas las preguntas que devuelven "TypeError: bytes esperados, no str" tienen la misma respuesta.
Lennart Regebro

Respuestas:

274

codificación Base64 tiene 8 bits de datos binarios de bytes y lo codifica utiliza sólo los caracteres A-Z, a-z, 0-9, +, /* para que pueda ser transmitida a través de canales que no conservan los 8 bits de datos, tales como correo electrónico.

Por lo tanto, quiere una cadena de bytes de 8 bits. Los creas en Python 3 con la b''sintaxis.

Si elimina el b, se convierte en una cadena. Una cadena es una secuencia de caracteres Unicode. base64 no tiene idea de qué hacer con los datos Unicode, no es de 8 bits. En realidad no es nada, de hecho. :-)

En tu segundo ejemplo:

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

Todos los caracteres encajan perfectamente en el conjunto de caracteres ASCII y, por lo tanto, la codificación base64 es en realidad un poco inútil. En su lugar, puede convertirlo a ASCII, con

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

O más simple:

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

Lo que sería lo mismo en este caso.


* La mayoría de los sabores base64 también pueden incluir un =al final como relleno. Además, algunas variantes de base64 pueden usar caracteres distintos de +y /. Consulte la tabla de resumen de Variantes en Wikipedia para obtener una descripción general.

Lennart Regebro
fuente
174

Respuesta corta

Es necesario para empujar un bytes-likeobjeto ( bytes, bytearray, etc.) al base64.b64encode()método. Aquí hay dos formas:

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

O con una variable:

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

¿Por qué?

En Python 3, los strobjetos no son matrices de caracteres de estilo C (por lo que no son matrices de bytes), sino estructuras de datos que no tienen ninguna codificación inherente. Puede codificar esa cadena (o interpretarla) de varias maneras. El más común (y predeterminado en Python 3) es utf-8, especialmente porque es compatible con versiones anteriores de ASCII (aunque, como son las codificaciones más utilizadas). Eso es lo que está sucediendo cuando toma un stringy llama al .encode()método: Python está interpretando la cadena en utf-8 (la codificación predeterminada) y le proporciona la matriz de bytes a la que corresponde.

Codificación Base-64 en Python 3

Originalmente, el título de la pregunta era sobre la codificación Base-64. Siga leyendo para obtener información sobre Base-64.

base64la codificación toma fragmentos binarios de 6 bits y los codifica con los caracteres AZ, az, 0-9, '+', '/' y '=' (algunas codificaciones usan caracteres diferentes en lugar de '+' y '/') . Esta es una codificación de caracteres que se basa en la construcción matemática del sistema de números radix-64 o base-64, pero son muy diferentes. Base-64 en matemáticas es un sistema numérico como binario o decimal, y usted hace este cambio de radix en todo el número, o (si la radix de la que está convirtiendo es una potencia de 2 menor que 64) en trozos de derecha a izquierda.

En la base64codificación, la traducción se realiza de izquierda a derecha; esos primeros 64 caracteres son la razón por la que se llama base64 codificación . El símbolo 65 '=' se usa para el relleno, ya que la codificación extrae fragmentos de 6 bits, pero los datos que generalmente debe codificar son bytes de 8 bits, por lo que a veces solo hay dos o 4 bits en el último fragmento.

Ejemplo:

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

Si interpreta esos datos binarios como un solo entero, entonces así es como los convertiría en base-10 y base-64 ( tabla 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 la codificación , sin embargo, reagrupará estos datos de esta manera:

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

Entonces, 'B0ZXN0' es la versión base 64 de nuestro binario, matemáticamente hablando. Sin embargo, la base64 codificación tiene que hacer la codificación en la dirección opuesta (por lo que los datos sin procesar se convierten a 'dGVzdA') y también tiene una regla para indicar a otras aplicaciones cuánto espacio queda al final. Esto se hace rellenando el final con símbolos '='. Entonces, la base64codificación de estos datos es 'dGVzdA ==', con dos símbolos '=' para indicar que dos pares de bits deberán eliminarse del final cuando estos datos se decodifiquen para que coincidan con los datos originales.

Probemos esto para ver si estoy siendo deshonesto:

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

¿Por qué usar base64codificación?

Digamos que tengo que enviar algunos datos a alguien por correo electrónico, como estos datos:

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

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

Hay dos problemas que planté:

  1. Si intenté enviar ese correo electrónico en Unix, el correo electrónico se enviaría tan pronto como \x04se leyera el carácter, ya que es ASCII para END-OF-TRANSMISSION(Ctrl-D), por lo que los datos restantes quedarían fuera de la transmisión.
  2. Además, si bien Python es lo suficientemente inteligente como para escapar de todos mis personajes de control malvados cuando imprimo los datos directamente, cuando esa cadena se decodifica como ASCII, puede ver que el 'mensaje' no está allí. Eso es porque usé tres BACKSPACEcaracteres y tres SPACEcaracteres para borrar el 'mensaje'. Por lo tanto, incluso si no tuviera el EOFcarácter allí, el usuario final no podría traducir del texto en pantalla a los datos reales y sin procesar.

Esta es solo una demostración para mostrarle lo difícil que puede ser simplemente enviar datos sin procesar. Codificar los datos en formato base64 le proporciona exactamente los mismos datos, pero en un formato que garantiza que sea seguro enviarlos por medios electrónicos como el correo electrónico.

Greg Schmit
fuente
66
base64.b64encode(s.encode()).decode()no es muy pitónico cuando todo lo que quieres es una conversión de cadena a cadena. base64.encode(s)debería ser suficiente al menos en python3. Gracias por una muy buena explicación sobre cadenas y bytes en python
MortenB
2
@MortenB Sí, es extraño, pero al revés está muy claro lo que está sucediendo siempre que el ingeniero sea consciente de la diferencia entre las matrices de bytes y cadenas, ya que no hay una sola asignación (codificación) entre ellas, como en otros idiomas asumir.
Greg Schmit
3
@MortenB Por cierto, base64.encode(s)no funcionaría en Python3; ¿Estás diciendo que algo así debería estar disponible? Creo que la razón por la que puede ser confuso es que, dependiendo de la codificación y el contenido de la cadena, spodría no tener 1 representación única como una matriz de bytes.
Greg Schmit
Schmitt: fue solo un ejemplo de lo simple que debería ser. Los casos de uso más comunes deberían ser así.
MortenB
1
@MortenB pero b64 no es solo para texto, cualquier contenido binario puede ser codificado en b64 (audio, imágenes, etc.). Hacer que funcione como usted propone en mi opinión oculta la diferencia entre el texto y la matriz de bytes aún más, lo que dificulta la depuración. Simplemente mueve la dificultad a otro lugar.
Michael Ekoka
32

Si los datos a codificar contienen caracteres "exóticos", creo que debe codificar en "UTF-8"

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

Si la cadena es Unicode, la forma más fácil es:

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
fuente
Realmente no es la forma más fácil, sino una de las formas más claras, cuando es importante qué codificación se utiliza para transmitir la cadena, que forma parte del "protocolo" de transmisión de datos a través de base64.
xuiqzy
12

Hay todo lo que necesitas:

expected bytes, not str

La guía bhace que su cadena sea binaria.

¿Qué versión de Python usas? 2.xo 3.x?

Editar: consulte http://docs.python.org/release/3.0.1/whatsnew/3.0.html#text-vs-data-instead-of-unicode-vs-8-bit para ver los detalles sangrientos de las cadenas en Python 3.x


fuente
Gracias estoy usando, 3.x. ¿Por qué Python quiere convertirlo explícitamente a binario? Lo mismo en Ruby sería ... requiere> "base64" y luego> Base64.encode64 ('datos a codificar')
dublintech
2
@dublintech Porque el texto (unicode) es diferente de los datos sin procesar. Si desea codificar una cadena de texto en Base64, primero debe determinar la codificación de caracteres (como UTF-8) y luego tiene bytes en lugar de caracteres, que puede codificar en una forma segura de texto ascii.
fortran
2
Esto no responde la pregunta. Él sabe que funciona con un objeto de bytes, pero no con un objeto de cadena. La pregunta es por qué .
Lennart Regebro
@fortran La codificación de cadena Python3 predeterminada es UTF, no sé, por qué tiene que establecerse explícitamente.
xmedeko
0

Eso b simplemente significa que está tomando la entrada como una matriz de bytes o bytes, no como una cadena.

Atul6.Singh
fuente