Inteligência artificial, sem ruído.
Tutoriais7 min

Marca d’água em texto de IA com Python: as 3 famílias testadas na prática

Guia prático com Python: as três famílias de marca d'água em texto de IA, do hash de lista verde à invariância semântica, com harness de robustez.

Marca d’água em texto de IA com Python: as 3 famílias testadas na prática

Com a explosão de conteúdo gerado por IA em 2026, saber se um texto saiu de um modelo — e, mais ainda, de qual modelo — virou um problema de infraestrutura, não só de curiosidade acadêmica. A União Europeia já cobra transparência sobre conteúdo sintético, plataformas começam a rotular mídia gerada, e a detecção post-hoc (tentar adivinhar depois) se mostra frágil demais. A resposta técnica madura é a marca d’água: embutir um sinal no próprio texto, no momento em que ele é produzido.

Não existe uma marca d’água universal. Tudo depende de uma única pergunta: qual etapa do pipeline de texto você controla? Se você controla a geração, consegue uma marca estatística no nível dos tokens. Se controla só o texto final, pode esconder bits em edições. Se não controla nenhum dos dois, sobra a detecção posterior ou metadados assinados. Este guia testa, com Python executável e sem API, as três famílias mais importantes.

Prós e contras de marcar texto de IA

✅ O que você ganha:

  • Proveniência verificável: prova criptográfica de que o texto veio do seu sistema, sem depender de estilo de escrita.
  • Detecção estatística confiável: a verificação é um teste de hipótese contra uma chave secreta, não um palpite.
  • Resistência seletiva a ataques: escolha a família certa conforme sua ameaça (edição, paráfrase, cópia).
  • Custo baixíssimo: as técnicas de lista verde rodam em Python puro, sem API.
  • Conformidade e auditoria: facilita atender exigências de rotulagem e rastrear vazamentos.

⚠️ O que você NÃO ganha:

  • Imunidade a parafraseadores agressivos: nenhuma família resiste a tudo; a maioria cai sob reescrita forte.
  • Proteção contra quem reescreve tokens: se o sinal vive em tokens exatos, reescrever os destrói.
  • Robustez universal: o que sobrevive a edição pode morrer com uma normalização Unicode simples.

Tabela de requisitos

ComponenteMínimoRecomendadoIdeal
Python3.83.10+3.12
Bibliotecashashlib, math, re (stdlib)+ unicodedata, random+ um LLM p/ parafrasear
ConhecimentoPython básicoNoções de probabilidadeFamiliaridade com amostragem de LLM
HardwareQualquer notebook4 GB de RAMGPU (só p/ testes de LLM)
Tempo estimado30 min1-2 horasMeio dia com testes sérios
Requisitos para rodar o harness e as três famílias — tudo com biblioteca padrão.

Família 1 — Lista verde estatística (marca na geração)

Esta é a família usada pelos sistemas industriais. A ideia: uma função de hash com chave decide, token a token, se ele é “verde” ou “vermelho”. Na geração, você adiciona um viés (delta) aos logits dos tokens verdes. Na verificação, conta quantos tokens verdes sobreviveram e calcula um z-score — se estiver muito acima do esperado ao acaso, o texto foi marcado.

import hashlib, math

def is_green(key, prev, tok, gamma=0.5):
    # hash determinístico: o mesmo token é sempre verde ou vermelho
    h = hashlib.sha256(f"{key}|{prev}|{tok}".encode()).digest()
    return int.from_bytes(h[:8], "big") / 2**64 < gamma

def bias_logits(logits, key, prev, delta=2.0, gamma=0.5):
    # soma delta ao logit dos tokens verdes antes do softmax
    return {t: lp + (delta if is_green(key, prev, t, gamma) else 0.0)
            for t, lp in logits.items()}

def detect(tokens, key, gamma=0.5):
    hits = sum(is_green(key, tokens[i-1] if i else "", tokens[i], gamma)
               for i in range(len(tokens)))
    n = len(tokens)
    # z-score: desvio do esperado (gamma*n) em desvios padrão
    return (hits - gamma * n) / math.sqrt(n * gamma * (1 - gamma))

Um |z| entre 4 e 6 em algumas centenas de tokens costuma ser o limiar de detecção. Os dois botões trocam diretamente entre si: gamma controla a fração de vocabulário marcada, delta controla a força do viés. Aumentar delta detecta com menos tokens, mas degrada a qualidade, porque a amostragem passa a ser guiada por hash e não pelas preferências do modelo. O ponto fraco é direto: como o sinal vive em escolhas exatas de token, qualquer coisa que reescreva tokens o ataca.

Família 2 — Marcas pós-edição e payloads de largura zero

Se você não toca no amostrador, o texto é seu único canal. Marcas pós-edição fazem uma passada de reescrita no texto pronto e guardam bits nas escolhas de reescrita. Os esquemas de sinônimo trocam palavras selecionadas por equivalentes com chave — o bit é qual sinônimo sobreviveu. Esquemas sintáticos (como o EXPEDITO) codificam a mensagem na estrutura da frase, então apagar palavras individuais não apaga os bits.

O truque mais simples — e mais frágil — é o payload de largura zero: esconder bits em caracteres Unicode invisíveis.

def zw_write(text, message):
    # cada byte vira bits; 0 vira ZERO WIDTH SPACE, 1 vira ZERO WIDTH NON-JOINER
    bits = "".join(format(b, "08b") for b in message.encode())
    payload = "".join("\u200b" if b == "0" else "\u200c" for b in bits)
    i = text.find(" ")
    return text[:i] + payload + text[i:] if i > 0 else text + payload

def zw_read(text):
    return "".join("0" if c == "\u200b" else "1"
                   for c in text if c in "\u200b\u200c")

O que esse código não aguenta: uma passada de normalização Unicode (NFKC) ou qualquer filtro que remova caracteres de categoria “format” apaga o payload inteiro. Muitas plataformas sanitizam ao colar, e leitores de tela enxergam ruído que você não vê mais. Trate marcas de largura zero como rastreio de vazamento para cópias intactas, não como prova de autoria.

Família 3 — Marcas invariantes semânticas (sobrevivem a paráfrase)

A paráfrase é o ataque que decide tudo. A abordagem SIR (semantic invariant robust) move o sinal dos tokens de superfície para escolhas entre entidades semanticamente equivalentes. Para cada posição do texto existe um conjunto canônico de formas intercambiáveis: um cargo pode ser “o CEO” ou “o diretor-executivo”; um plano pode ser “forte” ou “sólido”. Uma permutação com chave decide qual forma codifica 0 e qual codifica 1.

import hashlib

SLOTS = {"quality": ["strong", "solid"],   # só variantes intercambiáveis
         "cost":    ["cheap", "low-cost"]}

def order_for(key, slot):
    h = int(hashlib.sha256(f"{key}|{slot}".encode()).hexdigest(), 16)
    return sorted(SLOTS[slot]), h % 2

def encode(text, bits, key):
    for (slot, _), bit in zip(SLOTS.items(), bits):
        order, off = order_for(key, slot)
        text = text.replace(f"<{slot}>", order[off ^ bit])
    return text

def decode(text, key):
    out = []
    for slot in SLOTS:
        order, off = order_for(key, slot)
        idx = next((i for i, v in enumerate(order) if v in text), None)
        out.append(None if idx is None else idx ^ off)
    return out

Uma paráfrase reescreve o tecido conectivo do texto, mas os fatos persistem — e o bit vive nos fatos. O custo é real: a geração precisa de uma etapa extra (frequentemente uma chamada a um LLM) para produzir variantes que de fato caibam no contexto, e a verificação precisa mapear o texto de volta às entidades canônicas. Equivalência é uma suposição: duas variantes que você declarou intercambiáveis podem não ser, em contexto. O que você compra é a propriedade que nenhuma outra família oferece — resistência ao ataque que quebra as demais.

Um harness de robustez executável

O harness abaixo define três ataques e mede a sobrevivência de uma marca antes e depois de cada um. Sem API, sem chaves.

import re, random, unicodedata

def attack_copy_paste(t):
    t = unicodedata.normalize("NFKC", t)     # o assassino dos bits zero-width
    return re.sub(r"\s+", " ", t).strip()

def attack_edits(t, rate=0.15, seed=7):
    rng, swap = random.Random(seed), {"strong": "robust",
                                      "cheap": "frugal", "quick": "swift"}
    return " ".join(swap.get(w, w) if rng.random() < rate else w
                    for w in t.split())

def attack_paraphrase(t):
    # substituto por regras; troque por um parafraseador LLM real em teste sério
    rules = [("a strong and cheap plan", "the plan was strong yet cheap")]
    for a, b in rules:
        t = t.replace(a, b)
    return t

def survival(text, mark, verify, attacks):
    marked = mark(text)
    print(f"{'baseline':<10} {verify(marked)}")
    for name, fn in attacks.items():
        print(f"{name:<10} {verify(fn(marked))}")

Duas ressalvas mantêm os resultados honestos: o ataque de copiar-colar já inclui normalização NFKC, porque é isso que pipelines reais fazem com texto colado; e o substituto de paráfrase é deliberadamente fraco — trate o resultado como teto de sobrevivência e plugue um parafraseador real antes de confiar uma decisão de implantação ao harness.

O que os testes realmente mostram

FamíliaCópia/colarEdições de palavraParáfrase
Lista verde estatísticaSobrevive (z cai)Declínio gradual com a taxa de ediçãoPode tender ao acaso
Payload zero-widthSó em canais não sanitizadosSobreviveApagado por normalização
Sinônimo pós-ediçãoSobreviveBits erodem por trocaQuase todo perdido
Sintático pós-ediçãoSobreviveSegura melhor que sinônimoDegrada
Invariante semânticaSobreviveSobrevive se variantes mantidasSegura enquanto os fatos sobrevivem
Matriz qualitativa de sobrevivência reproduzida pelo harness.

Três leituras importam. Primeiro, marcas estatísticas degradam com graça: cada token sobrevivente continua votando verde ou vermelho, então o z cai junto com a contagem de tokens, e edições leves mantêm a detecção de pé. Segundo, paráfrase forte é outra história — trabalho de Sadasivan et al. (2023) mostra pressão de paráfrase degradando acentuadamente tanto a confiabilidade do detector quanto a detectabilidade da marca, às vezes rumo ao acaso. Terceiro, a escolha certa é sempre relativa à ameaça: pergunte primeiro qual ataque você precisa sobreviver, depois escolha a família.

Casos de uso reais

  • Plataforma de conteúdo: rotule textos gerados para o usuário final e consiga provar a origem em disputas de moderação.
  • Detecção de vazamento: distribua versões com marca zero-width diferente por cliente e descubra quem copiou para fora.
  • Integridade acadêmica: uma instituição marca a saída dos seus próprios assistentes para auditar uso em trabalhos.
  • Conformidade regulatória: atenda exigências de transparência (ex.: AI Act europeu) com prova verificável em vez de declaração.
  • Treinamento de modelos: marque dados sintéticos para evitar contaminação por conteúdo gerado em corridas futuras.

Comparação de custo

AbordagemCusto de implantaçãoCusto por verificaçãoDependências
Lista verde (local)Zero (stdlib)Desprezível (hash)Nenhuma
Zero-width (local)Zero (stdlib)DesprezívelNenhuma
Invariante semânticaChamadas a LLM (geração/verificação)Baixo a médioAPI de LLM
Detecção post-hoc (terceiros)Assinatura mensalPor documentoServiço externo
Custo em reais tende a zero nas famílias locais; a família semântica exige LLM na malha.

Troubleshooting — erros comuns

  • Detecção cai a zero após colar no editor → Causa: normalização NFKC apagou os bits zero-width. Solução: use lista verde ou invariante semântica para canais que sanitizam.
  • z baixo mesmo com texto marcado → Causa: poucos tokens sobreviventes ou gamma mal calibrado. Solução: aumente a amostra ou reduza gamma; confira o delta.
  • Qualidade do texto degrada com a marca → Causa: delta alto demais. Solução: baixe delta e aceite precisar de mais tokens para detectar.
  • Bits de sinônimo somem após edição manual → Causa: cada troca de palavra sobrescreve um bit. Solução: migre para esquema sintático ou invariante semântica.
  • Verificação da SIR retorna None → Causa: variante canônica não encontrada após reescrita agressiva. Solução: use um LLM para mapear o texto reescrito de volta às entidades.

FAQ

  • Preciso de uma GPU para testar? Não. As três famílias e o harness rodam em Python puro; GPU só ajuda se você plugar um parafraseador LLM real.
  • Qual família escolher para produção? Depende da ameaça: sem reescrita, lista verde; canais não sanitizados, zero-width; sob paráfrase, invariante semântica.
  • Marca d'água substitui detector de IA? Não. Ela é prova de origem para texto que você marcou; não detecta texto de terceiros.
  • Um parafraseador forte destrói tudo? A família semântica resiste enquanto os fatos sobrevivem; as demais tendem a cair.
  • Posso marcar texto já publicado? Só com pós-edição (sinônimo, sintático ou zero-width); a lista verde exige controle da geração.

Para onde isso vai

Em 2027, espere marcas d'água se tornarem parte do padrão de saída dos grandes modelos — não como opção, mas como configuração padrão de transparência —, impulsionadas por regulação e pela demanda de plataformas por proveniência verificável. A fronteira de pesquisa está em tornar a marca robusta a parafraseadores cada vez melhores, e em fechar a lacuna entre marcar e provar para um auditor externo sem revelar a chave.


Descubra mais sobre noticiAI

Assine para receber nossas notícias mais recentes por e-mail.

R
Sobre o autorRedação Noticiai

Equipe editorial dedicada a explicar inteligência artificial com clareza, independência e contexto.