Copiar la base de datos de producción a un entorno de pruebas es la forma más habitual de obtener datos realistas, pero traslada información personal —nombres, correos, DNI— a entornos con menos controles, en contra del RGPD. El enmascaramiento de datos evita ese riesgo sustituyendo los valores sensibles por otros ficticios pero verosímiles.
Esta es una guía práctica: cómo enmascarar datos en SQL Server con sus dos métodos —Dynamic Data Masking y enmascaramiento estático—, qué protege cada uno y cómo aplicarlos sin romper las relaciones entre tablas.
Los dos métodos de enmascaramiento en SQL Server
SQL Server permite enmascarar de dos formas con resultados muy distintos. Dynamic Data Masking oculta los datos en el momento de la consulta, sin modificar la tabla. El enmascaramiento estático reescribe los valores de forma permanente en una copia. El primero limita lo que se ve en producción; el segundo protege los datos que salen a otros entornos. La elección se aclara en cuanto se ve cómo funciona cada uno.
Cómo aplicar Dynamic Data Masking
Disponible desde SQL Server 2016, Dynamic Data Masking (DDM) se define a nivel de columna y se aplica de forma automática cada vez que se consulta el campo. Puede declararse al crear la tabla:
sql
CREATE TABLE dbo.Clientes (
ClienteID INT IDENTITY PRIMARY KEY,
Nombre NVARCHAR(100) MASKED WITH (FUNCTION = 'default()'),
Email NVARCHAR(256) MASKED WITH (FUNCTION = 'email()'),
Telefono VARCHAR(20) MASKED WITH (FUNCTION = 'partial(0,"+34 XXX XXX ",3)'),
Saldo MONEY MASKED WITH (FUNCTION = 'random(1, 1000)')
);O añadirse a una columna que ya existe:
ALTER TABLE dbo.Clientes
ALTER COLUMN Email ADD MASKED WITH (FUNCTION = 'email()');Las cuatro funciones de máscara
Cada función cubre un tipo de dato:
- default() — oculta el valor completo según el tipo: XXXX en texto, 0 en numéricos, una fecha fija en campos de fecha.
- email() — deja visible la primera letra y sustituye el resto por un sufijo .com (por ejemplo, aXXX@XXXX.com).
- partial(prefijo, relleno, sufijo) — expone los caracteres indicados al inicio y al final, y enmascara el centro con el texto de relleno.
- random(inicio, fin) — reemplaza un valor numérico por uno aleatorio dentro del rango.
Conceder y revocar el permiso UNMASK
Por defecto, los usuarios ven los valores enmascarados; el permiso UNMASK revela los originales. Desde SQL Server 2022 es granular y puede otorgarse a nivel de base de datos, esquema, tabla o columna:
sql
-- Permitir a un rol ver solo el correo sin máscara
GRANT UNMASK ON dbo.Clientes(Email) TO rol_soporte;
-- Retirar el permiso
REVOKE UNMASK ON dbo.Clientes(Email) FROM rol_soporte;Comprobar qué columnas están enmascaradas
La vista de catálogo sys.masked_columns lista las máscaras activas y su función, útil para auditar la configuración:
sql
SELECT t.name AS tabla, c.name AS columna, c.masking_function
FROM sys.masked_columns c
JOIN sys.tables t ON c.object_id = t.object_id
WHERE c.is_masked = 1;Los límites de seguridad del Dynamic Data Masking
Antes de confiar la protección a esta función conviene conocer un matiz que la mayoría de las guías omite. Microsoft es explícita: Dynamic Data Masking no es un mecanismo de seguridad. Evita la exposición accidental, no el acceso deliberado.
La razón es que la máscara solo afecta a la presentación; el valor real sigue almacenado y la base de datos sigue operando con él. Un usuario con permiso de SELECT, aunque no tenga UNMASK, puede deducir datos enmascarados con filtros:
sql
-- Saldo está enmascarado, pero el filtro revela qué clientes
-- superan un umbral concreto
SELECT Nombre FROM dbo.Clientes WHERE Saldo > 50000;Repitiendo consultas con distintas condiciones se reconstruyen los valores ocultos. Y cualquier cuenta administrativa, o una copia de la base hacia otro entorno, accede al dato real sin restricción. Por eso DDM funciona como capa complementaria junto a la auditoría, el cifrado y la seguridad a nivel de fila, pero no protege los datos que salen de producción.
Cómo hacer enmascaramiento estático para entornos no productivos
Cuando hay que entregar una copia segura a desarrollo, control de calidad, analítica o entrenamiento de modelos, el enmascaramiento debe ser estático: los valores se reescriben físicamente y el dato real desaparece. Así, aunque alguien acceda con privilegios totales, no hay información personal que recuperar. Es lo que retira esos entornos no productivos del alcance del RGPD. Estos son los pasos.
1. Descubrir las dependencias antes de transformar
Toda transformación segura empieza por mapear el esquema: localizar las claves primarias y foráneas para no enmascarar una columna y dejar huérfanas las que dependen de ella. Las vistas de catálogo de SQL Server las exponen:
sql
SELECT
tp.name AS tabla_padre, cp.name AS columna_padre,
tr.name AS tabla_hija, cr.name AS columna_hija
FROM sys.foreign_keys fk
JOIN sys.foreign_key_columns fkc ON fk.object_id = fkc.constraint_object_id
JOIN sys.tables tp ON fkc.referenced_object_id = tp.object_id
JOIN sys.columns cp ON fkc.referenced_object_id = cp.object_id AND fkc.referenced_column_id = cp.column_id
JOIN sys.tables tr ON fkc.parent_object_id = tr.object_id
JOIN sys.columns cr ON fkc.parent_object_id = cr.object_id AND fkc.parent_column_id = cr.column_id;2. Transformar con consistencia
Cada campo se sustituye respetando su formato y las validaciones del sistema. La clave es que un mismo valor se transforme siempre igual, para no romper los cruces entre tablas:
sql
-- Correo sintético derivado del identificador (determinista)
UPDATE dbo.Clientes
SET Email = CONCAT('cliente', ClienteID, '@ejemplo.es');Un DNI se reemplaza por otro con letra de control válida; un teléfono, por uno con formato +34 correcto pero ficticio.
3. Preservar la integridad referencial
La mayoría de los proyectos de enmascaramiento fracasan al preservar la integridad referencial. Si el identificador de un cliente cambia en su tabla pero no en las de pedidos o facturas, las relaciones se rompen y la copia queda inservible. La solución es un mapeo determinista propagado en cascada:
sql
-- Mapa único de equivalencias (ROW_NUMBER evita colisiones)
SELECT
ClienteID AS id_original,
ROW_NUMBER() OVER (ORDER BY ClienteID) AS id_nuevo
INTO #MapaClientes
FROM dbo.Clientes;
CREATE UNIQUE INDEX IX_MapaClientes ON #MapaClientes (id_original);
BEGIN TRAN;
UPDATE p
SET p.ClienteID = m.id_nuevo
FROM dbo.Pedidos p
JOIN #MapaClientes m ON p.ClienteID = m.id_original;
UPDATE c
SET c.ClienteID = m.id_nuevo
FROM dbo.Clientes c
JOIN #MapaClientes m ON c.ClienteID = m.id_original;
COMMIT;Si el identificador está protegido por claves foráneas, actualiza primero las tablas hijas, usa ON UPDATE CASCADE o desactiva temporalmente las restricciones durante la operación.
4. Automatizar y dejar evidencia
Un control que dependa de la ejecución manual no es fiable. La transformación debe integrarse en el flujo de aprovisionamiento (Azure DevOps, GitHub Actions, Jenkins) para que cada entorno se prepare con los datos ya protegidos. Y debe registrar qué se transformó, con qué regla y sobre qué versión del esquema: esa trazabilidad es lo que exigen el RGPD, la NIS2 y el ENS en una auditoría.

