Código Python 3.9+ puro, sem dependências. Abaixo do algoritmo você encontra os plugues prontos para Django e Pydantic, que costumam ser onde a validação de CNPJ realmente mora no projeto.
0=0 … 9=9, A=17 … Z=42), os pesos vão de 2 a 9 da direita para a esquerda, soma-se tudo, divide-se por 11 e o dígito é 0 quando o resto é menor que 2, ou 11 menos o resto. Ver a conta detalhada.Código completo
import re
NAO_ALFANUMERICO = re.compile(r"[^0-9A-Z]")
FORMATO = re.compile(r"^[0-9A-Z]{12}\d{2}$")
def limpar_cnpj(valor: str) -> str:
"""Remove máscara e normaliza para maiúsculas."""
return NAO_ALFANUMERICO.sub("", (valor or "").upper())
def _digito(sequencia: str) -> int:
"""Calcula um dígito do módulo 11 (valor do caractere = ASCII - 48)."""
soma = 0
tamanho = len(sequencia)
for indice, caractere in enumerate(sequencia):
peso = ((tamanho - 1 - indice) % 8) + 2
soma += (ord(caractere) - 48) * peso
resto = soma % 11
return 0 if resto < 2 else 11 - resto
def calcular_dv(base: str) -> str:
"""Devolve os dois dígitos verificadores da base de 12 caracteres."""
primeiro = _digito(base)
segundo = _digito(f"{base}{primeiro}")
return f"{primeiro}{segundo}"
def validar_cnpj(valor: str) -> bool:
"""Valida CNPJ numérico ou alfanumérico, com ou sem máscara."""
cnpj = limpar_cnpj(valor)
if not FORMATO.match(cnpj):
return False
if cnpj == cnpj[0] * 14:
return False
return calcular_dv(cnpj[:12]) == cnpj[12:]
def formatar_cnpj(valor: str) -> str:
"""Aplica a máscara 00.000.000/0000-00."""
cnpj = limpar_cnpj(valor)
if len(cnpj) != 14:
return cnpj
return f"{cnpj[:2]}.{cnpj[2:5]}.{cnpj[5:8]}/{cnpj[8:12]}-{cnpj[12:]}"
if __name__ == "__main__":
assert calcular_dv("12ABC34501DE") == "35"
assert validar_cnpj("12.ABC.345/01DE-35")
assert validar_cnpj("11.222.333/0001-81")
assert not validar_cnpj("11.222.333/0001-82")
print(formatar_cnpj("12abc34501de35")) # 12.ABC.345/01DE-35
Testes e integração com o framework
# Django — validador de campo
from django.core.exceptions import ValidationError
from django.db import models
def valida_cnpj(valor):
if not validar_cnpj(valor):
raise ValidationError("%(valor)s não é um CNPJ válido", params={"valor": valor})
class Empresa(models.Model):
# 14 caracteres sem máscara, 18 se você guardar formatado
cnpj = models.CharField(max_length=14, unique=True, validators=[valida_cnpj])
def save(self, *args, **kwargs):
self.cnpj = limpar_cnpj(self.cnpj)
super().save(*args, **kwargs)
# Pydantic v2 — tipo reutilizável
from typing import Annotated
from pydantic import AfterValidator, BaseModel
def _checa(valor: str) -> str:
limpo = limpar_cnpj(valor)
if not validar_cnpj(limpo):
raise ValueError("CNPJ inválido")
return limpo
CnpjStr = Annotated[str, AfterValidator(_checa)]
class EmpresaSchema(BaseModel):
razao_social: str
cnpj: CnpjStr
Detalhes que costumam quebrar em Python
ord()devolve o code point do caractere; para0–9eA–Zé o mesmo valor da tabela ASCII.- Cuidado com
str.isdigit()eint(cnpj)espalhados pelo código antigo: eles quebram assim que a primeira letra aparecer. - Se você usa
validate-docbrou similar, confira se a versão instalada já suporta o formato alfanumérico antes de confiar nela.
Casos de teste recomendados
Cubra pelo menos estes cenários — eles pegam praticamente todos os erros de implementação:
| Entrada | Esperado | O que testa |
|---|---|---|
12.ABC.345/01DE-35 | válido | exemplo oficial, com máscara |
12abc34501de35 | válido | normalização para maiúsculas |
11.222.333/0001-81 | válido | compatibilidade com o formato numérico |
11.222.333/0001-82 | inválido | dígito verificador errado |
12ABC34501DEAB | inválido | letra no dígito verificador |
12ABC34501DE3 | inválido | tamanho incorreto |
00.000.000/0000-00 | inválido | sequência repetida |
"" / null | inválido | entrada vazia sem exceção |
Precisa de mais massa de teste? O gerador produz até 500 CNPJs alfanuméricos válidos de uma vez, com opção de baixar em .txt.