# Base64 com acentos: como codificar e decodificar sem erro

> O Base64 codifica bytes, não texto, então o que define o resultado é a codificação do texto antes da conversão. Com UTF-8, acentos e emojis voltam iguais. Este guia explica por que o btoa erra com acentos e emojis e mostra como codificar e decodificar Base64 e Base64URL em JavaScript, Python e PHP.

- Endereço oficial: https://pradev.com.br/guias/base64-com-acentos-utf8
- Tipo: Guia
- Publicado em: 2026-09-30
- Atualizado em: 2026-09-30
- Autor: Leonardo Gaertner (https://pradev.com.br/sobre)
- Ferramentas: [Codificar Base64](https://pradev.com.br/codificar-base64), [Decodificar Base64](https://pradev.com.br/decodificar-base64)

## O que o Base64 faz

O Base64 representa uma sequência de bytes usando só 64 caracteres seguros (letras, números, `+` e `/`), com `=` no fim quando falta completar. Ele aumenta o tamanho em cerca de um terço e não protege nada: qualquer pessoa decodifica. Não é criptografia.

Como ele trabalha com bytes, o texto precisa virar bytes antes. Letras com acento ocupam mais de um byte em UTF-8, e é aí que os erros aparecem. Para testar um valor agora, use o [codificador de Base64](https://pradev.com.br/codificar-base64) ou o [decodificador de Base64](https://pradev.com.br/decodificar-base64).

## Por que o btoa erra com acentos e emojis

No navegador, o `btoa` trata cada caractere como um byte (até o código 255, o Latin-1). Com `btoa("ação")` não há erro, mas o resultado é diferente do Base64 em UTF-8 que o resto do mundo espera, e a outra ponta decodifica letras trocadas. Com um caractere fora do Latin-1, como `btoa("✓")` ou um emoji, ele falha com `InvalidCharacterError`. O `atob` tem o mesmo limite na volta.

A solução é converter o texto em bytes UTF-8 com `TextEncoder`, codificar esses bytes e, na volta, usar `TextDecoder`:

```javascript
function codificarBase64(texto) {
  const bytes = new TextEncoder().encode(texto);
  let binario = '';
  bytes.forEach((b) => (binario += String.fromCharCode(b)));
  return btoa(binario);
}

function decodificarBase64(base64) {
  const binario = atob(base64);
  const bytes = Uint8Array.from(binario, (c) => c.charCodeAt(0));
  return new TextDecoder().decode(bytes);
}
```

_JavaScript. Funciona no navegador e também no Node.js._

## Em Node.js

No Node.js o `Buffer` já cuida da codificação do texto, e o UTF-8 é o padrão:

```javascript
function codificarBase64(texto) {
  return Buffer.from(texto, 'utf8').toString('base64');
}

function decodificarBase64(base64) {
  return Buffer.from(base64, 'base64').toString('utf8');
}
```

_JavaScript_

## Em Python e PHP

Em Python, `base64.b64encode` recebe bytes: chame `encode("utf-8")` antes e `decode("utf-8")` depois. Em PHP, as strings já são sequências de bytes, então `base64_encode` funciona direto, e vale usar o modo estrito no `base64_decode`.

```python
import base64


def codificar_base64(texto: str) -> str:
    return base64.b64encode(texto.encode("utf-8")).decode("ascii")


def decodificar_base64(b64: str) -> str:
    return base64.b64decode(b64).decode("utf-8")
```

_Python_

```php
<?php
function codificarBase64(string $texto): string
{
    return base64_encode($texto);
}

function decodificarBase64(string $b64): string
{
    $saida = base64_decode($b64, true); // true: recusa caracteres que não são Base64
    if ($saida === false) {
        throw new InvalidArgumentException('Base64 inválido');
    }
    return $saida;
}
```

_PHP_

## Base64URL: a variante para endereços e tokens

O `+` e o `/` têm significado em endereços, então a variante Base64URL troca `+` por `-` e `/` por `_` e costuma omitir o `=` do fim. É o formato usado nas partes de um JWT (veja o [guia de JWT](https://pradev.com.br/guias/como-decodificar-e-verificar-jwt)). No Node.js, `Buffer.toString("base64url")` já faz isso. Nas outras linguagens, a troca é simples:

```javascript
function paraBase64Url(base64) {
  return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}

function deBase64Url(url) {
  const base64 = url.replace(/-/g, '+').replace(/_/g, '/');
  return base64 + '='.repeat((4 - (base64.length % 4)) % 4);
}
```

_JavaScript. Converte de e para o Base64 comum. O preenchimento com = é refeito na volta._

```python
import base64


def codificar_base64url(dados: bytes) -> str:
    return base64.urlsafe_b64encode(dados).decode("ascii").rstrip("=")


def decodificar_base64url(texto: str) -> bytes:
    return base64.urlsafe_b64decode(texto + "=" * (-len(texto) % 4))
```

_Python_

```php
<?php
function codificarBase64Url(string $dados): string
{
    return rtrim(strtr(base64_encode($dados), '+/', '-_'), '=');
}

function decodificarBase64Url(string $texto): string
{
    $b64 = strtr($texto, '-_', '+/');
    return base64_decode($b64 . str_repeat('=', (4 - strlen($b64) % 4) % 4), true);
}
```

_PHP_

## Cuidados

- Não use Base64 para esconder senhas ou dados sensíveis: ele só muda a representação.
- Ao decodificar um texto de origem desconhecida, trate o erro: valores com caracteres inválidos ou tamanho errado não decodificam.
- Quebras de linha no meio do Base64 (comum em e-mail e certificados) precisam ser removidas antes de decodificar em algumas bibliotecas.
- Se o resultado decodificado não for texto, como uma imagem, trate-o como bytes e não como string.

## Perguntas frequentes

### Base64 é criptografia?

Não. É só uma forma de representar bytes com caracteres comuns, e qualquer pessoa decodifica sem chave. Para proteger dados, use criptografia de verdade.

### Por que o Base64 termina com um ou dois sinais de igual?

O Base64 trabalha em blocos de 3 bytes. Quando o último bloco tem menos que isso, o sinal de igual completa o tamanho: um quando faltou um byte e dois quando faltaram dois. Na variante URL, ele costuma ser omitido.

### Qual a diferença entre Base64 e Base64URL?

O Base64URL troca o mais e a barra por hífen e sublinhado, para não atrapalhar endereços e nomes de arquivo, e costuma omitir o preenchimento com igual. O conteúdo codificado é o mesmo.

### O Base64 aumenta o tamanho dos dados?

Sim, em cerca de 33%: a cada 3 bytes originais, ele gera 4 caracteres.

## Fontes

- [RFC 4648: codificações Base16, Base32 e Base64](https://www.rfc-editor.org/rfc/rfc4648)
- [MDN: Base64 (glossário)](https://developer.mozilla.org/pt-BR/docs/Glossary/Base64)

## Guias relacionados

- [Como decodificar e verificar um JWT em JavaScript e Python](https://pradev.com.br/guias/como-decodificar-e-verificar-jwt): Entenda a diferença entre decodificar e verificar um JWT e veja código testado em JavaScript e Python para ler o token e checar a assinatura HS256.
