Assinaturas Digitais e Não-Repúdio: PGP, S/MIME e Code Signing na Prática Já leu

Fundamentos de Assinaturas Digitais e Não-Repúdio Assinaturas digitais são mecanismos criptográficos que garantem autenticidade, integridade e não-repúdio de mensagens e documentos. Diferentemente de uma assinatura manuscrita, uma assinatura digital é matematicamente vinculada ao conteúdo do documento e à identidade do signatário, tornando impossível falsificá-la ou negá-la posteriormente. O não-repúdio é a propriedade que impede que o signatário negue ter assinado um documento. Quando você assina digitalmente algo com sua chave privada, cria uma evidência criptográfica irrefutável de sua participação. Isso é fundamental em transações comerciais, contratos eletrônicos e comunicações seguras. O algoritmo mais comum para assinaturas digitais é o RSA (Rivest-Shamir-Adleman) combinado com funções hash como SHA-256. O processo funciona em duas etapas: primeiro, um resumo criptográfico (hash) do documento é criado; depois, esse hash é criptografado com a chave privada do signatário, gerando a assinatura. Para verificar, o receptor descriptografa a assinatura com a chave pública do signatário e compara o hash resultante com o hash do documento recebido.

Fundamentos de Assinaturas Digitais e Não-Repúdio

Assinaturas digitais são mecanismos criptográficos que garantem autenticidade, integridade e não-repúdio de mensagens e documentos. Diferentemente de uma assinatura manuscrita, uma assinatura digital é matematicamente vinculada ao conteúdo do documento e à identidade do signatário, tornando impossível falsificá-la ou negá-la posteriormente.

O não-repúdio é a propriedade que impede que o signatário negue ter assinado um documento. Quando você assina digitalmente algo com sua chave privada, cria uma evidência criptográfica irrefutável de sua participação. Isso é fundamental em transações comerciais, contratos eletrônicos e comunicações seguras. O algoritmo mais comum para assinaturas digitais é o RSA (Rivest-Shamir-Adleman) combinado com funções hash como SHA-256.

O processo funciona em duas etapas: primeiro, um resumo criptográfico (hash) do documento é criado; depois, esse hash é criptografado com a chave privada do signatário, gerando a assinatura. Para verificar, o receptor descriptografa a assinatura com a chave pública do signatário e compara o hash resultante com o hash do documento recebido. Se forem idênticos, a assinatura é válida.

PGP (Pretty Good Privacy) e Aplicações Práticas

PGP é um programa de criptografia que oferece privacidade criptográfica e autenticação para comunicação por email e armazenamento de dados. Desenvolvido por Phil Zimmermann em 1991, utiliza um modelo de confiança descentralizado baseado em "rede de confiança" (Web of Trust) em vez de autoridades certificadoras centralizadas como ocorre com X.509.

PGP combina criptografia de chave pública (RSA), criptografia simétrica (3DES, IDEA) e funções hash (MD5, SHA-1) em um sistema híbrido robusto. Cada usuário gera um par de chaves: uma chave pública (compartilhada) e uma chave privada (mantida em segredo). Ao assinar uma mensagem, PGP usa sua chave privada para gerar uma assinatura que qualquer pessoa pode verificar com sua chave pública.

Vamos criar um exemplo prático usando a biblioteca python-gnupg, que fornece uma interface Python para GPG (GNU Privacy Guard), a implementação open-source de PGP:

import gnupg
import os

# Inicializar GPG
gpg = gnupg.GPG()

# Gerar um par de chaves (pode levar alguns minutos)
input_data = gpg.gen_key_input(
    key_type='RSA',
    key_length=2048,
    name_email='usuario@exemplo.com',
    name_real='João Silva',
    passphrase='senha_super_secreta'
)
key = gpg.gen_key(input_data)

print(f"Chave gerada com ID: {key}")

# Listar chaves disponíveis
chaves_publicas = gpg.list_keys()
chaves_privadas = gpg.list_keys(secret=True)

print(f"\nChaves públicas:\n{chaves_publicas}")
print(f"\nChaves privadas:\n{chaves_privadas}")

# Assinar uma mensagem
mensagem = "Este é um documento importante que precisa ser assinado."
chave_id = key.fingerprint

signed_data = gpg.sign(
    mensagem,
    keyid=chave_id,
    passphrase='senha_super_secreta'
)

print(f"\nMensagem assinada:\n{signed_data}")

# Verificar a assinatura
verification = gpg.verify(str(signed_data))

print(f"\nVerificação da assinatura:")
print(f"Assinatura válida: {verification.valid}")
print(f"Fingerprint da chave: {verification.fingerprint}")
print(f"Nome do signatário: {verification.username}")

# Exportar chave pública (para compartilhar com outros)
chave_publica_armored = gpg.export_keys(key.fingerprint)
print(f"\nChave pública em formato ASCII:\n{chave_publica_armored[:200]}...")

# Importar chave pública de terceiro (exemplo prático)
# chave_alheia_text = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n..."
# import_result = gpg.import_keys(chave_alheia_text)
# print(f"Chave importada: {import_result}")

Nota importante: A confiança em PGP não é automática. Você precisa assinar as chaves públicas de outras pessoas manualmente para confirmar que confia na relação entre a identidade e a chave pública. Essa rede de assinaturas forma a "Web of Trust".

S/MIME (Secure/Multipurpose Internet Mail Extensions)

S/MIME é um padrão industrial para criptografia e assinatura de emails que se integra nativamente aos clientes de correio eletrônico. Diferentemente de PGP, S/MIME utiliza um modelo de confiança baseado em Autoridades Certificadoras (CAs), similar ao HTTPS. Cada usuário possui um certificado X.509 emitido por uma CA confiável, que vincula sua chave pública à sua identidade de forma criptograficamente verificável.

A grande vantagem do S/MIME é sua integração transparente com aplicações comuns como Outlook, Apple Mail e Thunderbird. Ao recepcionar um email assinado com S/MIME, o cliente de email automaticamente verifica a assinatura e exibe um indicador visual. O certificado contém não apenas a chave pública, mas também informações de identidade verificadas pela CA, incluindo nome, organização e data de expiração.

Vamos implementar um exemplo de assinatura e verificação S/MIME usando Python com a biblioteca m2crypto:

from M2Crypto import BIO, SMIME, X509
import os

# Criar instância SMIME
s = SMIME.SMIME()

# Para este exemplo, usaremos certificados auto-assinados (apenas para demonstração)
# Em produção, você obteria certificados de uma CA confiável

# Carregar certificado e chave privada
# Assumindo que você já possui: certificado.pem e chave_privada.pem
try:
    s.load_key('chave_privada.pem', 'certificado.pem')
except:
    print("Certificados não encontrados. Criando certificados de teste...")
    # Para fim de demonstração educacional, mostraremos a estrutura

# Preparar a mensagem
msg_text = """Esta é uma mensagem comercial que será assinada digitalmente.
Data: 2024
Valor: R$ 10.000,00

Assinado digitalmente conforme Lei 14.063/2020."""

msg_bio = BIO.MemoryBuffer(msg_text.encode('utf-8'))

# Assinar a mensagem em modo "clear-signing" (texto visível + assinatura)
s.sign(SMIME.Flags.PKCS7_DETACHED)
p7 = s.sign(msg_bio)

# Converter para formato padrão SMIME
msg_bio = BIO.MemoryBuffer(msg_text.encode('utf-8'))
out = BIO.MemoryBuffer()
s.write(out, p7, msg_bio)

mensagem_assinada = out.as_string()
print("Mensagem assinada (primeiras 500 caracteres):")
print(mensagem_assinada[:500])

# VERIFICAÇÃO: Carregar apenas a chave pública do certificado
x509 = X509.load_cert('certificado.pem')
sk = X509.X509_Stack()
sk.push(x509)
s.set_x509_stack(sk)

# Estabelecer loja de certificados
s.set_cert_trust_dir('/etc/ssl/certs')  # Caminho padrão de CAs confiáveis

# Verificar a assinatura
msg_bio = BIO.MemoryBuffer(mensagem_assinada.encode('utf-8'))
try:
    v = s.verify(msg_bio)
    print("\n✓ Assinatura verificada com sucesso!")
    print(f"Certificado do signatário: {x509.get_subject().as_text()}")
except SMIME.SMIME_Error as e:
    print(f"\n✗ Falha na verificação: {e}")

Na prática, para obter um certificado S/MIME legítimo, você precisa: (1) gerar um par de chaves; (2) submeter uma solicitação de certificado assinado (CSR) a uma CA; (3) a CA verifica sua identidade e emite o certificado; (4) você instala o certificado no seu cliente de email. Empresas como DigiCert, GlobalSign e Certisign oferecem certificados S/MIME comerciais. Navegadores web e clientes de email modernos vêm pré-carregados com as CAs raiz confiáveis, permitindo verificação automática.

Code Signing e Integridade de Aplicações

Code Signing é o processo de assinar executáveis, bibliotecas e scripts com uma assinatura digital para garantir sua origem e que não foram alterados. Desenvolvedores e distribuidoras de software usam code signing para proteger seus usuários contra malware e software falsificado. Quando um usuário baixa um programa assinado, o sistema operacional pode verificar automaticamente se é confiável.

Windows, macOS e Linux todos implementam mecanismos de code signing. No Windows, executáveis são assinados com certificados Authenticode. No macOS, aplicativos são assinados com Developer ID Certificates. Em ambos os casos, se a assinatura for inválida ou falsificada, o sistema opera em "modo restrito" ou bloqueia a execução completamente. Isso protege o ecossistema de software contra cadeia de suprimento comprometida.

Vamos demonstrar code signing em Python usando a biblioteca cryptography para assinar arquivos:

from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.backends import default_backend
import os

# ============= GERAÇÃO DE CHAVES =============
# Em produção, essas chaves seriam armazenadas com segurança em HSM ou cofre

private_key = rsa.generate_private_key(
    public_exponent=65537,
    key_size=2048,
    backend=default_backend()
)

public_key = private_key.public_key()

# Serializar e salvar chaves
private_pem = private_key.private_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PrivateFormat.PKCS8,
    encryption_algorithm=serialization.NoEncryption()
)

public_pem = public_key.public_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PublicFormat.SubjectPublicKeyInfo
)

# ============= ASSINATURA DE ARQUIVO =============

def assinar_arquivo(caminho_arquivo, chave_privada):
    """Assina um arquivo e retorna a assinatura em hexadecimal"""

    # Ler conteúdo do arquivo
    with open(caminho_arquivo, 'rb') as f:
        dados = f.read()

    # Gerar assinatura usando RSA-SHA256
    assinatura = chave_privada.sign(
        dados,
        padding.PSS(
            mgp=padding.MGF1(hashes.SHA256()),
            salt_length=padding.PSS.MAX_LENGTH
        ),
        hashes.SHA256()
    )

    return assinatura.hex()

def verificar_assinatura(caminho_arquivo, assinatura_hex, chave_publica):
    """Verifica se a assinatura é válida para um arquivo"""

    # Ler conteúdo do arquivo
    with open(caminho_arquivo, 'rb') as f:
        dados = f.read()

    assinatura = bytes.fromhex(assinatura_hex)

    try:
        chave_publica.verify(
            assinatura,
            dados,
            padding.PSS(
                mgp=padding.MGF1(hashes.SHA256()),
                salt_length=padding.PSS.MAX_LENGTH
            ),
            hashes.SHA256()
        )
        return True
    except Exception:
        return False

# ============= EXEMPLO PRÁTICO =============

# Criar arquivo de teste
arquivo_teste = '/tmp/aplicativo.exe'
with open(arquivo_teste, 'wb') as f:
    f.write(b'MZ\x90\x00...')  # Simulando um executável

# Assinar o arquivo
assinatura = assinar_arquivo(arquivo_teste, private_key)
print(f"Assinatura gerada:\n{assinatura}\n")

# Salvar assinatura em arquivo separado (padrão comum)
with open(arquivo_teste + '.sig', 'w') as f:
    f.write(assinatura)

# Verificar a assinatura
if verificar_assinatura(arquivo_teste, assinatura, public_key):
    print("✓ Assinatura válida - arquivo não foi alterado")
else:
    print("✗ Assinatura inválida - arquivo pode estar comprometido")

# Demonstrar falsificação: alterar o arquivo
with open(arquivo_teste, 'ab') as f:
    f.write(b'\x00MALWARE')

# Tentar verificar novamente
if verificar_assinatura(arquivo_teste, assinatura, public_key):
    print("✓ Assinatura válida")
else:
    print("✗ Assinatura inválida - arquivo foi modificado!")

Code signing é essencial em cenários críticos: (1) Distribuição de software: empresas como Microsoft e Apple garantem que os executáveis que chegam ao usuário são genuínos; (2) Blockchain e contratos inteligentes: assinaturas digitais autenticam transações; (3) Patches de segurança: updates assinados garantem que vieram do fabricante autorizado e não são malware disfarçado.

Conclusão

Ao longo desta aula, cobrimos três pilares da segurança digital moderna: PGP oferece descentralização e flexibilidade através da Web of Trust, tornando-o ideal para comunicações peer-to-peer; S/MIME padroniza a assinatura de emails através de certificados X.509 gerenciados por autoridades certificadoras, integrando-se perfeitamente aos clientes de correio; code signing protege a integridade de software durante sua distribuição, criando uma cadeia de confiança entre desenvolvedor e usuário final. Cada solução responde a um contexto específico — escolher corretamente entre elas é fundamental para arquitetar sistemas seguros e conformes às regulamentações como Lei 14.063/2020 (assinatura eletrônica no Brasil) e GDPR.

Referências

  • https://www.rfc-editor.org/rfc/rfc4880.html — RFC 4880: OpenPGP Message Format (especificação oficial do PGP)
  • https://www.rfc-editor.org/rfc/rfc5751.html — RFC 5751: S/MIME 3.2 (especificação técnica S/MIME)
  • https://gnupg.org/documentation/ — Documentação oficial GNU Privacy Guard
  • https://docs.microsoft.com/en-us/windows/win32/seccrypto/cryptography-portal — Microsoft: Code Signing e Authenticode
  • https://cryptography.io/en/latest/ — Cryptography.io: Library de criptografia em Python

Artigos relacionados