In Python, hex to Base64 is a two-line standard-library operation: bytes.fromhex() parses a continuous hexadecimal string into raw bytes, and base64.b64encode() returns the canonical RFC 4648 padded Base64 representation of those bytes. The result is a bytes literal, which means the third line of nearly every correct script is a .decode("ascii") call. The conversion path is identical whether the input came from a hash, a network packet, a hex dump, or a Cryptopals exercise: validate that the hex string has an even number of characters from the set 0-9 and a-f, hand the bytes to the Base64 encoder, and copy the printed text. The reason this pattern is so short is that both functions do exactly one thing each. bytes.fromhex() refuses odd-length input, non-hex characters, and the 0x prefix that C and Rust allow. base64.b64encode() always pads the output with one or two equals signs when the byte count is not a multiple of three, which is the canonical form that every RFC 4648 parser accepts. The whole transformation is reversible without information loss, so there is nothing to configure, no key to manage, and no integrity check to interpret.

To confirm the pattern handles arbitrary lengths, the well-known Cryptopals Set 1 test vector "I'm killing your brain like a poisonous mushroom" written as the 48-byte hex string 49276d206b696c6c696e6720796f757220627261696e206c696b65206120706f69736f6e6f7573206d757368726f6f6d becomes SSdtIGtpbGxpbmcgeW91ciBicmFpbiBsaWtlIGEgcG9pc29ub3VzIG11c2hyb29t through exactly the same calls. The only thing that scales between a six-byte smoke test and a 48-byte sentence is how you obtain the bytes; the encoding logic does not change.

how to convert hex to base64 in python
how to convert hex to base64 in python

Hex to Base64 in Two Python Lines

The Python standard library gives you exactly two functions for this transformation, and they cooperate because bytes.fromhex() returns a bytes object and base64.b64encode() expects a bytes-like input. The output is also a bytes object, which is the part most snippets gloss over when they print it without decoding.

The full pattern, using the RFC 4648 "foobar" test vector so the result is independently verifiable:

import base64

hex_string = "666f6f626172"

raw = bytes.fromhex(hex_string)

b64 = base64.b64encode(raw).decode("ascii")

print(b64) # Zm9vYmFy

Here is the same logic broken into ordered steps you can adapt to any input:

  1. Import the base64 module and place your hex payload in a string variable with an even number of characters.
  2. Call bytes.fromhex(hex_string) to validate the input and produce the raw bytes; the call raises ValueError on odd lengths, 0x prefixes, underscores, or non-hex characters.
  3. Pass those bytes to base64.b64encode(raw) to get a bytes object containing the Base64 text, padded with one or two equals signs if the input length is not a multiple of three.
  4. Call .decode("ascii") on the result so you can print it, write it to a file in text mode, or paste it into a log line.
  5. Compare the printed string against the expected output for a known short input (the bytes b'foobar' must produce Zm9vYmFy) to confirm your environment is emitting canonical RFC 4648 Base64.

Three small details decide whether this prints the correct string or a confusing one. The hex format must be exactly two digits per byte. The decode step is what turns b'Zm9vYmFy' into the printable string "Zm9vYmFy"; without it, print(b64) still works but writing b64 directly to a text-mode file raises TypeError. And base64.b64encode() always produces canonical padded Base64, which is what most specifications require — if a downstream tool expects base64url or unpadded Base64, you must translate afterwards.

What "Strict Hex" Actually Means

The reason hex feels deceptively permissive is that any individual hex digit is valid — but a byte needs two of them. bytes.fromhex() enforces that boundary by raising ValueError on an odd number of digits, on characters outside 0-9 and a-f, and on the 0x prefix that other languages allow. A value like "f" alone is incomplete and rejected; "0f" is one byte. This is what stops an input mistake from quietly padding with zeros and producing the wrong bytes. Hex output is also lowercase by default in the strict Base16 view that most specifications assume: binascii.hexlify() returns lowercase, and bytes.hex() returns lowercase. If a spec asks for uppercase hex, you call .upper() on the result — but you should not assume the receiving tool accepts both cases without checking.

FormatExamplebytes.fromhex() behavior
Plain hex, even length666f6f626172Accepted; returns six bytes (b'foobar')
Uppercase hex digits666F6F626172Accepted; same six bytes
0x prefix per byte0x66 0x6f 0x6f 0x62 0x61 0x72Rejected; raise ValueError
Odd number of digits666f6f62617Rejected; raise ValueError
Whitespace or separators66 6f 6f 62 61 72Rejected; raise ValueError
Underscores between bytes66_6f_6f_62_61_72Rejected; raise ValueError

That strictness is a feature, not friction. When you are comparing a hash, a token, or a signature, the boundary check is the only thing that catches a missing digit before it propagates as a wrong comparison downstream.

A Browser Alternative When You Don't Want to Run Python

For one-off conversions — inspecting a payload from a log line, checking the bytes behind a hash, or pasting a value into a ticket — opening a REPL or a notebook is heavier than the task deserves. The Base64 to Hex Converter performs the same RFC 4648 Base64 to hexadecimal or hexadecimal to Base64 translation entirely in your browser, with no upload and no Python runtime to manage. The conversion contract is the same one bytes.fromhex() and b64encode() implement in Python: strict character validation, exact two-digit bytes, and canonical padded output.

Choose a direction, paste one representation, run the conversion, and copy the exact result. Input is bounded to 500,000 decoded bytes so the page does not stall on a gigabyte blob that should have stayed on the command line. The page is also useful as a cross-check. If your Python output disagrees with what the converter prints, one of two things has happened: the input had a 0x prefix or whitespace that bytes.fromhex() rejected somewhere upstream, or the receiving tool actually expects base64url or MIME-wrapped Base64 rather than standard RFC 4648. Both are easy to miss in a long pipeline. For the reverse direction and its specific pitfalls, the detailed Base64-to-hex conversion guide covers the same strict alphabet and padding rules in the other order.

Where Conversions Silently Go Wrong

Hex-to-Base64 conversions almost always succeed; the danger is succeeding with the wrong bytes. A handful of patterns account for most of the silent breakage.

  • ASCII-decoding the hex string first. Writing "foobar".encode().hex() is fine, but bytes.fromhex(hex_str.encode("ascii")) adds an extra step you do not need. If the hex string came from a socket or file that is not pure ASCII, that second decode can corrupt characters at the boundaries.
  • Calling b64decode instead of b64encode. The names are easy to swap. b64decode expects Base64 input and fails on hex with a binascii.Error. It is also strict by default — passing validate=False lets malformed input through with undefined behavior.
  • Mixing Base64 variants. base64.urlsafe_b64encode() replaces + with - and / with _, which is correct for URLs and many JWT libraries but wrong for almost everything else. RFC 4648 standard Base64 is the safe default; if a protocol spec says "Base64url" or "URL-safe Base64", use that function on purpose.
  • Stripping padding on short inputs. Python's b64encode always emits = or == on the last group when the input length is not a multiple of three. If you trim the padding to fit a fixed-width column, the next decoder may reject the result unless it is told to be lenient.
  • Reading the input as UTF-8 instead of bytes. The whole point of the conversion is byte fidelity. If you read a hex dump as UTF-8, you may replace invalid byte sequences with the U+FFFD replacement character and never know it happened.

Hex and Base64 Are Not Encryption

Both hex and Base64 are reversible representations of the same bytes — there is no key, no scrambling, and no integrity check. The output of base64.b64encode() is exactly as sensitive as the bytes you passed in. Converting a password hash, an API token, a private key, or a session identifier to Base64 before pasting it into a chat window does not protect it; it just makes the value look less recognizable. The same is true in reverse, which is why a hex dump of a secret is still a secret.

If the value you are converting touches production credentials or signing material, keep the conversion local. Run the Python in your own interpreter rather than a hosted notebook that logs input. Use a browser-based converter only on data that is safe to put into a browser tab, and assume anything copied to the clipboard can be read by another page or another user at the keyboard. For anything that needs to stay secret, the answer is encryption with a real key — and that is a different class of tool entirely, not a different encoding of the same bytes.