Hola, chicos,
¿Todo muy bien?
Introducción
En este post me gustaría demostrar una característica muy interesante de SQL Server, disponible a partir de la versión 2016 en adelante, que es el Enmascaramiento Dinámico de Datos (DDM) y que nos permite enmascarar y ocultar información sensible de determinados usuarios de una forma rápida y práctica.
A diferencia de las funciones de cifrado de SQL Server Transparent Data Encryption (TDE) y Always Encrypted, que cifran físicamente los datos y archivos de la base de datos (incluso las copias de seguridad y las copias de seguridad de registros se generan ya cifradas, y los archivos MDF, LDF y NDF también están cifrados), el enmascaramiento de datos Dynamic Data Masking en realidad no cifra los datos, solo aplica una máscara a las consultas realizadas en la base de datos para usuarios sin privilegios.
Para aplicar el enmascaramiento de datos, debe ejecutar un comando ALTER en la columna a la que desea aplicar la máscara (el enmascaramiento de datos es a nivel de columna) o puede agregar esta definición en CREATE de la tabla.
Limitaciones y restricciones
No puede definir una regla de enmascaramiento para los siguientes tipos de columnas:
- Columnas cifradas (siempre cifradas)
- CORRIENTE DE ARCHIVOS
- No se puede establecer una máscara en una columna calculada, pero si la columna calculada depende de una columna con una MÁSCARA, devolverá datos enmascarados.
- Una columna con datos enmascarados no puede ser una clave para un índice FULLTEXT.
tipos de mascaras
En el enmascaramiento dinámico de datos, puede definir cómo se enmascararán los datos utilizando algunas funciones de enmascaramiento de datos, que se detallarán a continuación:
| Función | Descripción | Ejemplos |
|---|---|---|
| Por defecto | Enmascaramiento según los tipos de datos de los campos designados. Para los tipos de datos de cadena, los valores originales se reemplazarán con caracteres XXXX. Es aplicable a tipos de datos. carbonizarse, nchar, varchar, nvarchar, texto y ntexto Para tipos de datos numéricos, el valor original se reemplazará con el número 0 (cero). Es aplicable a tipos de datos. bigint, poco, decimal, entero, dinero, numérico, pequeño, poco dinero, diminuto, flotar y real. Para los tipos de datos de fecha y hora, se utilizará la fecha 1900-01-01 00:00:00.0000000. Es aplicable a tipos de datos. fecha, fechahora2, fecha y hora, compensación de fecha y hora, pequeña fecha y hora y equipo. Para tipos de datos binarios, el valor original se reemplazará con un solo byte de valor ASCII 0. Es aplicable a tipos de datos. binario, varbinario y imagen. | ALTER TABLE dbo.DDM ALTER COLUMN Nombre AGREGAR ENMASCARADO CON(FUNCTION = 'default()') |
| Correo electrónico | El método de enmascaramiento que expone la primera letra de una dirección de correo electrónico y el sufijo constante ".com" en el formato de una dirección de correo electrónico. Ex: [correo electrónico protegido] estará enmascarado como [correo electrónico protegido] | ALTERAR TABLA dbo.DDM ALTERAR COLUMNA Correo electrónico AGREGAR ENMASCARADO CON(FUNCCIÓN = 'correo electrónico()') |
| Aleatorio | La función de enmascaramiento aleatorio (número_inicial, número_final) le permite reemplazar un valor numérico con un número aleatorio generado a partir de un rango predefinido. Las columnas que tienen tipos de datos que aceptan valores decimales pueden aceptar rangos en forma de números decimales o enteros en la función random(). Si el rango está compuesto por 2 números enteros en una columna que acepta valores decimales, los valores aleatorios generados también tendrán posiciones decimales con valores aleatorios (ver ejemplos). | ALTER TABLE dbo.DDM ALTER COLUMN Salario AGREGAR ENMASCARADO CON(FUNCTION = 'random(0.5, 0.99)') ALTER TABLE dbo.DDM ALTER COLUMN Peso AGREGAR ENMASCARADO CON(FUNCTION = 'random(70, 120)') |
| Parcial | La función de enmascaramiento parcial (prefijo, relleno, sufijo) muestra las primeras N letras (N = prefijo) y las últimas N letras (N = sufijo) y agrega una cadena de relleno personalizada S entre ellas (S = relleno). Nota: si el valor original es demasiado corto para completar toda la máscara, parte del prefijo o sufijo no quedará expuesto, es decir, si la cadena es "Dirceu" y la función de enmascaramiento es parcial(4, "XXXX", 4), la cadena quedará enmascarada como "XXXX", no respetando los valores de prefijo y sufijo, que no se mostrarán. | ALTERAR TABLA dbo.DDM ALTERAR COLUMNA CPF AGREGAR ENMASCARADO CON(FUNCCIÓN = 'parcial(0, "XXXXXXXXX", 2)') ALTER TABLE dbo.DDM ALTER COLUMN Cargo AGREGAR ENMASCARADO CON(FUNCTION = 'parcial(3, "XXXXXXXXXXXX", 4)') |
Ejemplos y demostraciones
Para demostrar mejor las características y funciones del enmascaramiento dinámico de datos, preparé un ejemplo a continuación, que demuestra cómo crear una tabla con algunos campos ya enmascarados, luego visualizaremos estos campos con la máscara.
Cambiaré la tabla para enmascarar más columnas, obtener una vista previa de los datos enmascarados (ahora con todas las columnas) y luego obtener una vista previa del resultado 5 veces para observar el comportamiento de la función de enmascaramiento random() cuando se ejecuta varias veces.
Ejemplo de enmascaramiento de datos dinámicos
-- Criação da tabela de testes do Dynamic Data Masking
-- Máscaras criadas nas colunas "CPF" e "Nome" na criação na tabela
IF (OBJECT_ID('dbo.Teste_DDM') IS NOT NULL) DROP TABLE dbo.Teste_DDM
CREATE TABLE dbo.Teste_DDM (
CPF VARCHAR(11) MASKED WITH(FUNCTION = 'partial(0, "XXXXXXXXX", 2)'),
Nome VARCHAR(60) MASKED WITH(FUNCTION = 'default()'),
Cargo VARCHAR(40),
Data_Nascimento DATETIME,
Email VARCHAR(100),
Email_Corporativo VARCHAR(100),
Salario NUMERIC(15, 2),
Numero_Cartao VARCHAR(16),
Peso INT,
Altura FLOAT,
Estado_Civil SMALLINT,
Genero BIT
)
-- Inseração de registro na tabela de testes
INSERT INTO dbo.Teste_DDM
VALUES
(
'12345678909', -- CPF - varchar(11)
'Joãozinho da Silva', -- Nome - varchar(60)
'Analista de Sistemas', -- Cargo - varchar(40)
'1987-05-28', -- Data_Nascimento - datetime
'[email protected]', -- Email - varchar(100)
'[email protected]', -- Email_Corporativo - varchar(100)
12345.67, -- Salario - numeric(15, 2)
'1234567890123456', -- Numero_Cartao - varchar(16)
85, -- Peso - int
1.81, -- Altura - float
1, -- Estado_Civil - smallint
1 -- Genero - bit
)
SELECT * FROM dbo.Teste_DDM
GO
-- Vamos criar um usuário para conseguirmos visualizar os dados mascarados
-- Lembre-se: Usuários com permissão db_owner ou sysadmin SEMPRE vão ver os dados sem máscara
IF (USER_ID('Teste_DDM') IS NULL)
CREATE USER [Teste_DDM] WITHOUT LOGIN
GRANT SELECT ON dbo.Teste_DDM TO [Teste_DDM]
-- Visualizando os dados mascarados (Como se fosse o usuário Teste_DDM, que acabamos de criar)
EXECUTE AS USER = 'Teste_DDM'
GO
SELECT * FROM dbo.Teste_DDM
GO
REVERT -- Reverte as permissões para o seu usuário
GO
-- Vamos criar mais algumas máscaras na tabela
ALTER TABLE dbo.Teste_DDM ALTER COLUMN Cargo ADD MASKED WITH(FUNCTION = 'partial(4, "XXXX", 4)')
ALTER TABLE dbo.Teste_DDM ALTER COLUMN Data_Nascimento ADD MASKED WITH(FUNCTION = 'default()')
ALTER TABLE dbo.Teste_DDM ALTER COLUMN Email ADD MASKED WITH(FUNCTION = 'email()')
ALTER TABLE dbo.Teste_DDM ALTER COLUMN Email_Corporativo ADD MASKED WITH(FUNCTION = 'email()')
ALTER TABLE dbo.Teste_DDM ALTER COLUMN Salario ADD MASKED WITH(FUNCTION = 'random(0.5, 0.99)')
ALTER TABLE dbo.Teste_DDM ALTER COLUMN Numero_Cartao ADD MASKED WITH(FUNCTION = 'partial(4, "********", 4)')
ALTER TABLE dbo.Teste_DDM ALTER COLUMN Peso ADD MASKED WITH(FUNCTION = 'random(70, 120)')
ALTER TABLE dbo.Teste_DDM ALTER COLUMN Altura ADD MASKED WITH(FUNCTION = 'default()')
ALTER TABLE dbo.Teste_DDM ALTER COLUMN Estado_Civil ADD MASKED WITH(FUNCTION = 'default()')
ALTER TABLE dbo.Teste_DDM ALTER COLUMN Genero ADD MASKED WITH(FUNCTION = 'default()')
-- Visualizando os dados mascarados
EXECUTE AS USER = 'Teste_DDM'
GO
SELECT * FROM dbo.Teste_DDM
SELECT * FROM dbo.Teste_DDM
SELECT * FROM dbo.Teste_DDM
SELECT * FROM dbo.Teste_DDM
SELECT * FROM dbo.Teste_DDM
REVERT
Visualización de los datos originales (con el usuario sysadmin):

Ver datos enmascarados después de la creación de la tabla (con un usuario normal):
IF (OBJECT_ID('dbo.Teste_DDM') IS NOT NULL) DROP TABLE dbo.Teste_DDM
CREATE TABLE dbo.Teste_DDM (
CPF VARCHAR(11) MASKED WITH(FUNCTION = 'partial(0, "XXXXXXXXX", 2)'),
Nome VARCHAR(60) MASKED WITH(FUNCTION = 'default()'),
...
)
Ver los datos enmascarados después de todos los cambios:
ALTER TABLE dbo.Teste_DDM ALTER COLUMN Cargo ADD MASKED WITH(FUNCTION = 'partial(4, "XXXX", 4)')
ALTER TABLE dbo.Teste_DDM ALTER COLUMN Data_Nascimento ADD MASKED WITH(FUNCTION = 'default()')
ALTER TABLE dbo.Teste_DDM ALTER COLUMN Email ADD MASKED WITH(FUNCTION = 'email()')
ALTER TABLE dbo.Teste_DDM ALTER COLUMN Email_Corporativo ADD MASKED WITH(FUNCTION = 'email()')
ALTER TABLE dbo.Teste_DDM ALTER COLUMN Salario ADD MASKED WITH(FUNCTION = 'random(0.5, 0.99)')
ALTER TABLE dbo.Teste_DDM ALTER COLUMN Numero_Cartao ADD MASKED WITH(FUNCTION = 'partial(4, "********", 4)')
ALTER TABLE dbo.Teste_DDM ALTER COLUMN Peso ADD MASKED WITH(FUNCTION = 'random(70, 120)')
ALTER TABLE dbo.Teste_DDM ALTER COLUMN Altura ADD MASKED WITH(FUNCTION = 'default()')
ALTER TABLE dbo.Teste_DDM ALTER COLUMN Estado_Civil ADD MASKED WITH(FUNCTION = 'default()')
ALTER TABLE dbo.Teste_DDM ALTER COLUMN Genero ADD MASKED WITH(FUNCTION = 'default()')
Seguridad y permisos
Para crear o cambiar una tabla usando el enmascaramiento dinámico de datos, no se requieren permisos especiales, solo los permisos tradicionales para crear/cambiar tablas (CREATE TABLE y/o ALTER).
Para agregar, cambiar o eliminar una máscara de columna, el usuario debe tener los permisos ALTERAR CUALQUIER MÁSCARA y ALTERAR.
Los usuarios con permiso SELECCIONAR en una tabla podrán ver los datos de la tabla y las columnas enmascaradas mostrarán los datos enmascarados. Para que el usuario pueda ver los datos originales, es decir sin máscaras, debe tener el permiso DESMASCARAR (GRANT UNMASK TO [Usuario]).
Además, el permiso CONTROL en la base de datos incluye los permisos ALTERAR CUALQUIER MÁSCARA y DESMASCARAR, es decir, los usuarios de los roles db_owner o sysadmin pueden ver los datos originales de las columnas enmascaradas.
¿Qué columnas están enmascaradas?
Para identificar qué columnas de su base de datos están enmascaradas mediante el enmascaramiento dinámico de datos, simplemente ejecute la siguiente consulta:
SELECT
A.[name],
B.[name] AS table_name,
A.masking_function
FROM
sys.masked_columns AS A
JOIN sys.tables AS B ON A.[object_id] = B.[object_id]
WHERE
A.is_masked = 1
Desglosando el enmascaramiento de datos dinámicos
Habiendo presentado las características del enmascaramiento dinámico de datos, también debemos mencionar que este método no debería ser la única forma de garantizar la seguridad de sus datos. Aunque ayuda a proteger información confidencial, tiene algunas fallas que permiten que usuarios no autorizados accedan a esta información mediante técnicas de fuerza bruta.
En el ejemplo anterior, inserté un registro en la tabla donde el salario era de R$ 12.345,67. Usando fuerza bruta (o prueba y error), podemos encontrar el valor aproximado de este salario (con paciencia o creando un algoritmo para ello, hasta el valor exacto)
Ejemplo 1 – Valor numérico:
En este ejemplo, demostraré cómo es posible identificar valores numéricos enmascarados a partir de SELECT simples en la tabla. En el siguiente ejemplo, solo hice 6 consultas y ya pude descubrir un valor muy cercano al valor real. Si quisiera saber el valor exacto, unas cuantas consultas más serían suficientes para hacerlo:
¿Quieres saber el valor exacto? Usemos un script simple para esto:
-- Simulando as permissões do usuário "Teste_DDM"
EXECUTE AS USER = 'Teste_DDM'
GO
-- Criando uma tabela de testes com vários números para tentar fazer o JOIN
IF (OBJECT_ID('tempdb..#Numeros') IS NOT NULL) DROP TABLE #Numeros
CREATE TABLE #Numeros ( Valor NUMERIC(15, 2) )
INSERT INTO #Numeros
VALUES(0.01)
DECLARE @Contador INT = 1, @Total INT = 100, @Maior_Valor NUMERIC(15, 2)
-- 33s
WHILE(@Contador <= @Total)
BEGIN
SELECT @Maior_Valor = MAX(Valor) FROM #Numeros
INSERT INTO #Numeros
SELECT @Maior_Valor + ROW_NUMBER() OVER(ORDER BY [name], id) / 100.00
FROM sys.syscolumns
SET @Contador += 1
END
-- Verificando se foi possível identificar o valor do salário
SELECT A.*, B.Valor
FROM dbo.Teste_DDM A
JOIN #Numeros B ON A.Salario = B.Valor
Resultado: ¡Revelado el salario de Joãozinho!

Ejemplo 2: texto
Aquí, demostraré cómo devolver la cadena exacta que fue enmascarada usando una tabla de caracteres y uniendo letra por letra con esta tabla. Cada letra de la cadena enmascarada se unirá a mi tabla de caracteres para descubrir el carácter original.
-- Executando o código abaixo como um usuário SEM permissão de UNMASK
EXECUTE AS USER = 'Teste_DDM'
GO
------------------------------------------------------------------------
-- Criando a tabela com todos os caracteres da tabela ASCII
------------------------------------------------------------------------
IF (OBJECT_ID('tempdb..#Caracteres') IS NOT NULL) DROP TABLE #Caracteres
CREATE TABLE #Caracteres ( Caractere CHAR(1) )
-- DECLARE @Contador INT = 32, @Total INT = 255 /* Case SENSTIVE */
DECLARE @Contador INT = 32, @Total INT = 96 /* Case INSENSITIVE */
WHILE(@Contador <= @Total)
BEGIN
INSERT INTO #Caracteres VALUES (CHAR(@Contador))
SET @Contador += 1
END
------------------------------------------------------------------------
-- Fazendo os joins para recuperar o valor original da string mascarada
------------------------------------------------------------------------
DECLARE
@Query VARCHAR(MAX),
@Maior_Tamanho INT = 18, -- Tamanho máximo de caracteres da string em questão
@Contador_Caractere INT = 1
SET @Query = '
SELECT
A.Nome,'
SET @Contador_Caractere = 1
WHILE(@Contador_Caractere <= @Maior_Tamanho)
BEGIN
SET @Query += '
A' + CAST(@Contador_Caractere AS VARCHAR(20)) + '.Caractere' + (CASE WHEN @Contador_Caractere < @Maior_Tamanho THEN ',' ELSE '' END)
SET @Contador_Caractere += 1
END
SET @Query += '
FROM
dbo.Teste_DDM A'
SET @Contador_Caractere = 1
WHILE(@Contador_Caractere <= @Maior_Tamanho)
BEGIN
SET @Query += '
LEFT JOIN #Caracteres A' + CAST(@Contador_Caractere AS VARCHAR(20)) + ' ON SUBSTRING(A.Nome, ' + CAST(@Contador_Caractere AS VARCHAR(20)) + ', 1) = A' + CAST(@Contador_Caractere AS VARCHAR(20)) + '.Caractere'
SET @Contador_Caractere += 1
END
-- Executa nossa query dinâmica e mostrando os valores originais
EXEC(@Query)
¡Eso es todo, amigos!
Espero que hayas disfrutado de esta publicación y ¡hasta luego!





Comentários (0)
Carregando comentários…