Por que mascarar dados?
Com a consolidação da LGPD (Lei Geral de Proteção de Dados) e
a crescente preocupação com privacidade, um desafio muito comum em projetos de
banco de dados é: como usar dados reais para testes, homologação ou treinamento
sem expor informações sensíveis dos usuários?
A resposta é o mascaramento de dados técnica que substitui
valores sensíveis por dados fictícios, mas estruturalmente válidos, tornando o
ambiente seguro sem perder a representatividade dos dados para os fins
desejados.
Neste artigo vou mostrar como implementamos uma solução de
mascaramento de dados no Oracle Database de forma organizada, rastreável e
reutilizável. Vou usar um sistema fictício chamado APP_OWNER como exemplo ao
longo do tutorial.
💡 Esta abordagem foi desenvolvida e aplicada em um ambiente
Oracle real. Os nomes de schemas, tabelas e colunas foram substituídos por
exemplos genéricos para fins didáticos.
Visão Geral da Estratégia
A solução é composta por quatro pilares:
•
Um schema dedicado ao controle do mascaramento
•
Uma tabela de controle que registra quais colunas devem
ser mascaradas e o status de cada uma
•
Uma função PL/SQL de mascaramento de texto
•
Uma procedure que lê a tabela de controle e executa o
mascaramento automaticamente
Essa separação de responsabilidades permite que o processo
seja auditável, repetível e fácil de expandir para novas tabelas.
Passo 1 — Criar o Schema de Controle
O primeiro passo é criar um schema (usuário Oracle) dedicado
exclusivamente ao processo de mascaramento. Isso evita poluir o schema da
aplicação com objetos de controle e permite gerenciar permissões de forma
isolada.
-- Criação do schema de controle
CREATE USER MASC_CTRL
IDENTIFIED BY
"P@ssw0rd_Exemplo#99"
DEFAULT TABLESPACE
USERS
TEMPORARY TABLESPACE
TEMP
QUOTA UNLIMITED ON
USERS;
Em seguida, concedemos os privilégios mínimos necessários para
que este schema possa operar:
-- Privilégios básicos do schema de controle
GRANT CREATE SESSION TO
MASC_CTRL;
GRANT CREATE TABLE TO
MASC_CTRL;
GRANT CREATE PROCEDURE TO MASC_CTRL;
GRANT CREATE SEQUENCE TO
MASC_CTRL;
GRANT CREATE VIEW TO
MASC_CTRL;
💡 Seguindo o princípio do menor privilégio, concedemos
apenas o que é estritamente necessário. Não utilize GRANT DBA em ambientes de
produção para este schema.
Passo 2 — Conceder Acesso às Tabelas Alvo
O schema de controle precisa de permissão de SELECT e UPDATE
nas tabelas do sistema que serão mascaradas. Fazemos isso de forma granular,
tabela a tabela:
-- GRANTs nas tabelas do sistema de origem
GRANT SELECT, UPDATE ON APP_OWNER.TB_PESSOA TO MASC_CTRL;
GRANT SELECT, UPDATE ON APP_OWNER.TB_LOCALIZACAO TO MASC_CTRL;
GRANT SELECT, UPDATE ON APP_OWNER.TB_USUARIO TO MASC_CTRL;
GRANT SELECT, UPDATE ON APP_OWNER.TB_REGISTRO TO MASC_CTRL;
GRANT SELECT, UPDATE ON APP_OWNER.TB_HISTORICO TO MASC_CTRL;
-- ... demais tabelas conforme necessidade
Neste projeto, identificamos as tabelas que continham dados
sensíveis pessoais (nomes, CPF, e-mail, endereços, documentos) e cobrimos todas
elas com esses grants.
Passo 3 — Tabela de Controle do Mascaramento
Esta é a peça central da solução. A tabela de controle
registra cada coluna que deve ser mascarada, o tipo de dado dela e o status de
execução:
CREATE TABLE MASC_CTRL.CONTROLE_MASCARAMENTO (
ID_CONTROLE NUMBER GENERATED BY DEFAULT AS IDENTITY
PRIMARY KEY,
NOME_SCHEMA VARCHAR2(50) NOT NULL,
TABELA VARCHAR2(100) NOT NULL,
COLUNA VARCHAR2(100) NOT NULL,
TIPO_DADO VARCHAR2(20) NOT NULL,
MASCARADO CHAR(1) DEFAULT 'N' NOT NULL,
DATA_EXEC DATE,
LINHAS_ALTERADAS NUMBER,
STATUS_EXEC VARCHAR2(20),
MENSAGEM_ERRO VARCHAR2(4000),
USUARIO_EXEC VARCHAR2(50)
);
-- Constraint para garantir valores válidos na flag
ALTER TABLE MASC_CTRL.CONTROLE_MASCARAMENTO
ADD CONSTRAINT
CK_CTRL_MASC_01
CHECK (MASCARADO IN
('S','N'));
-- Índice único: não permite duplicidade de schema+tabela+coluna
CREATE UNIQUE INDEX MASC_CTRL.UK_CTRL_MASC_01
ON
MASC_CTRL.CONTROLE_MASCARAMENTO (NOME_SCHEMA, TABELA, COLUNA);
Cada registro nesta tabela representa uma coluna que será ou
já foi mascarada. O campo MASCARADO ('S'/'N') funciona como flag de controle,
e os campos de auditoria registram quando, por quem e com qual resultado o
mascaramento foi executado.
Passo 4 — Função de Mascaramento de Texto
A função FN_MASCARA_TEXTO recebe um valor de texto e retorna
uma versão mascarada palavra por palavra, preservando a estrutura do dado
(comprimento, espaços) mas substituindo caracteres por asteriscos.
A lógica de mascaramento por tamanho de palavra funciona
assim:
•
1 caractere → '*'
•
2 caracteres → primeira letra + '*'
•
3 caracteres → primeira letra + '**'
•
4 caracteres → primeira + '**' + última
•
5+ caracteres → primeira, asteriscos no meio,
antepenúltima visível, demais mascarados
CREATE OR REPLACE FUNCTION MASC_CTRL.FN_MASCARA_TEXTO (
P_TEXTO IN VARCHAR2
) RETURN VARCHAR2
IS
V_RESULTADO VARCHAR2(4000) := '';
V_PALAVRA VARCHAR2(4000);
V_MASCARADA VARCHAR2(4000);
V_TAM NUMBER;
BEGIN
IF P_TEXTO IS NULL
THEN
RETURN NULL;
END IF;
FOR I IN 1 ..
REGEXP_COUNT(P_TEXTO, '[^ ]+') LOOP
V_PALAVRA := REGEXP_SUBSTR(P_TEXTO, '[^ ]+', 1, I);
V_TAM := LENGTH(V_PALAVRA);
V_MASCARADA := '';
IF V_TAM = 1 THEN
V_MASCARADA :=
'*';
ELSIF V_TAM = 2
THEN
V_MASCARADA :=
SUBSTR(V_PALAVRA, 1, 1) || '*';
ELSIF V_TAM = 3
THEN
V_MASCARADA := SUBSTR(V_PALAVRA, 1, 1) ||
'**';
ELSIF V_TAM = 4
THEN
V_MASCARADA :=
SUBSTR(V_PALAVRA, 1, 1) || '**' || SUBSTR(V_PALAVRA, 4, 1);
ELSE
FOR J IN 1 ..
V_TAM LOOP
IF J = 1
THEN
V_MASCARADA := V_MASCARADA || SUBSTR(V_PALAVRA, J, 1);
ELSIF J =
V_TAM - 2 THEN
V_MASCARADA := V_MASCARADA || SUBSTR(V_PALAVRA, J, 1);
ELSE
V_MASCARADA := V_MASCARADA || '*';
END IF;
END LOOP;
END IF;
IF I > 1 THEN
V_RESULTADO :=
V_RESULTADO || ' ';
END IF;
V_RESULTADO :=
V_RESULTADO || V_MASCARADA;
END LOOP;
RETURN V_RESULTADO;
END FN_MASCARA_TEXTO;
/
Exemplo de resultado para o nome "Carlos Eduardo
Silva":
-- Entrada: 'Carlos Eduardo Silva'
-- Saída: 'C****s
E*****o S***a'
💡 A função preserva os espaços entre palavras e trata NULLs
com segurança, retornando NULL sem erro.
Passo 5 — Procedure de Execução Automatizada
A procedure PRC_EXECUTAR_MASCARAMENTO lê a tabela de controle,
filtra os registros ainda não mascarados e executa o UPDATE de forma dinâmica
via EXECUTE IMMEDIATE. Ela suporta filtros opcionais por schema, tabela e
coluna:
CREATE OR REPLACE PROCEDURE MASC_CTRL.PRC_EXECUTAR_MASCARAMENTO
(
P_SCHEMA IN VARCHAR2 DEFAULT NULL,
P_TABELA IN VARCHAR2 DEFAULT NULL,
P_COLUNA IN VARCHAR2 DEFAULT NULL
)
AS
V_SQL VARCHAR2(4000);
V_LINHAS NUMBER;
V_ERRO VARCHAR2(4000);
BEGIN
FOR R IN (
SELECT
ID_CONTROLE, NOME_SCHEMA, TABELA, COLUNA, TIPO_DADO
FROM
MASC_CTRL.CONTROLE_MASCARAMENTO
WHERE MASCARADO =
'N'
AND (P_SCHEMA
IS NULL OR NOME_SCHEMA = UPPER(P_SCHEMA))
AND (P_TABELA
IS NULL OR TABELA = UPPER(P_TABELA))
AND (P_COLUNA
IS NULL OR COLUNA = UPPER(P_COLUNA))
ORDER BY
NOME_SCHEMA, TABELA, COLUNA
) LOOP
BEGIN
-- Lógica por
tipo de dado
IF
UPPER(R.TIPO_DADO) IN ('VARCHAR', 'VARCHAR2', 'CHAR') THEN
V_SQL :=
'UPDATE ' || R.NOME_SCHEMA || '.' || R.TABELA
|| '
SET ' || R.COLUNA
|| ' = MASC_CTRL.FN_MASCARA_TEXTO('
|| R.COLUNA || ')'
|| '
WHERE ' || R.COLUNA || ' IS NOT NULL';
ELSIF
UPPER(R.TIPO_DADO) = 'NUMBER' THEN
V_SQL :=
'UPDATE ' || R.NOME_SCHEMA || '.' || R.TABELA
|| '
SET ' || R.COLUNA || ' = 0'
|| '
WHERE ' || R.COLUNA || ' IS NOT NULL';
ELSIF
UPPER(R.TIPO_DADO) = 'DATE' THEN
V_SQL :=
'UPDATE ' || R.NOME_SCHEMA || '.' || R.TABELA
|| ' SET ' || R.COLUNA || " =
DATE '1900-01-01'"
|| '
WHERE ' || R.COLUNA || ' IS NOT NULL';
ELSE
-- Tipo
não suportado: registra erro e continua
UPDATE
MASC_CTRL.CONTROLE_MASCARAMENTO
SET
STATUS_EXEC = 'ERRO',
MENSAGEM_ERRO = 'TIPO_DADO INVALIDO: ' || R.TIPO_DADO,
DATA_EXEC = SYSDATE, USUARIO_EXEC = USER
WHERE ID_CONTROLE = R.ID_CONTROLE;
COMMIT;
CONTINUE;
END IF;
EXECUTE
IMMEDIATE V_SQL;
V_LINHAS :=
SQL%ROWCOUNT;
-- Atualiza
controle como SUCESSO
UPDATE MASC_CTRL.CONTROLE_MASCARAMENTO
SET
MASCARADO = 'S', DATA_EXEC = SYSDATE,
LINHAS_ALTERADAS = V_LINHAS,
STATUS_EXEC = 'SUCESSO', MENSAGEM_ERRO = NULL,
USUARIO_EXEC = USER
WHERE ID_CONTROLE
= R.ID_CONTROLE;
COMMIT;
EXCEPTION
WHEN OTHERS
THEN
V_ERRO :=
SUBSTR(SQLERRM, 1, 4000);
UPDATE
MASC_CTRL.CONTROLE_MASCARAMENTO
SET
MASCARADO = 'N', STATUS_EXEC = 'ERRO',
MENSAGEM_ERRO = V_ERRO, DATA_EXEC = SYSDATE,
USUARIO_EXEC = USER
WHERE
ID_CONTROLE = R.ID_CONTROLE;
COMMIT;
END;
END LOOP;
END PRC_EXECUTAR_MASCARAMENTO;
/
O COMMIT por iteração garante que erros em uma coluna não
revertam o trabalho já feito nas anteriores. O bloco EXCEPTION captura qualquer
falha e registra a mensagem de erro diretamente na tabela de controle.
Passo 6 — Inserindo as Colunas na Tabela de
Controle
Antes de executar, é necessário registrar quais colunas devem
ser mascaradas. Abaixo um exemplo com algumas tabelas fictícias:
INSERT INTO MASC_CTRL.CONTROLE_MASCARAMENTO (NOME_SCHEMA,
TABELA, COLUNA, TIPO_DADO)
VALUES ('APP_OWNER', 'TB_PESSOA', 'NM_COMPLETO', 'VARCHAR');
INSERT INTO MASC_CTRL.CONTROLE_MASCARAMENTO (NOME_SCHEMA,
TABELA, COLUNA, TIPO_DADO)
VALUES ('APP_OWNER', 'TB_PESSOA', 'NR_DOCUMENTO', 'VARCHAR');
INSERT INTO MASC_CTRL.CONTROLE_MASCARAMENTO (NOME_SCHEMA,
TABELA, COLUNA, TIPO_DADO)
VALUES ('APP_OWNER', 'TB_PESSOA', 'DS_EMAIL', 'VARCHAR');
INSERT INTO MASC_CTRL.CONTROLE_MASCARAMENTO (NOME_SCHEMA,
TABELA, COLUNA, TIPO_DADO)
VALUES ('APP_OWNER', 'TB_LOCALIZACAO', 'DS_LOGRADOURO', 'VARCHAR');
INSERT INTO MASC_CTRL.CONTROLE_MASCARAMENTO (NOME_SCHEMA,
TABELA, COLUNA, TIPO_DADO)
VALUES ('APP_OWNER', 'TB_LOCALIZACAO', 'NR_CEP', 'VARCHAR');
INSERT INTO MASC_CTRL.CONTROLE_MASCARAMENTO (NOME_SCHEMA,
TABELA, COLUNA, TIPO_DADO)
VALUES ('APP_OWNER', 'TB_USUARIO', 'NR_MATRICULA', 'NUMBER');
INSERT INTO MASC_CTRL.CONTROLE_MASCARAMENTO (NOME_SCHEMA,
TABELA, COLUNA, TIPO_DADO)
VALUES ('APP_OWNER', 'TB_USUARIO', 'NM_GENITORA', 'VARCHAR');
INSERT INTO MASC_CTRL.CONTROLE_MASCARAMENTO (NOME_SCHEMA,
TABELA, COLUNA, TIPO_DADO)
VALUES ('APP_OWNER', 'TB_REGISTRO', 'NUMERO_TB_REGISTRO',
'VARCHAR');
COMMIT;
Visualizando o estado atual da tabela de controle antes da
execução:
|
Schema |
Tabela |
Coluna |
Tipo |
Mascarado |
|
APP_OWNER |
TB_PESSOA |
NM_COMPLETO |
VARCHAR2 |
N |
|
APP_OWNER |
TB_PESSOA |
NR_DOCUMENTO |
VARCHAR2 |
N |
|
APP_OWNER |
TB_PESSOA |
DS_EMAIL |
VARCHAR2 |
N |
|
APP_OWNER |
TB_LOCALIZACAO |
DS_LOGRADOURO |
VARCHAR2 |
N |
|
APP_OWNER |
TB_LOCALIZACAO |
NR_CEP |
VARCHAR2 |
N |
|
APP_OWNER |
TB_USUARIO |
NR_MATRICULA |
NUMBER |
N |
|
APP_OWNER |
TB_USUARIO |
NM_GENITORA |
VARCHAR2 |
N |
|
APP_OWNER |
TB_REGISTRO |
NUMERO_TB_REGISTRO |
VARCHAR2 |
N |
|
APP_OWNER |
TB_REGISTRO |
DS_OBSERVACAO |
VARCHAR2 |
N |
Passo 7 — Executando o Mascaramento
Com tudo preparado, basta chamar a procedure. Ela oferece três
modos de execução:
Mascarar
tudo de uma vez
BEGIN
MASC_CTRL.PRC_EXECUTAR_MASCARAMENTO;
END;
/
Mascarar
um schema inteiro
BEGIN
MASC_CTRL.PRC_EXECUTAR_MASCARAMENTO('APP_OWNER');
END;
/
Mascarar
uma tabela específica
BEGIN
MASC_CTRL.PRC_EXECUTAR_MASCARAMENTO(
P_SCHEMA =>
'APP_OWNER',
P_TABELA =>
'TB_PESSOA'
);
END;
/
Mascarar
uma coluna específica
BEGIN
MASC_CTRL.PRC_EXECUTAR_MASCARAMENTO(
P_SCHEMA =>
'APP_OWNER',
P_TABELA =>
'TB_USUARIO',
P_COLUNA =>
'NR_MATRICULA'
);
END;
/
Passo 8 — Verificando o Resultado
Após a execução, consulte a tabela de controle para confirmar
o sucesso e verificar possíveis erros:
-- Visão geral do processo
SELECT
NOME_SCHEMA,
TABELA,
COLUNA,
TIPO_DADO,
MASCARADO,
DATA_EXEC,
LINHAS_ALTERADAS,
STATUS_EXEC,
MENSAGEM_ERRO,
USUARIO_EXEC
FROM MASC_CTRL.CONTROLE_MASCARAMENTO
ORDER BY TABELA, COLUNA;
-- Filtrar apenas erros
SELECT * FROM MASC_CTRL.CONTROLE_MASCARAMENTO
WHERE STATUS_EXEC = 'ERRO';
Considerações Finais
Esta abordagem oferece algumas vantagens importantes em
relação a soluções ad-hoc:
•
Rastreabilidade completa: cada coluna mascarada fica
registrada com data, usuário e quantidade de linhas afetadas
•
Resiliência a erros: falhas em uma coluna não afetam o
processamento das demais
•
Flexibilidade de execução: é possível executar em lote
ou coluna a coluna, útil para bases muito grandes
•
Facilidade de extensão: basta inserir novos registros
na tabela de controle para cobrir novas tabelas
•
Auditoria de conformidade: o registro histórico
facilita comprovação de adequação à LGPD
Vale lembrar que esta solução realiza mascaramento destrutivo os dados originais são sobrescritos. Certifique-se sempre de que está
operando no ambiente correto (homologação/teste) e nunca em produção com dados
reais que precisem ser preservados.
💡 Recomendo fortemente criar um backup do banco antes de
executar o processo, especialmente na primeira vez. Use RMAN ou Data Pump para
garantir uma cópia segura dos dados originais.
Gostou do artigo? Deixe um comentário com sua dúvida ou
experiência. Até o próximo post! 🚀
