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
CnpjouCpf, ela é garantidamente válida - Serialização JSON nativa: conversores para
System.Text.Jsonincluí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:
| Documento | Cenário | Throughput |
|---|---|---|
| CPF | Pior caso (dígito inválido) | ~15,6 milhões/s |
| CPF | Dataset misto | ~22,9 milhões/s |
| CNPJ numérico | Pior caso | ~5,3 milhões/s |
| CNPJ numérico | Dataset misto | ~9,1 milhões/s |
| CNPJ alfanumérico | Formato 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:
| Formato | CNPJ | CPF | Exemplo CNPJ |
|---|---|---|---|
"G" | Com pontuação | Com pontuação | 09.358.105/0001-91 |
"B" | 14 caracteres | 11 dígitos | 09358105000191 |
"S" | Sem zeros à esquerda | Sem zeros à esquerda | 9358105000191 |
"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.
Anteriormente, após cada artigo de nosso blog, havia uma seção de comentários e opções de compartilhamento simples.
Contudo, com a entrada em vigor da LGPD, a coleta de informações pessoais exige consentimento prévio, o que dificulta manter uma interação fluida. Além disso, provedores de comentários e ferramentas de moderação exigem coleta de dados, tornando-nos corresponsáveis ao incorporá-los em nosso site.
Paralelamente, decisões recentes (final de 2024) do STF aumentaram a responsabilidade dos proprietários de sites sobre o conteúdo postado por terceiros, exigindo remoção imediata de conteúdos potencialmente ofensivos, mesmo sem determinação judicial prévia.
Diante desse cenário ruim em termos de Liberdade de Expressão, optamos por encerrar a seção de comentários, visando cumprir a legislação e reduzir riscos operacionais.