The Python binascii module provides fast, low-level operations for converting bytes to ASCII representations such as hexadecimal, Base64, quoted-printable, and uuencode. It also includes CRC calculations used to detect accidental changes in files and protocol messages. Most applications use higher-level wrappers, but understanding binascii gives you precise control over validation, errors, buffers, and binary formats.
This guide explains when the module is appropriate, how to keep text and bytes separate, how to decode Base64 strictly, how to inspect binary values in hexadecimal, and why CRC checksums are not cryptographic hashes. The examples rely only on the standard library and apply to diagnostics, file processing, network protocols, import pipelines, and integration services.
What is binascii?
The name means “binary to ASCII.” The module contains C-implemented primitives that convert binary data to printable encodings and back. Higher-level modules such as base64 and quopri use these functions internally.
For everyday code, prefer the higher-level APIs because their interfaces describe the relevant standard more directly. Use binascii when you need strict low-level decoding, direct access to CRC functions, specific exception handling, or efficient work with objects implementing the buffer protocol.
It is essential to distinguish str from bytes. A Python string contains Unicode text, while bytes contain octets. Moving between them requires an explicit encoding such as UTF-8. The guides to Python data structures and Python slicing provide useful background for manipulating sequences safely.
Hexadecimal with hexlify and unhexlify
Hexadecimal is useful for protocol debugging, binary identifiers, hashes, packet fields, and technical logs. Every input byte becomes two hexadecimal digits, so encoded output is twice as long as the source.
import binascii
data = b"Python\x00\xff"
hex_data = binascii.hexlify(data)
print(hex_data) # b'507974686f6e00ff'
print(binascii.unhexlify(hex_data))
b2a_hex() is an alias of hexlify(), while a2b_hex() is an alias of unhexlify(). The longer names are usually easier to understand during code review.
You can add separators for display:
address = b"\xaa\xbb\xcc\xdd\xee\xff"
print(binascii.hexlify(address, sep=b":"))
# b'aa:bb:cc:dd:ee:ff'
The built-in bytes.hex() returns text, and bytes.fromhex() accepts whitespace between groups. unhexlify() is stricter: input must have an even number of valid hexadecimal digits. This behavior is valuable when a protocol field must match an exact grammar.
try:
value = binascii.unhexlify("abc")
except binascii.Error as error:
print(f"Invalid hexadecimal value: {error}")
Low-level Base64 conversion
b2a_base64() encodes bytes as Base64. It includes a trailing newline by default, reflecting traditional line-oriented formats. Set newline=False for JSON fields, database columns, compact logs, or HTTP values.
payload = b"binary data\x00\x01"
encoded = binascii.b2a_base64(payload, newline=False)
decoded = binascii.a2b_base64(encoded)
assert decoded == payload
print(encoded)
Base64 is encoding, not encryption. Anyone who receives it can recover the original bytes. It is useful for transporting binary values through text-oriented systems. The dedicated Python Base64 guide covers URL-safe alphabets, Base32, and Base85 in more detail.
Strict Base64 validation
Lenient decoding may discard characters outside the alphabet. That is convenient for MIME text with line breaks, but dangerous when an API field is expected to use one canonical representation. Since Python 3.11, a2b_base64() supports strict_mode=True.
def decode_base64_strict(value: str) -> bytes:
try:
return binascii.a2b_base64(value, strict_mode=True)
except binascii.Error as error:
raise ValueError("Invalid Base64 value") from error
print(decode_base64_strict("UHl0aG9u"))
Strict mode rejects characters outside the alphabet, leading padding, excess padding, and trailing data after padding. Validation still needs a size limit. Decoding attacker-controlled data without limits can consume unnecessary memory and processing time.
Quoted-printable data
Quoted-printable keeps most ASCII text readable and represents other bytes with sequences such as =C3=A9. It is common in MIME email bodies and some legacy integrations.
text = "café and information".encode("utf-8")
encoded = binascii.b2a_qp(text)
decoded = binascii.a2b_qp(encoded)
print(encoded)
print(decoded.decode("utf-8"))
Options such as header, quotetabs, and istext change how spaces, tabs, and line endings are treated. For complete email messages, use the email package, which understands headers, MIME boundaries, policies, and transfer encodings. Use binascii only when the exact field format is already known.
CRC-32 for accidental corruption
crc32() calculates an unsigned 32-bit checksum compatible with the checksum used by ZIP. It can reveal accidental corruption during storage, copying, or transmission.
from pathlib import Path
import binascii
def crc32_file(path: Path, chunk_size: int = 64 * 1024) -> int:
crc = 0
with path.open("rb") as file:
while chunk := file.read(chunk_size):
crc = binascii.crc32(chunk, crc)
return crc
result = crc32_file(Path("data.bin"))
print(f"{result:08x}")
Chunked processing avoids loading a large file into memory. This is the same general pattern used when reading giant files without freezing Python.
CRC is not appropriate for passwords, API signatures, session tokens, or protection against intentional tampering. An attacker can modify the payload and calculate a new CRC. Use hashlib for cryptographic digests and HMAC for authenticated messages. Passwords require a dedicated password-hashing algorithm; see the Python password hashing guide.
CRC-HQX for legacy protocols
crc_hqx() implements a 16-bit CRC-CCITT calculation. It is used by some devices and older formats. The initial value must match the protocol specification:
data = b"message"
crc = binascii.crc_hqx(data, 0xffff)
print(f"{crc:04x}")
Do not choose the initial value by guesswork. Systems using the same polynomial may still differ in initialization, bit reflection, byte order, and final XOR processing.
Buffers and reduced copying
Most functions accept bytes-like objects, including bytes, bytearray, and objects exposing the buffer protocol. A memoryview can select part of a buffer without first constructing another bytes object.
buffer = bytearray(b"ABCDEF")
view = memoryview(buffer)[1:5]
print(binascii.hexlify(view)) # b'42434445'
When processing encoded data incrementally, a block can end in the middle of an encoded unit. Some operations may raise binascii.Incomplete. Keep the remaining bytes and prepend them to the next block instead of treating every incomplete chunk as permanent corruption.
Safe error handling
External input should be considered untrusted. Validate size and type before conversion, choose the decoder that matches the expected format, and convert low-level exceptions into a clear domain error.
def parse_hex_field(value: str, max_chars: int = 128) -> bytes:
if len(value) > max_chars:
raise ValueError("Hex field is too large")
if len(value) % 2:
raise ValueError("Hex field has an odd length")
try:
return binascii.unhexlify(value)
except (binascii.Error, ValueError) as error:
raise ValueError("Invalid hexadecimal field") from error
Avoid logging complete tokens, credentials, or binary payloads. Log the operation, length, failure class, and a correlation identifier instead. Structured diagnostics are easier to maintain and reduce accidental exposure.
When should you use higher-level modules?
Use base64 for Base16, Base32, Base64, Base85, and URL-safe variants. Use quopri or email for MIME quoted-printable content. Use bytes.hex() when a plain text result is sufficient. Use binascii for low-level primitives, CRC calculations, strict decoding, or efficient buffer integration.
Best practices
Document whether your function accepts text or bytes. Apply input limits before decoding. Use strict Base64 validation for canonical API fields. Never treat CRC as a security mechanism. Process files in chunks. Catch binascii.Error at the application boundary and preserve the original exception as the cause for debugging.
Conclusion
binascii is a compact and fast module for the lower-level details of binary-to-ASCII conversion. Its hexadecimal utilities, strict Base64 mode, quoted-printable helpers, and CRC functions are useful when a higher-level wrapper does not expose enough control. The key is to use the correct abstraction and apply explicit validation to all external data.
Read the official binascii documentation and RFC 4648 for the formal definitions of Base16, Base32, and Base64.







