# Base45 Decoder

> Decode Base45, the QR-code-friendly encoding defined in RFC 9285, into text and bytes.

- Page: https://decoder.tools/base45-decoder
- Category: Data, Radix & String
- Status: live, runs in the browser
- Last reviewed: 2026-10-23

A Base45 decoder converts a string written in the 45-character alphabet of RFC 9285 back into bytes. Every 3 characters become 2 bytes.

## Example

This is the test vector from RFC 9285:

```text Input
%69 VD92EX0
```

```text Output
Hello!!
```

Note that the space in the input is part of the data. In Base45 a space is a valid character, not a separator.

## What is Base45?

QR codes have an **alphanumeric mode** that can only store 45 characters: `0–9`, `A–Z`, space and `$ % * + - . / :`. Characters in that mode take fewer bits each than in byte mode, so the same data fits in a smaller code. Base45 was designed to fit binary data into exactly that character set.

The encoding takes 2 bytes at a time, treats them as a number from 0 to 65535, and writes that number in base 45 using three characters, least significant first. A single trailing byte becomes two characters.

The best-known use is the **EU Digital COVID Certificate**. Its QR code text starts with `HC1:`, followed by Base45. The decoded bytes are zlib-compressed, and inside is a COSE-signed CBOR document. So decoding a certificate takes three steps: Base45 decode, inflate with zlib, then parse CBOR.

## How to use the Base45 decoder

1. Paste the Base45 text. If it starts with a prefix like `HC1:`, remove the prefix first.
2. Read the result. Short test strings decode to text; real certificate payloads decode to binary.
3. For a certificate, check the hex view: zlib data starts with `78`. Copy the hex into the [Gzip / zlib decompressor](/gzip-decompressor) with **Input is: Hex** and **Format: zlib / deflate** to unpack it.

## Decode Base45 in code

```python Python
A = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"

def b45decode(s: str) -> bytes:
    out = bytearray()
    for i in range(0, len(s), 3):
        chunk = [A.index(c) for c in s[i:i + 3]]
        n = sum(v * 45 ** k for k, v in enumerate(chunk))
        out += n.to_bytes(2 if len(chunk) == 3 else 1, "big")
    return bytes(out)

print(b45decode("%69 VD92EX0").decode())  # Hello!!
```

## Base45 at a glance

| | |
|---|---|
| Specification | RFC 9285 |
| Alphabet | `0–9 A–Z`, space and `$ % * + - . / :` |
| Ratio | 3 characters per 2 bytes |
| Main use | QR codes in alphanumeric mode, EU Digital COVID Certificates |

## FAQ

### Why use Base45 instead of Base64 in a QR code?

Base64 needs lowercase letters, which forces the QR code into byte mode. Base45 stays inside the alphanumeric mode, which packs characters more tightly, so the same data produces a smaller, easier-to-scan code.

### How do I read an EU COVID certificate?

Remove the `HC1:` prefix, decode the rest as Base45, inflate the result with zlib, and then parse the CBOR/COSE structure. This tool handles the first step and the gzip tool handles the second. The CBOR step needs a CBOR decoder.

### Is the space character really part of the data?

Yes. The space is character number 36 in the Base45 alphabet. Removing or collapsing spaces changes the data, so paste the string exactly as it is.
