A Biblioteca .NET Definitiva para CNPJ e CPF

Em julho de 2026 a Receita Federal poderá começar a emitir CNPJs alfanuméricos. Seu sistema está pronto para validar 12.ABC.345/01DE-35? Se a resposta for não (ou "o quê?"), continue lendo, ainda dá tempo de ajustar-se.

CNPJ e CPF são estruturas onipresentes em qualquer sistema que rode no Brasil, a Receita não nos deixa esquecer. Mesmo um Sistema de Risco de Mercado, como o nosso RiskSystem, não escapa da necessidade de guardar esses identificadores e usá-los para buscas: fundos de investimento, custodiantes, administradores, contrapartes, todos identificados pelo CNPJ em múltiplos sistemas.

Há 14 anos publicamos aqui uma solução para esse problema: uma estrutura .NET para validar, armazenar e formatar CNPJs e CPFs. Era 2011, usávamos Visual Studio 2010 (depois 2012), não existiam LLMs 😉, e o mundo era mais simples. Agora (na verdade em 2024, apenas tornamos um pacote público agora), com a mudança regulatória que introduz letras no CNPJ, reescrevemos a biblioteca do zero, moderna, rápida, e disponível como pacote NuGet.

O CNPJ Alfanumérico

A Instrução Normativa RFB nº 2.229/2024, publicada em 2024-10-16, estabelece que, a partir de julho de 2026, a Receita Federal passará a emitir CNPJs alfanuméricos. Os 8 caracteres da raiz e os 4 caracteres da filial poderão conter letras de A a Z, além dos dígitos 0 a 9. O formato visual permanece o mesmo: RR.RRR.RRR/FFFF-DD, mas agora com possibilidades como 12.ABC.345/01DE-35.

O algoritmo de cálculo dos dígitos verificadores também muda (muito pouco). Para o cálculo do módulo 11, cada letra é convertida em seu valor numérico: A=17 (o valor ASCII de A subtraído do valor ASCII de 0), B=18, C=19... O resto da mecânica permanece igual. CNPJs puramente numéricos, já existentes, continuam válidos: ninguém terá de trocar o CNPJ, somente novos CNPJs, ou CNPJs com mais de 10 mil filiais, começarão a usar as letras.

A motivação da Receita é clara: com o crescimento do número de empresas no Brasil (6 milhões a mais por ano!), os CNPJs numéricos (com "apenas" 1 bilhão de combinações na raiz, 10⁹) começavam a ficar escassos. Com letras, o espaço salta para mais de 100 trilhões (36⁹) de possíveis empresas, cada uma podendo ter até 1,6 milhões (36⁴) de filiais. Problema resolvido por algumas gerações. A solução dada também é plenamente justificada: dentre as soluções possíveis para o problema do esgotamento, o uso de letras, sem aumentar o tamanho de campos, e mantendo o mesmo algoritmo, é claramente a de menor impacto.

Oportunidades Perdidas

Ainda que a solução data seja a melhor, creio que poderiam ir um pouco mais longe...

Banir I e O: A confusão visual entre I/1 e O/0 é um problema conhecido há décadas. Placas de veículos em vários países excluem essas letras justamente por isso. Códigos de produto, chaves de licença, qualquer sistema que envolva humanos lendo e digitando caracteres evita esse par problemático. Permitiu-se (por omissão) todas as 26 letras. Prepare-se para chamados de suporte sobre o CNPJ 00.I0O.O1I/I0O1-01. Espero que, internamente, eles barrem a distribuição de CNPJs visualmente confusos, talvez o façam. Mas preferiria isso já escrito na norma.

Fancy Plates: Uma oportunidade interessante que a Receita deixou passar. Imagine poder registrar ELEKTO/0001-40 ou AMAZON/0001-49 pagando uma taxa extra, como fazem em alguns países, legalmente, com placas personalizadas de veículos. Receita adicional para o governo, marketing instantâneo, e sutil, para empresas. Mas, aparentemente, continuaremos com identificadores aleatórios e sem graça... E muito desconfiados se alguém ganhar um CNPJ como BA.NCO.BOM/0001-91. Espero, também, que a receita impeça o registro de expletivos. Se não é possível escolher, que ao menos fosse possível evitar, legalmente, ofertas ruins, como LO.JAR.UIM/0001-29, ou piores.

A Biblioteca

Disponível no NuGet, a instalação é trivial:

dotnet add package Elekto.BrazilianDocuments

As características principais:

  • Suporte completo ao CNPJ alfanumérico: pronto para julho de 2026 (e compatível com CNPJs numéricos existentes)
  • Zero alocações: validação opera em ReadOnlySpan<char>, sem criar strings intermediárias
  • Tipos sempre válidos: se você tem uma instância de Cnpj ou Cpf, ela é garantidamente válida
  • Serialização JSON nativa: conversores para System.Text.Json incluídos
  • Serialização XML nativa: Para suportar SOAP/WCF em integrações legadas
  • Multi-target: suporta .NET 8, 9, 10 e .NET Standard 2.0 (e, portanto, o antigo .Net Framework 4.8)

O código-fonte está no GitHub, licença MIT.

Uso Básico

Parsing e validação seguem o padrão estabelecido por estruturas como Guid e DateTime:

using Elekto.BrazilianDocuments;

// Parse de CNPJ (aceita vários formatos, inclusive alfanumérico)
var cnpj = Cnpj.Parse("12.ABC.345/01DE-35");

// Validação sem exceção
if (Cnpj.TryParse(userInput, out var parsed))
{
    // É um CNPJ válido
    Console.WriteLine(parsed.ToString("G")); // 12.ABC.345/01DE-35
}

// Criação com cálculo automático dos dígitos verificadores
var elekto = Cnpj.Create("ELEKTO", "0001");
Console.WriteLine(elekto.ToString("G")); // 00.ELE.KTO/0001-40

// CPF funciona do mesmo modo
var cpf = Cpf.Parse("123.456.789-09");
var novoCpf = Cpf.Create(123456789); // dígitos calculados automaticamente

Para situações em que o input pode ser CPF ou CNPJ (um campo "documento" genérico, por exemplo), a classe BrazilianDocument faz a detecção automática:

var found = BrazilianDocument.TryParse(input, out Cpf cpf, out Cnpj cnpj);

switch (found)
{
    case DocumentType.Cpf:
        ProcessarPessoaFisica(cpf);
        break;
    case DocumentType.Cnpj:
        ProcessarPessoaJuridica(cnpj);
        break;
    case DocumentType.Unknown:
        // Documento inválido ou ambíguo
        break;
}

O Que É Considerado Válido

Esta biblioteca considera válidos CPFs e CNPJs cujos dígitos verificadores estejam corretamente calculados, e apenas isso. Não fazemos (nem poderíamos fazer) consulta à base da Receita para verificar se o documento está efetivamente cadastrado.

Após alguma pesquisa, não encontramos qualquer normativo da Receita Federal que proíba CNPJs como 99.999.999/9999-62 ou mesmo 00.000.000/0000-00, desde que os dígitos verificadores estejam corretos. A validação é puramente matemática, e a biblioteca reflete isso.

Por essa razão, 0/0000-00 é um CNPJ válido (dígitos verificadores: 00), assim como 0-00 é um CPF válido. Ambos estão mapeados para a propriedade estática .Empty das respectivas estruturas, útil para representar "ausência de documento" de modo tipado, sem recorrer a nullables.

A biblioteca é extremamente tolerante a digitação parcial, pode-se omitir os zeros iniciais (como em 1/0001-36), a pontuação (como em ERRADOERRO51), e até errar de lugar a pontuação (como em 0353.6783-0001/10, ou trocar . por , ou ; (como em 353,67,83;0001/10). Cremos que isso facilita o uso ou não fazer digitadores perderem tempo com formalidades. Naturalmente, sempre mostre devidamente formatado para pessoas, e sempre salve num formato saudável no storage: recomendamos B, que mantém o espaço ocupado constante, não desperdiça nada com pontuação e, no caso de Cnpj, não aloca nada, pois é o formato internamente usado pela estrutura.

Performance

A biblioteca foi projetada para uso em hot paths, com validação que não pressiona o garbage collector:

DocumentoCenárioThroughput
CPFPior caso (dígito inválido)~15,6 milhões/s
CPFDataset misto~22,9 milhões/s
CNPJ numéricoPior caso~5,3 milhões/s
CNPJ numéricoDataset misto~9,1 milhões/s
CNPJ alfanuméricoFormato 2026~5,3 milhões/s

Medições em Intel Xeon E-2146G @ 3.50 GHz, .NET 10, BenchmarkDotNet 0.14.0. Alocações: 0 bytes em todos os cenários, confirmado por [MemoryDiagnoser].

Para comparação: a versão original de 2011 validava "mais de 1 milhão de CNPJs por segundo". A versão atual faz mais de 5 milhões, e sem alocar memória. Quatorze anos de evolução do .NET, e do hardware, fazem diferença.

Tratamento de Erros

Quando o parsing falha, a biblioteca lança BadDocumentException, que inclui informações sobre qual tipo de documento estava sendo parseado:

try
{
    var cnpj = Cnpj.Parse("invalido");
}
catch (BadDocumentException ex) when (ex.SourceType == DocumentType.Cnpj)
{
    Console.WriteLine($"CNPJ inválido: {ex.InvalidDocument}");
}

Para validação sem exceções (o padrão recomendado em hot paths), use TryParse ou IsValid.

Formatos de Saída

Ambas as estruturas suportam format strings no método ToString:

FormatoCNPJCPFExemplo CNPJ
"G"Com pontuaçãoCom pontuação09.358.105/0001-91
"B"14 caracteres11 dígitos09358105000191
"S"Sem zeros à esquerdaSem zeros à esquerda9358105000191
"BS"Apenas raiz (8 chars)-09358105

Notas Finais

O uso é livre, a licença MIT é extremamente liberal, mas não nos responsabilizamos por problemas que você venha a ter caso use esse código. Ainda assim, caso encontre algum problema (ou tenha sugestões), abra uma issue no GitHub, ou mande um pull request.

O algoritmo de validação de CNPJ sem alocações foi baseado no trabalho de Fernando Cerqueira e Elemar Júnior. Créditos merecidos.

Boa sorte!

Sobre Esta Edição

Este artigo é uma reedição completa do original publicado em 2011 (e atualizado em 2013). A biblioteca foi inteiramente reescrita para suportar o CNPJ alfanumérico e as práticas modernas do .NET.

O código da versão anterior (sem suporte ao formato alfanumérico, mas funcional para CNPJs numéricos) permanece disponível no branch legacy do repositório, caso você tenha saudades, ou apenas queira comparar.