升级大对象(Large Object,即LOB)存储能力的关键原因是在Oracle 11g中数据安全需求越来越高,本文主要是研究如何扩充对LOB和表空间的透明数据加密(Transparent Data Encryption,即TDE)特性来提高数据的安全性,并解释这些特性是如何保护复杂的、非结构化数据的,如医学数字图像通讯信息(Digital Imaging for Communication of Medical Information,即DICOM)对象。
Oracle 11g新的SecureFile特性主要是集中在数据压缩和重复数据删除方面,主要是为了节约存储LOB对象的空间,当然在安全特性方面Oracle 11g不仅只有这两个,所有这些安全特性也非常符合最近当选的美国总统奥巴马颁布的议事日程,白宫的技术方向明确指明新的管理计划:
在电子信息技术系统方面增加投入以降低医疗保健方面的成本,使用卫生信息技术降低医疗保健方面的成本,每年投入100亿美元,到五年后让美国的医疗保健系统扩展为基于标准的电子医疗信息系统,包括电子健康记录。
加密LOB:把安全放进SecureFile
Oracle 11g现在把在SecureFile LOB中存储敏感信息放在非常重要的战略位置,因为这样才能够证明在Oracle 10gR2中推出的透明数据加密(TDE)的作用,TDE在列级提供了遵循工业标准的自动加密算法(如3DES168,AES128,AES192和AES256)。
1、开启透明数据加密
在开始使用透明数据加密特性之前,需要在数据库中进行一翻设置,幸运的是,在Oracle 11g数据库中这个设置非常简单了,因为现在只需要在数据库的网络配置文件中添加合适的配置目录即可,在之前的Oracle版本中,最简单的方法就是通过Oracle Wallet Manager utility设置这个“wallet”文件,欲了解前期版本是如何启用透明数据加密特性的,请参考我之前的文章“如何在Oracle 10g R2中实现透明数据加密”。
清单1中的内容显示了我在SQLNET.ORA网络配置文件中添加的内容,以便在我指定的目录中创建默认的TDE PKI密钥文件ewallet.p12,然后我使用ALTER SYSTEM SET ENCRYPTION KEY命令打开这个“wallet”并开启加密特性。
清单1 开启透明数据加密
在SQLNET.ORA网络配置文件中添加参数设置开启Oracle 11g数据库的透明数据加密功能
ENCRYPTION_WALLET_LOCATION = |
然后,打开wallet并设置加密密钥密码激活Oracle 11g的加密功能
SQL> ALTER SYSTEM SET ENCRYPTION KEY IDENTIFIED BY "r3aL1y!T16ht"; |
2、控制SecureFile加密
完成TDE设置后,在开启SecureFile LOB加密相对就简单了,和在Oracle表中开启其它类型的加密很类似,ENCRYPT告诉Oracle在现有SecureFile LOB上应用TDE加密,也可以通过DECRYPT告诉Oracle从SecureFile LOB上移除加密特性。
3、改变SecureFile加密算法或加密密钥
和其它Oracle数据类型一样,ALTER TABLE REKEY命令可以用来修改当前的加密算法,如默认的加密算法AES192改为AES256,TDE PKI密钥发生变化的话,REKEY命令也可以用于重新加密现有的SecureFile LOB。Oracle将会在块级进行加密,确保重新加密执行得更有效。
但请注意在相同的分区下对应的SecureFile LOB段只能够被修改为启用或禁用加密,如LOB段不能被REKEY,这是因为Oracle 11g在相同的LOB分区内对所有SecureFile LOB使用了相同的加密算法。
清单2显示这些命令的示例。
清单2 对已有的SecureFile LOB应用透明数据加密
应用默认的加密给单个SecureFile LOB
SQL> ALTER TABLE trbtkt.secure_tickets |
应用非默认的AES 256位加密算法给单个SecureFile LOB
SQL> ALTER TABLE trbtkt.secure_tickets |
为单个SecureFile LOB rekey加密
SQL> ALTER TABLE trbtkt.secure_tickets |
将加密应用给一个分区段
SQL> ALTER TABLE trbtkt.secure_tickets |
从单个SecureFile LOB中移除加密
SQL> ALTER TABLE trbtkt.secure_tickets |
加密表空间
Oracle 10g R2将它的加密能力扩展到数据库的任意表和索引了,但要识别和隔离那些需要加密的对象和列工作量很大,通常,可能仅仅是将许多在业务功能上相似的对象全部加密了,如数据库中所有封装了机密雇员信息的表和索引,为了使在这些情况下的加密变得更加简单易行,Oracle 11g现在可以加密整个表空间了。
表空间加密仍然是在块级实现的,但遗憾的是它不能在现有的表空间上执行,因此Oracle DBA必须在一开始创建表空间的时候就启用加密,然后Oracle DBA就可以使用ALTER TABLE MOVE命令来将表移动到加密表空间中,与此类似,已有的索引也可以通过重新创建命令ALTER INDEX REBUILD ONLINE,直接迁移到加密表空间中去。
和加密列一样,在创建加密表空间之前,数据库加密wallet必须先打开才行,通过CREATE TABLESPACE命令中新的ENCRYPTION指令,新的表空间将会自动应用指定的加密算法到所有存储在其内部的对象,默认采用的是AES 128位加密算法,但可以应用任意一个标准的加密算法(3DES168,AES128,AES192和AES256之一),如果不出什么问题的话,一个加密表空间可以传输到一个不同的Oracle 11g数据库中,只要源和目标数据库服务器使用了相同的endianness,并共享了相同的加密wallet即可。
但注意临时表空间和UNDO表空间不能使用这类加密算法,同样,扩展表源数据和扩展LOB(如BFILE)也不能加密。最后,由于加密密钥是在表级应用的,因此无法为加密表空间内的加密对象执行全局rekey,但在初始化加密表空间时可以使用这个方法来执行一次rekey操作。
为了说明加密表空间的特性,我创建了一个新的表空间PATIMAGES,采用了AES 256位加密算法,如清单3中的代码所示。我将使用这个新的表空间作为容器,存储后面我们将要讨论到的通过SecureFile在表中存储敏感的医疗信息:使用来自DICOM文件的源数据,在Oracle 11g中存储医疗影像和相关信息。
清单3 创建一个加密表空间存储敏感信息
DROP TABLESPACE patimages INCLUDING CONTENTS AND DATAFILES; |
#p#
DICOM:存储病人医疗影像和元数据的工业标准
美国放射学会(ACR)和国家电气制造联合会(NEMA)在1993年通过协作将医学影像和通讯(DICOM)标准化了,为医学影像设备和相关应用程序的开发提供了可供参照执行的依据,后来成为存储和传输放射影像通用的标准,Oracle 10g R2完全支持DICOM 3.0标准,但是直到Oracle 11g才实现了对DICOM数据进行加密。
Oracle 11g扩展了Oracle 10g R2 中ORDImage对象类型的能力,增加了一个新的ORDDicom对象类型,以便更有效地存储DICOM影像,由于ORDDicom对象可以存储在SecureFile LOB中,这样就可以实现压缩、重复数据删除和加密存储在DICOM文件中的影像和相关的元数据的能力,医生、大学和医院使用计算的病人信息,特别是诊断和研究。
Oracle 10gR2提供了转储病人信息到XML文件的能力,但现在的ORDDicom对象扩展了这些能力,使得可以直接在Oracle 11g数据库查询病人的元数据,这就意味着可以使用新的高级索引特性如XMLIndex数据类型来检索特殊的病人信息,此外,Oracle 11g现在还可以创建、存储、展现和检索存储在ORDDicom对象内的数据影像的指纹特征,也就是说将会更容易选中正确的病人信息,ORDDicom数据模型也可以帮助保证病人信息的机密性,因为它提供了一个方法可以避免泄露来自DICOM文件病人元数据中的私密信息。
1、创建DICOM对象
为了说明如何使用这些强大的功能特性增强Oracle 11g SecureFile的加密能力,我创建了一个新的方案和表MIPS.PATIENT_IMAGES,我将使用这个表存储DICOM元数据,并在ORDDicom和ORDImage数据类型列中存储影像数据,DICOM_IMAGE列将会存储来自对应的源文件直接载入的DICOM信息,ANONYMOUS列将会存储病人的敏感数据,THUMBPRINT列将会存储来自每个DICOM文件中第一个影像的指纹图像,清单4显示了我创建这个表的DDL语句。
清单4 创建加密的SecureFile LOB存储敏感信息
创建一个用户(MIPS)
DROP USER mips CASCADE;CREATE USER mips
IDENTIFIED BY mips
DEFAULT TABLESPACE patimages
TEMPORARY TABLESPACE temp
QUOTA UNLIMITED ON patimages;
GRANT CONNECT, RESOURCE TO mips;
GRANT EXECUTE ANY PROCEDURE to mips;
GRANT CREATE ANY DIRECTORY TO mips;
创建一个新的目录DROP DIRECTORY mips_imgs;
CREATE OR REPLACE DIRECTORY mips_imgs
AS '/home/oracle/dicom';
GRANT READ ON DIRECTORY mips_imgs TO mips;
创建一个新表MIPS.PATIENT_IMAGES使用SecureFile LOB存储DICOM文件。DROP TABLE mips.patient_images PURGE;
CREATE TABLE mips.patient_images (
patient_id NUMBER
,name VARCHAR2(30)
,ssn VARCHAR2(11)
,dob DATE
,dicom_image ORDSYS.ORDDICOM
,anonymous ORDSYS.ORDDICOM
,thumbprint ORDSYS.ORDIMAGE
)
TABLESPACE patimages
LOB(dicom_image.source.localData)
STORE AS SECUREFILE (
TABLESPACE patimages
DISABLE STORAGE IN ROW
DEDUPLICATE
COMPRESS HIGH
CACHE READS
)
LOB(anonymous.source.localData)
STORE AS SECUREFILE (
TABLESPACE patimages
DISABLE STORAGE IN ROW
DEDUPLICATE
COMPRESS HIGH
CACHE READS
)
LOB(thumbprint.source.localData)
STORE AS SECUREFILE (
TABLESPACE patimages
DISABLE STORAGE IN ROW
DEDUPLICATE
COMPRESS HIGH
CACHE READS
)
;
-- 注释
COMMENT ON TABLE mips.patient_images
IS 'Contains Patient metadata and DICOM images';
COMMENT ON COLUMN mips.patient_images.patient_id
IS 'Unique identifier for a Patient';
COMMENT ON COLUMN mips.patient_images.name
IS 'Patient Name';
COMMENT ON COLUMN mips.patient_images.ssn
IS 'Patient Social Security Number';
COMMENT ON COLUMN mips.patient_images.dob
IS 'Patient Date of Birth';
COMMENT ON COLUMN mips.patient_images.dicom_image
IS 'DICOM LOB';
COMMENT ON COLUMN mips.patient_images.anonymous
IS 'Anonymous DICOM LOB';
COMMENT ON COLUMN mips.patient_images.thumbprint
IS 'Thumbprint of DICOM image';
-- 创建索引和约束
CREATE UNIQUE INDEX mips.patient_images_pk_idx
ON mips.patient_images(patient_id)
TABLESPACE patimages;
ALTER TABLE mips.patient_images
ADD CONSTRAINT patient_images_pk
PRIMARY KEY (patient_id);
2、从DICOM源文件转移到ORDDicom对象
现在我已经为DICOM信息创建好了适当的资料库,我将要使用SQL*Loader直接从DICOM源文件中提取并格式化病人元数据,对于我的源数据,我将使用若干个简单的DICOM文件,这些DICOM文件都是从http://www.barre.nom.fr/medical/下载得到的,病人的姓名和其它信息完全是虚构的,只是为了说明这些概念,清单5显示了SQL*Loader用于初始化、格式化和从这些源文件中载入DICOM数据到ORDDicom和ORDImage数据类型列的参数文件,以及调用SQL*Loader执行这个初始化数据载入的结果。
清单5 使用SQL*Loader载入DICOM原始文件到Oracle 11g数据库中
-- SQL*Loader参数文件: LoadDICOMFromFile.sqlparms
-- 目的: 这个参数文件将直接从DICOM文件载入DICOM数据到MIPS.PATIENT_IMAGES 表的DICOM_IMAGE 列,它也控制了ANONYMOUS列(它容纳匿名的DICOM数据)和THUMBPRINT列(它容纳DICOM图像本身的指纹图像)的初始化。
LOAD DATAINFILE *
INTO TABLE mips.patient_images
TRUNCATE
FIELDS TERMINATED BY WHITESPACE
OPTIONALLY ENCLOSED BY '"'
(
patient_id INTEGER EXTERNAL
,name CHAR
,ssn CHAR
,dob DATE "yyyy-mm-dd"
,fn FILLER CHAR
-- 载入列对象MIPS.PATIENT_IMAGES.DICOM_IMAGE:
-- 1.) LOB属性source.localData和DICOM数据一起载入。
-- 2.) 属性srcType被设置为"local"。
-- 3.) 属性updateTime被初始化到当前日期。
-- 4.) LOB属性扩展使用空的LOB进行初始化。
,dicom_image COLUMN OBJECT (
source COLUMN OBJECT (
localData LOBFILE(fn) TERMINATED BY EOF
,srcType CONSTANT 'local'
,updateTime EXPRESSION "SYSDATE"
)
,extension LOBFILE(fn) TERMINATED BY EOF
DEFAULTIF dicom_image.source.srcType='local'
)
-- 初始化(但不载入)列对象MIPS.PATIENT_IMAGES.ANONYMOUS:
-- 1.) LOB属性source.localData和扩展被初始化。
-- 2.) srcType属性被初始化为"local"。
-- 3.) localData LOB将容纳DICOM数据的匿名内容。
-- 4.) srcType属性被初始化为"local"。
-- 5.) LOB扩展是一个由ORDDICOM使用的内部字段。
,anonymous COLUMN OBJECT (
source COLUMN OBJECT (
localData LOBFILE(fn) TERMINATED BY EOF
DEFAULTIF anonymous.source.srcType='local'
,srcType CONSTANT 'LOCAL'
)
,extension LOBFILE(fn) TERMINATED BY EOF
DEFAULTIF dicom_image.source.srcType='local'
)
-- 初始化(但不载入)列对象MIPS.PATIENT_IMAGES.THUMBPRINT:
-- 1.) LOB属性扩展被一个空的LOB初始化。
-- 2.) LOB属性source.localData被一个空的LOB初始化。
-- 3.) 本地属性被初始化为1。
,thumbprint COLUMN OBJECT (
source COLUMN OBJECT (
localData LOBFILE(fn) TERMINATED BY EOF
DEFAULTIF thumbprint.source.local=X'1'
,local CONSTANT 1
)
)
)
BEGINDATA
101 "Ames, Aldritch" 322-51-1111 1907-12-01 CT-MONO2-16-ankle.dcm
202 "Barry, Dave" 345-21-2222 1968-12-24 OT-MONO2-8-colon.dcm
303 "Colson, Charles" 342-43-3333 1931-11-03 MR-MONO2-8-16x-heart.dcm
404 "Dean, John" 322-09-4444 1942-02-18 NM-MONO2-16-13x-heart.dcm
505 "Ehrlichman, John" 345-09-5555 1914-08-01 US-PAL-8-10x-echo.dcm
606 "Haldeman, Robert" 322-18-6666 1918-11-11 US-RGB-8-esopecho.dcm
>>> SQL*Loader results:
SQL*Loader: Release 11.1.0.6.0 - Production on Sat Mar 14 13:00:40 2009
Copyright (c) 1982, 2007, Oracle. All rights reserved.
Control File: LoadDICOMFromFile.sqlparms
Data File: LoadDICOMFromFile.sqlparms
Bad File: LoadDICOMFromFile.bad
Discard File: none specified
(Allow all discards)
Number to load: ALL
Number to skip: 0
Errors allowed: 50
Bind array: 64 rows, maximum of 256000 bytes
Continuation: none specified
Path used: Conventional
Table MIPS.PATIENT_IMAGES, loaded from every logical record.
Insert option in effect for this table: TRUNCATE
Column Name Position Len Term Encl Datatype
------------------------------ ---------- ----- ---- ---- ---------------------
PATIENT_ID FIRST * WHT O(") CHARACTER
NAME NEXT * WHT O(") CHARACTER
SSN NEXT * WHT O(") CHARACTER
DOB NEXT * WHT O(") DATE yyyy-mm-dd
FN NEXT * WHT O(") CHARACTER
(FILLER FIELD)
DICOM_IMAGE DERIVED * COLUMN OBJECT
*** Fields in DICOM_IMAGE
SOURCE DERIVED * COLUMN OBJECT
*** Fields in DICOM_IMAGE.SOURCE
LOCALDATA DERIVED * EOF CHARACTER
Dynamic LOBFILE. Filename in field FN
SRCTYPE CONSTANT
Value is 'local'
UPDATETIME EXPRESSION
SQL string for column : "SYSDATE"
*** End of fields in DICOM_IMAGE.SOURCE
EXTENSION DERIVED * EOF CHARACTER
Dynamic LOBFILE. Filename in field FN
DEFAULT if DICOM_IMAGE.SOURCE.SRCTYPE = 0X6c6f63616c(character 'local')
*** End of fields in DICOM_IMAGE
ANONYMOUS DERIVED * COLUMN OBJECT
*** Fields in ANONYMOUS
SOURCE DERIVED * COLUMN OBJECT
*** Fields in ANONYMOUS.SOURCE
LOCALDATA DERIVED * EOF CHARACTER
Dynamic LOBFILE. Filename in field FN
DEFAULT if ANONYMOUS.SOURCE.SRCTYPE = 0X6c6f63616c(character 'local')
SRCTYPE CONSTANT
Value is 'LOCAL'
*** End of fields in ANONYMOUS.SOURCE
EXTENSION DERIVED * EOF CHARACTER
Dynamic LOBFILE. Filename in field FN
DEFAULT if DICOM_IMAGE.SOURCE.SRCTYPE = 0X6c6f63616c(character 'local')
*** End of fields in ANONYMOUS
THUMBPRINT DERIVED * COLUMN OBJECT
*** Fields in THUMBPRINT
SOURCE DERIVED * COLUMN OBJECT
*** Fields in THUMBPRINT.SOURCE
LOCALDATA DERIVED * EOF CHARACTER
Dynamic LOBFILE. Filename in field FN
DEFAULT if THUMBPRINT.SOURCE.LOCAL = 0X01(character '')
LOCAL CONSTANT
Value is '1'
*** End of fields in THUMBPRINT.SOURCE
*** End of fields in THUMBPRINT
Table MIPS.PATIENT_IMAGES:
6 Rows successfully loaded.
0 Rows not loaded due to data errors.
0 Rows not loaded because all WHEN clauses were failed.
0 Rows not loaded because all fields were null.
Space allocated for bind array: 83456 bytes(64 rows)
Read buffer bytes: 1048576
Total logical records skipped: 0
Total logical records read: 6
Total logical records rejected: 0
Total logical records discarded: 0
Run began on Sat Mar 14 13:00:40 2009
Run ended on Sat Mar 14 13:00:49 2009
Elapsed time was: 00:00:09.65
CPU time was: 00:00:00.61
3、生成指纹图像和匿名的元数据
为了完成ANONYMOUS和THUMBPRINT列的填充,我将使用一些技巧:
(1)首先,我将通过调用setModel 方法载入ORDDicom对象模型,这个对象模型必须在调用其它方法之前载入。
(2)然后使用processCopy方法生成一张150x200的JPEG图像,将其存储到THUMBPRINT列中。
(3)最后,我使用makeAnonymous方法创建一个匿名版本的DICOM_IMAGE列数据,并将其存储到ANONYMOUS列中。
清单6 显示了如何在最初被载入MIPS.PATIENT_IMAGES表中的所有行上执行这些方法。
清单6 从前面载入的ORDDicom对象创建和存储匿名的病人信息和指纹图像:
1)将DICOM模型载入到内存中。
2)将原始的DICOM文件元数据转换成对应的元素。
3)基于原始的DICOM影像以JPEG格式创建一个缩略图。
4)生成一个匿名的DICOM文件版本。
5)将新的信息保存到它们对应的SecureFile LOB中。
SET SERVEROUTPUT ONDECLARE
dcm_row ORDSYS.ORDDICOM;
BEGIN
-- 载入DICOM数据模型
ord_dicom.setDatamodel;
-- 在医学图像表中遍历所有行
FOR dcm_row IN (
SELECT
patient_id
,dicom_image
,anonymous
,thumbprint
FROM mips.patient_images
FOR UPDATE
)
LOOP
BEGIN
-----
--通过setProperties存储过程初始化DICOM SecureFile LOB列属性
-----
dcm_row.dicom_image.setProperties();
-----
-- 在原始DICOM影像的基础上构建一个缩略图图像
-----
dcm_row.dicom_image.processCopy('fileFormat=JPEG fixedScale=150,200', dcm_row.thumbprint);
-----
-- 通过makeAnonymous()函数生成DICOM对象的匿名版本
-----
dcm_row.dicom_image.makeAnonymous(genUID(dcm_row.patient_id), dcm_row.anonymous);
-----
-- 将新产生的信息保存到它们对应的SecureFile LOB中
-----
UPDATE mips.patient_images
SET dicom_image = dcm_row.dicom_image
,anonymous = dcm_row.anonymous
,thumbprint = dcm_row.thumbprint
WHERE patient_id = dcm_row.patient_id;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Error processing image for Patient ID #' || dcm_row.patient_id);
END;
END LOOP;
COMMIT;
END;
/
4、使用ORDDicom和ORDImage列的内容
最后,我已经准备好研究这些已经载入到DICOM列的DICOM信息,幸运的是,ORDDicom数据类型使得这些工作变得相当简单,因为它提供了多种方法来直接查询病人的元数据属性,清单7显示了一个简单的查询,它直接从DICOM_IMAGE和THUMBPRINT列查询多个属性。
清单7 查看已经载入到ORDDICOM和ORDIMAGE对象中的元数据
显示选择的内容:
1)普通数据类型
2)ORDDICOM数据类型
3)ORDIMAGE数据类型
SET LINESIZE 80SET PAGESIZE 80
TTITLE 'Sample Patient Metadata|(from MIPS.PATIENT_IMAGES)'
COLUMN patient_id FORMAT 99999 HEADING 'Pat|ID'
COLUMN name FORMAT A20 HEADING 'Patient Name' WRAP
COLUMN ssn FORMAT A11 HEADING 'Patient SSN'
COLUMN di_sop_uid FORMAT A30 HEADING 'DICOM Image SOP UID' WRAP
COLUMN tp_len FORMAT 99999 HEADING 'Thumb|Print|Image|Size'
SELECT
PI.patient_id
,PI.name
,PI.ssn
,PI.dicom_image.sop_instance_uid AS di_sop_uid
,PI.thumbprint.getcontentlength() AS tp_len
FROM mips.patient_images PI
ORDER BY PI.patient_id
;
TTITLE OFF
Sun Mar 15 page 1
Sample Patient Metadata
(from MIPS.PATIENT_IMAGES)
Thumb
Pat Image
ID Patient Name Patient SSN DICOM Image SOP UID Size
------ -------------------- ----------- ------------------------------ ------
101 Ames, Aldritch 322-51-1111 1.2.840.113619.2.1.2411.103115 5034
2382.365.1.736169244
202 Barry, Dave 345-21-2222 1.3.46.670589.17.1.7.0.16 5677
303 Colson, Charles 342-43-3333 999.999.2.19960619.163000.1.10 3648
3
404 Dean, John 322-09-4444 2.16.840.1.113662.5.8796818449 1596
476.121423489.1.1.3101.5309511
143
505 Ehrlichman, John 345-09-5555 999.999.133.1996.1.1800.1.6.25 5252
606 Haldeman, Robert 322-18-6666 999.999.2.19941105.112000.2.10 4683
7
校验存储在ORDDicom或ORDImage数据类型列中的图像需要一点技巧,因为需要一个接口才能查看这些图像,因为这些图像都采用基于工业标准的格式存储,如JPEG,TIF,PNG等,所以有大量的免费图像查看程序可以拿来使用。
小结
Oracle 11g新的SecureFile特性大大扩展在Oracle数据库中存储大对象的能力,并提高了安全性和有效性,SecureFILE LOB的压缩和重复数据删除功能在空间利用上更是让人惊讶,SecureFILE LOB的透明数据加密特性让存储在Oracle 11g数据库中的敏感信息和机密信息安全性更有保障,这些特性让Oracle 11g数据库成为美国新的联邦政府在启动存储医院、病人和医疗元数据方面的主要候选数据库。
【编辑推荐】