En SQL Server 2005 y 2008 disponemos de encriptación nativa dentro del motor de base de datos. Sin entrar en demasiados detalles sobre la arquitectura de encriptación diremos que su fin es la de impedir el acceso no autorizado a información sensible como tarjetas de crédito, historiales médicos, etc. En SQL Server 2005 la encriptación debía ser gestionada de una forma bastante poco transparente (requería que la aplicación se adaptara) y su ámbito era exclusivamente el de columna encriptada. Con SQL Server 2008 aparece la encriptación transparente (TDE) que afecta a tablas completas y es transparente para las aplicaciones su uso lo cual mejora su usabilidad.Comenzaremos analizando la encriptación no transparente que tenemos presente en ambas versiones. Crearemos una base de datos para las pruebas así como una tabla que contendrá nuestra información a encriptar:

USE MASTER

CREATE DATABASE ENCRYPTION

GO

USE ENCRYPTION

GO

CREATE TABLE DATA

(

    ID INT IDENTITY (1,1) PRIMARY KEY,

    NOMBRE VARCHAR(255) NOT NULL,

    PASSWORD VARCHAR(255) NOT NULL

)

GO

 

Una vez creada la tabla crearemos la master key para dicha base de datos, el certificado y la clave simétrica:

CREATE MASTER KEY ENCRYPTION BY PASSWORD = ‘Pa$$w0rd’

GO

CREATE CERTIFICATE CertificadoEncriptacion WITH SUBJECT = ‘Certificado para encriptar’

GO

CREATE SYMMETRIC KEY ClaveSimetrica

WITH ALGORITHM = AES_256

ENCRYPTION BY CERTIFICATE CertificadoEncriptacion

GO

Una vez tenemos la infraestructura lista para encriptar añadiremos algunos datos a nuestra tabla:

INSERT INTO DATA

VALUES (‘USUARIO 1’,‘11111111’)

GO

INSERT INTO DATA

VALUES (‘USUARIO 2’,‘22222222’)

GO

INSERT INTO DATA

VALUES (‘USUARIO 3’,‘33333333’)

GO

SELECT * FROM DATA

Imaginemos que queremos encriptar la información de los passwords de nuestros usuarios de forma que podamos certificar que ya no existe forma de obtener dichos datos en claro. Para conseguir esto se nos plantean a priori varias alternativas:

  1. Conversión «in-place». Consiste en realizar un UPDATE de los datos pero utilizando la función de encriptado (ENCRYPTBYKEY). Esto requiere que los datos de la columna sean convertidos previamente a binario.
  2. Conversión «side-by-side». Consiste en añadir una nueva columna encriptada, volcar los datos en ésta y eliminar la columna no encriptada.
  3. Copia de la tabla. Consiste en crear una nueva tabla con la columna encriptada, realizar un volcado de los datos, eliminar la anterior y renombrar la nueva tabla al nombre de la anterior.

Vamos a tratar pues las alternativas 2 y 3 por su mayor practicidad (y por no variar sustancialmente el escenario) y vamos a ver como con cualquiera de ellas si no tomamos medidas adicionales sería posible por un atacante obtener información no encriptada a partir de los «residuos» del proceso.

  1. Conversión «side-by-side»

Los pasos a seguir serán crear la nueva columna que contendrá los datos encriptados, abrir la clave simétrica correspondiente, guardar la información encriptada y finalmente eliminar la columna password sin cifrar. Consultaremos la información de la tabla antes y después del proceso para comprobar que efectivamente queda encriptada.

 

OPEN SYMMETRIC KEY ClaveSimetrica

DECRYPTION BY CERTIFICATE CertificadoEncriptacion

GO

ALTER TABLE DATA

ADD PASSWORD2 VARBINARY(max)

GO

UPDATE DATA

SET PASSWORD2=ENCRYPTBYKEY(KEY_GUID(‘ClaveSimetrica’),PASSWORD)

GO

SELECT * FROM DATA

GO

ALTER TABLE DATA

DROP COLUMN PASSWORD

GO

SELECT * FROM DATA

 

En principio, en este momento la información sin encriptar no debería estar accesible. Sin embargo si consultamos las páginas que componen el objeto podemos ver que la información aún existe en claro en la base de datos:

— Obtenemos la página de datos de la tabla (pagetype=1) = página 165

DBCC IND (‘ENCRYPTION’, ‘data’, 1);

— Leemos la página

DBCC TRACEON (3604);

DBCC PAGE (ENCRYPTION, 1, 165, 1);

 

PAGE: (1:165)

 

 

BUFFER:

 

 

BUF @0x0000000085FE1A00

 

bpage = 0x0000000085BF4000 bhash = 0x0000000000000000 bpageno = (1:165)

bdbid = 6 breferences = 0 bUse1 = 30842

bstat = 0x7c0010b blog = 0xbbbbbbbb bnext = 0x0000000000000000

 

PAGE HEADER:

 

 

Page @0x0000000085BF4000

 

m_pageId = (1:165) m_headerVersion = 1 m_type = 1

m_typeFlagBits = 0x4 m_level = 0 m_flagBits = 0x8000

m_objId (AllocUnitId.idObj) = 32 m_indexId (AllocUnitId.idInd) = 256

Metadata: AllocUnitId = 72057594040025088

Metadata: PartitionId = 72057594039042048 Metadata: IndexId = 1

Metadata: ObjectId = 213575799 m_prevPage = (0:0) m_nextPage = (0:0)

pminlen = 8 m_slotCnt = 3 m_freeCnt = 7778

m_freeData = 510 m_reservedCnt = 0 m_lsn = (36:387:4)

m_xactReserved = 0 m_xdesId = (0:0) m_ghostRecCnt = 0

m_tornBits = 0

 

Allocation Status

 

GAM (1:2) = ALLOCATED SGAM (1:3) = NOT ALLOCATED

PFS (1:1) = 0x60 MIXED_EXT ALLOCATED 0_PCT_FULL DIFF (1:6) = CHANGED

ML (1:7) = NOT MIN_LOGGED

 

DATA:

 

 

Slot 0, Offset 0xc6, Length 104, DumpStyle BYTE

 

Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS

Record Size = 104

Memory Dump @0x000000001182C0C6

 

0000000000000000: 30000800 01000000 04000003 001c0024 †0…………..$

0000000000000010: 00680055 53554152 494f2031 31313131 †.h.USUARIO 11111

0000000000000020: 31313131 00ac185a 64107141 92c03a71 †1111.¬.Zd.qA’À:q

0000000000000030: 86fca4bd 01000000 6a1abbc1 c3e8c16e ††ü¤½….j.»ÁÃèÁn

0000000000000040: dcbe7b38 e6d6ef0a 5287b29b ceafac4f †Ü¾{8æÖï.R‡²›Î¯¬O

0000000000000050: aae8a23c a15b7638 93cbb14b 5f802d2a †ªè¢<¡[v8“˱K_.-*

0000000000000060: 81bc1123 650b89d1 †††††††††††††††††††.¼.#e.‰Ñ

 

Slot 1, Offset 0x12e, Length 104, DumpStyle BYTE

 

Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS

Record Size = 104

Memory Dump @0x000000001182C12E

 

0000000000000000: 30000800 02000000 04000003 001c0024 †0…………..$

0000000000000010: 00680055 53554152 494f2032 32323232 †.h.USUARIO 22222

0000000000000020: 32323232 00ac185a 64107141 92c03a71 †2222.¬.Zd.qA’À:q

0000000000000030: 86fca4bd 01000000 ef6f9d72 4f1659ec ††ü¤½….ïo.rO.Yì

0000000000000040: b4b42101 de28b5bf 936f9145 71f9a4b8 †´´!.Þ(µ¿“o‘Eqù¤¸

0000000000000050: 3526ad47 fa7bf4c7 a33c57b5 21852348 †5&­Gú{ôÇ£<Wµ!

#H

0000000000000060: a18e5e4b fc3e95e2 †††††††††††††††††††¡Ž^Kü>•â

 

Slot 2, Offset 0x196, Length 104, DumpStyle BYTE

 

Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS

Record Size = 104

Memory Dump @0x000000001182C196

 

0000000000000000: 30000800 03000000 04000003 001c0024 †0…………..$

0000000000000010: 00680055 53554152 494f2033 33333333 †.h.USUARIO 33333

0000000000000020: 33333333 00ac185a 64107141 92c03a71 †3333.¬.Zd.qA’À:q

0000000000000030: 86fca4bd 01000000 fcb364da dcc7daeb ††ü¤½….ü³dÚÜÇÚë

0000000000000040: fe0ed4d9 40337855 0504c3a0 4e8c38b5 †þ.ÔÙ@3xU..àNŒ8µ

0000000000000050: c13cb80a 7ac44ed0 d498df23 1b6106ab †Á<¸.zÄNÐÔ.ß#.a.«

0000000000000060: 824bcc56 dc6eaf46 †††††††††††††††††††‚KÌVÜn¯F

 

OFFSET TABLE:

 

Row – Offset

2 (0x2) – 406 (0x196)

1 (0x1) – 302 (0x12e)

0 (0x0) – 198 (0xc6)

 

En realidad este es el comportamiento habitual cada vez que eliminamos una columna. Para poder realizar dicha operación de forma eficiente no se recupera el espacio automáticamente hasta que no se reescriba la página en cuestión. No sería suficiente con forzar un update en la fila en cuestión:

UPDATE DATA

SET NOMBRE=NOMBRE+‘_NEW’

WHERE NOMBRE = ‘USUARIO 1’

Slot 0, Offset 0x25e, Length 108, DumpStyle BYTE

 

Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS

Record Size = 108

Memory Dump @0x000000001182C25E

 

0000000000000000: 30000800 01000000 04000003 00200028 †0………… .(

0000000000000010: 006c0055 53554152 494f2031 5f4e4557 †.l.USUARIO 1_NEW

0000000000000020: 31313131 31313131 00ac185a 64107141 †11111111.¬.Zd.qA

0000000000000030: 92c03a71 86fca4bd 01000000 6a1abbc1 †’À:q†ü¤½….j.»Á

0000000000000040: c3e8c16e dcbe7b38 e6d6ef0a 5287b29b †ÃèÁnܾ{8æÖï.R‡²›

0000000000000050: ceafac4f aae8a23c a15b7638 93cbb14b †Î¯¬Oªè¢<¡[v8“˱K

0000000000000060: 5f802d2a 81bc1123 650b89d1 ††††††††††_.-*.¼.#e.‰Ñ

Por tanto necesitamos reconstruir el índice para reclamar el espacio de la columna y eliminar de esta forma los restos no encriptados. En nuestro caso al contar con únicamente una página de datos no podemos fácilmente forzar un REBUILD del índice pues poco REBUILD podemos hacer a un índice de una página. Por tanto modificaremos la definición del índice dos veces para forzar así su reconstrucción al cambiar a un fillfactor muy bajo y luego un fillfactor del 100%:

ALTER INDEX PK__DATA__3214EC270EA330E9 ON DATA REBUILD WITH (FILLFACTOR=1)

GO

ALTER INDEX PK__DATA__3214EC270EA330E9 ON DATA REBUILD WITH (FILLFACTOR=100)

 

— Obtenemos la página de datos de la tabla (pagetype=1) = 166

DBCC IND (‘ENCRYPTION’, ‘data’, 1);

— Leemos la página

DBCC TRACEON (3604);

DBCC PAGE (ENCRYPTION, 1, 166, 1);

 

PAGE: (1:166)

 

 

BUFFER:

 

 

BUF @0x0000000086F84600

 

bpage = 0x000000008604C000 bhash = 0x0000000000000000 bpageno = (1:166)

bdbid = 6 breferences = 0 bUse1 = 32185

bstat = 0x7c0000b blog = 0x1212121b bnext = 0x0000000000000000

 

PAGE HEADER:

 

 

Page @0x000000008604C000

 

m_pageId = (1:166) m_headerVersion = 1 m_type = 1

m_typeFlagBits = 0x0 m_level = 0 m_flagBits = 0x0

m_objId (AllocUnitId.idObj) = 43 m_indexId (AllocUnitId.idInd) = 256

Metadata: AllocUnitId = 72057594040745984

Metadata: PartitionId = 72057594039369728 Metadata: IndexId = 1

Metadata: ObjectId = 213575799 m_prevPage = (0:0) m_nextPage = (0:0)

pminlen = 8 m_slotCnt = 4 m_freeCnt = 7708

m_freeData = 476 m_reservedCnt = 0 m_lsn = (37:168:55)

m_xactReserved = 0 m_xdesId = (0:0) m_ghostRecCnt = 0

m_tornBits = 0

 

Allocation Status

 

GAM (1:2) = ALLOCATED SGAM (1:3) = ALLOCATED

PFS (1:1) = 0x60 MIXED_EXT ALLOCATED 0_PCT_FULL DIFF (1:6) = CHANGED

ML (1:7) = NOT MIN_LOGGED

 

DATA:

 

 

Slot 0, Offset 0x60, Length 98, DumpStyle BYTE

 

Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS

Record Size = 98

Memory Dump @0x000000001182C060

 

0000000000000000: 30000800 01000000 03000002 001e0062 †0…………..b

0000000000000010: 00555355 4152494f 20315f4e 455700ac †.USUARIO 1_NEW.¬

0000000000000020: 185a6410 714192c0 3a7186fc a4bd0100 †.Zd.qA’À:q†ü¤½..

0000000000000030: 00006a1a bbc1c3e8 c16edcbe 7b38e6d6 †..j.»ÁÃèÁnܾ{8æÖ

0000000000000040: ef0a5287 b29bceaf ac4faae8 a23ca15b †ï.R‡²›Î¯¬Oªè¢<¡[

0000000000000050: 763893cb b14b5f80 2d2a81bc 1123650b †v8“˱K_.-*.¼.#e.

0000000000000060: 89d1†††††††††††††††††††††††††††††††††‰Ñ

 

Slot 1, Offset 0xc2, Length 94, DumpStyle BYTE

 

Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS

Record Size = 94

Memory Dump @0x000000001182C0C2

 

0000000000000000: 30000800 02000000 03000002 001a005e †0…………..^

0000000000000010: 00555355 4152494f 203200ac 185a6410 †.USUARIO 2.¬.Zd.

0000000000000020: 714192c0 3a7186fc a4bd0100 0000ef6f †qA’À:q†ü¤½….ïo

0000000000000030: 9d724f16 59ecb4b4 2101de28 b5bf936f †.rO.Yì´´!.Þ(µ¿“o

0000000000000040: 914571f9 a4b83526 ad47fa7b f4c7a33c †‘Eqù¤¸5&­Gú{ôÇ£<

0000000000000050: 57b52185 2348a18e 5e4bfc3e 95e2††††††Wµ!

#H¡Ž^Kü>•â

 

Slot 2, Offset 0x120, Length 94, DumpStyle BYTE

 

Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS

Record Size = 94

Memory Dump @0x000000001182C120

 

0000000000000000: 30000800 03000000 03000002 001a005e †0…………..^

0000000000000010: 00555355 4152494f 203300ac 185a6410 †.USUARIO 3.¬.Zd.

0000000000000020: 714192c0 3a7186fc a4bd0100 0000fcb3 †qA’À:q†ü¤½….ü³

0000000000000030: 64dadcc7 daebfe0e d4d94033 78550504 †dÚÜÇÚëþ.ÔÙ@3xU..

0000000000000040: c3a04e8c 38b5c13c b80a7ac4 4ed0d498 †Ã NŒ8µÁ<¸.zÄNÐÔ.

0000000000000050: df231b61 06ab824b cc56dc6e af46††††††ß#.a.«‚KÌVÜn¯F

 

Podemos ver como tras la manipulación del índice de esta forma las páginas ya no incluyen la columna desencriptada y únicamente los datos encriptados. Llegados a este punto podríamos darnos por satisfechos por el trabajo bien hecho sin merecerlo. ¿El motivo? Lo explicaremos conjuntamente con los problemas de la alternativa 3 ya que sería aplicable igualmente a este escenario J

  1. Copia de la tabla

Si partimos de tabla con los datos sin encriptar podemos optar por crear una nueva tabla donde volcaremos directamente los datos encriptados. De esta forma no tendremos el problema de la columna borrada del ejemplo anterior. Recrearemos la tabla original y lanzaremos el siguiente script:

OPEN SYMMETRIC KEY ClaveSimetrica

DECRYPTION BY CERTIFICATE CertificadoEncriptacion

GO

SELECT ID,NOMBRE,ENCRYPTBYKEY(KEY_GUID(‘ClaveSimetrica’),PASSWORD) PASSWORD2

INTO DATA2

FROM DATA

GO

— Obtenemos la página de datos de la tabla (pagetype=1) = 173

DBCC IND (‘ENCRYPTION’, ‘data2’, 1);

— Leemos la página

DBCC TRACEON (3604);

DBCC PAGE (ENCRYPTION, 1, 173, 1);

 

PAGE: (1:173)

 

 

BUFFER:

 

 

BUF @0x0000000086F83500

 

bpage = 0x000000008602A000 bhash = 0x0000000000000000 bpageno = (1:173)

bdbid = 6 breferences = 0 bUse1 = 33086

bstat = 0x7c0000b blog = 0x12121bcb bnext = 0x0000000000000000

 

PAGE HEADER:

 

 

Page @0x000000008602A000

 

m_pageId = (1:173) m_headerVersion = 1 m_type = 1

m_typeFlagBits = 0x0 m_level = 0 m_flagBits = 0x0

m_objId (AllocUnitId.idObj) = 46 m_indexId (AllocUnitId.idInd) = 256

Metadata: AllocUnitId = 72057594040942592

Metadata: PartitionId = 72057594039500800 Metadata: IndexId = 0

Metadata: ObjectId = 341576255 m_prevPage = (0:0) m_nextPage = (0:0)

pminlen = 8 m_slotCnt = 3 m_freeCnt = 7808

m_freeData = 378 m_reservedCnt = 0 m_lsn = (37:243:21)

m_xactReserved = 0 m_xdesId = (0:0) m_ghostRecCnt = 0

m_tornBits = 0

 

Allocation Status

 

GAM (1:2) = ALLOCATED SGAM (1:3) = ALLOCATED

PFS (1:1) = 0x61 MIXED_EXT ALLOCATED 50_PCT_FULL DIFF (1:6) = CHANGED

ML (1:7) = NOT MIN_LOGGED

 

DATA:

 

 

Slot 0, Offset 0x60, Length 94, DumpStyle BYTE

 

Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS

Record Size = 94

Memory Dump @0x000000001182C060

 

0000000000000000: 30000800 01000000 03000002 001a005e †0…………..^

0000000000000010: 00555355 4152494f 203100ac 185a6410 †.USUARIO 1.¬.Zd.

0000000000000020: 714192c0 3a7186fc a4bd0100 0000d29d †qA’À:q†ü¤½….Ò.

0000000000000030: 55058c77 dc55a913 f188170c f63b9e54 †U.ŒwÜU©.ñ…ö;žT

0000000000000040: c5fdca9d 9688b869 78591870 2b28dbd4 †ÅýÊ.–.¸ixY.p+(ÛÔ

0000000000000050: f9ad6949 3408a4bd 18206250 afd9††††††ù­iI4.¤½. bP¯Ù

 

Slot 1, Offset 0xbe, Length 94, DumpStyle BYTE

 

Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS

Record Size = 94

Memory Dump @0x000000001182C0BE

 

0000000000000000: 30000800 02000000 03000002 001a005e †0…………..^

0000000000000010: 00555355 4152494f 203200ac 185a6410 †.USUARIO 2.¬.Zd.

0000000000000020: 714192c0 3a7186fc a4bd0100 00009ca9 †qA’À:q†ü¤½….œ©

0000000000000030: 7e1d1b2e 19fefe2d cba1f93e e153940c †~….þþ-Ë¡ù>áS”.

0000000000000040: aa8a9734 f93f9312 78f86a43 f9d591af †ªŠ—4ù?“.xøjCùÕ‘¯

0000000000000050: a21d12d1 252ce941 db0e3887 ca34††††††¢..Ñ%,éAÛ.8‡Ê4

 

Slot 2, Offset 0x11c, Length 94, DumpStyle BYTE

 

Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS

Record Size = 94

Memory Dump @0x000000001182C11C

 

0000000000000000: 30000800 03000000 03000002 001a005e †0…………..^

0000000000000010: 00555355 4152494f 203300ac 185a6410 †.USUARIO 3.¬.Zd.

0000000000000020: 714192c0 3a7186fc a4bd0100 00008c81 †qA’À:q†ü¤½….Œ.

0000000000000030: 88781385 6d888024 a1119a23 bbf28620 †.x.m..$¡.š#»ò†

0000000000000040: 015ca2dc c4e77ea5 56773534 58fdf235 †.¢ÜÄç~¥Vw54Xýò5

0000000000000050: 517d43f0 1525b232 43ed451d 8424††††††Q}Cð.%²2CíE.„$

Podemos ver como ahora la nueva tabla no tiene ningún resto desencriptado. El siguiente paso sería borrar la tabla original y renombrar la nueva tabla con el nombre de la anterior. Antes de borrar la tabla obtendremos la página de datos original donde están almacenados los datos:

— Obtenemos la página de datos de la tabla (pagetype=1) = 169

DBCC IND (‘ENCRYPTION’, ‘data’, 1);

— Borramos

DROP TABLE DATA

— Leemos la página después de borrar

DBCC TRACEON (3604);

DBCC PAGE (ENCRYPTION, 1, 169, 1);

PAGE: (1:169)

 

 

BUFFER:

 

 

BUF @0x0000000085FFE000

 

bpage = 0x0000000085F80000 bhash = 0x0000000000000000 bpageno = (1:169)

bdbid = 6 breferences = 0 bUse1 = 33245

bstat = 0x7c0000b blog = 0xbcb79bbb bnext = 0x0000000000000000

 

PAGE HEADER:

 

 

Page @0x0000000085F80000

 

m_pageId = (1:169) m_headerVersion = 1 m_type = 1

m_typeFlagBits = 0x4 m_level = 0 m_flagBits = 0x8000

m_objId (AllocUnitId.idObj) = 45 m_indexId (AllocUnitId.idInd) = 256

Metadata: AllocUnitId = 72057594040877056 Metadata: PartitionId = 0

Metadata: IndexId = -1 Metadata: ObjectId = 0 m_prevPage = (0:0)

m_nextPage = (0:0) pminlen = 8 m_slotCnt = 3

m_freeCnt = 7988 m_freeData = 198 m_reservedCnt = 0

m_lsn = (37:233:3) m_xactReserved = 0 m_xdesId = (0:0)

m_ghostRecCnt = 0 m_tornBits = 0

 

Allocation Status

 

GAM (1:2) = ALLOCATED SGAM (1:3) = ALLOCATED PFS (1:1) = 0x20 MIXED_EXT 0_PCT_FULL

DIFF (1:6) = CHANGED ML (1:7) = NOT MIN_LOGGED

 

DATA:

 

 

Slot 0, Offset 0x60, Length 34, DumpStyle BYTE

 

Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS

Record Size = 34

Memory Dump @0x000000001182C060

 

0000000000000000: 30000800 01000000 03000002 001a0022 †0…………..»

0000000000000010: 00555355 4152494f 20313131 31313131 †.USUARIO 1111111

0000000000000020: 3131†††††††††††††††††††††††††††††††††11

 

Slot 1, Offset 0x82, Length 34, DumpStyle BYTE

 

Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS

Record Size = 34

Memory Dump @0x000000001182C082

 

0000000000000000: 30000800 02000000 03000002 001a0022 †0…………..»

0000000000000010: 00555355 4152494f 20323232 32323232 †.USUARIO 2222222

0000000000000020: 3232†††††††††††††††††††††††††††††††††22

 

Slot 2, Offset 0xa4, Length 34, DumpStyle BYTE

 

Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS

Record Size = 34

Memory Dump @0x000000001182C0A4

 

0000000000000000: 30000800 03000000 03000002 001a0022 †0…………..»

0000000000000010: 00555355 4152494f 20333333 33333333 †.USUARIO 3333333

0000000000000020: 3333†††††††††††††††††††††††††††††††††33

 

OFFSET TABLE:

 

Row – Offset

2 (0x2) – 164 (0xa4)

1 (0x1) – 130 (0x82)

0 (0x0) – 96 (0x60)

Como podemos ver, el borrar una tabla tampoco nos asegura que las páginas que estaban asignadas a dicha tabla sean limpiadas de datos desencriptados. Por tanto nos encontramos con una situación aún peor que en la anterior pues, una vez borrada la tabla, será mucho más complicado eliminar el rastro de dicha página al no tener acceso a la tabla de la que formaba parte. Podríamos recurrir a DBCC WRITEPAGE pero creemos que ha quedado claro lo desafortunado de esta alternativa.

Tanto en la alternativa 2 y 3 hemos despreciado otro factor importante, nuestro amigo el log de transaccionesJ. Aunque realicemos las operaciones apropiadas para asegurar que los datos de las páginas de datos estén sobreescritas las operaciones de update/insert previas pueden dejar un rastro de datos sin encriptar en el log de transacciones. Por ejemplo, si volvemos al primero de los ejemplos tras insertar los datos en el log de transacciones aún aparece la información desencriptada la cual no desaparece «mágicamente» aunque posteriores operaciones encripten dicha información. Si volcamos el contenido del log:

DBCC LOG(encryption,4)

En el campo Log Record tenemos almacenado el contenido del registro del log en binario. Si realizamos algunas conversiones al campo Log Record podemos sacar el valor del campo password antes de encriptar más fácilmente. Para ello podemos aplicar el siguiente pseudocódigo:

  1. Convertir la cadena binaria en texto copiándola entre comillas simples.
  2. Reemplazar los 00 por 20 para facilitar la posterior conversión a varchar y evitar el terminador (el 00) sustituyéndolo por un espacio en blanco (20).
  3. Volver a convertir a binario con código dinámico. Sería el equivalente inverso de la función fn_varbintohexstr.
  4. Convertir el nuevo binario en una cadena de texto

Esto en TSQL sería el siguiente fragmento que nos mostrará como resulta la cadena con el password:

DECLARE @cadena VARCHAR(MAX)

SET @cadena = ‘0x00003E00260000004F000000020002000504000000000202A9000000010001003700000026000000140000001300000100001C00000
000010000000000000300220000001A006600300008000200000003000002001A0022005553554152494F2032323232323232323200050101000C000024E1C
1200000010200040204000A020068E8B2740000’

DECLARE @bin VARBINARY(MAX)

DECLARE @sql NVARCHAR(MAX)

SET @sql = ‘SELECT @bin = ‘ + replace(@cadena,’00’,’20’)

EXEC sp_executesql @sql, N’@bin VARBINARY(MAX) OUTPUT’,@bin OUTPUT

SELECT convert(varchar(max),@bin)

En conclusión hemos visto que encriptar información no es una labor trivial si queremos hacerlo bien. Aunque no es habitual este tipo de ataques como los mostrados cuando una ley nos obliga a encriptar la información y nosotros aseguramos/certificamos su cumplimiento nada nos salvará de una multa si se encuentran datos sensibles en una auditoría seria. Por ejemplo a partir de un fichero de backup que supuestamente tiene la información encriptada podríamos obtener los mismos datos pues éste almacenaría las páginas en su formato original. Por otra parte ante datos que siguen un patrón concreto como los números de tarjeta de crédito sería posible realizar una búsqueda binaria contra el fichero de datos o de backup para intentarlas localizar estén donde estén. En el siguiente post sobre encriptación nos centraremos en la encriptación transparente de SQL Server 2008 (TDE) y en cómo podemos, tanto en 2005 como 2008, realizar un cifrado con seguridad J

 

 

Rubén Garrigós

Mentor at SolidQ
I am an expert in high-availability enterprise solutions based on SQL Server design, tuning, and troubleshooting. Over the past fifteen years, I have worked with Microsoft data access technologies in leading companies around the world.

Nowadays, I am a Microsoft SQL Server and .NET applications architect with SolidQ. I am certified by Microsoft as a Solution Expert on the Microsoft Data Platform (MSCE: Data Platform) and as a Solution Expert on the Microsoft Private Cloud (MSCE: Private Cloud). As a Microsoft Certified Trainer (MCT), I have taught multiple official Microsoft courses as well as other courses specializing in SQL Server. I have also presented sessions at official events for various Microsoft technologies user groups.
Rubén Garrigós

Latest posts by Rubén Garrigós (see all)