安全问题一直是头条新闻,但近年来变得越来越重要。 企业必须遵守行业和政府法规,以确保其敏感信息(例如信用卡号,社会保险号,工资信息,与健康相关的信息等)始终是安全的。 为此,强大的数据加密解决方案是必不可少的。
在7.1版之前的DB2 for i版本中,使用列触发器来自动进行加密。 尽管非SQL本机接口无法自动解密数据,但是SQL接口可以半透明地使用SQL视图进行解密,但是这种方法需要大量的精力并且很不方便。
DB2 i 7.1版提供了一种称为现场过程( FieldProc
)的启用技术,以提供透明的列级加密实现。 通过使用CREATE TABLE
和ALTER TABLE
的FIELDPROC
子句,可以将字段过程注册到列。 现场过程是用户编写的退出例程,旨在在单个列中转换值。 当更改列中的值或插入新值时,将为每个值调用字段过程,并可以以任何方式转换(编码)该值,然后将其存储。 从列中检索值时,将为每个编码值调用字段过程,然后将其解码回原始值。 在使用字段过程的非派生列上定义的所有索引都是使用编码值构建的。
现场程序支持的优势
您将通过以下方式受益于DB2 for i 7.1中新引入的现场过程加密支持:
- 安全加密。 存储在硬盘或磁带上的数据,索引和日志是数据的转换(加密)版本。 没有FieldProc程序,没有人可以获得解密后的数据。
- 更容易使用。 要启用现场过程,您只需要编写一个FieldProc程序(或调用第三方加密/解密函数),然后将其注册到表中即可。
- 更大的灵活性。 通过添加,删除或移至其他FieldProc程序,您可以轻松地更改为其他加密方法。 请注意,即使编码值可以比字段的定义值长,但它还是灵活的,可以通过FieldProc程序进行更改。 无需更改原始表定义。
现场程序支持的性能考虑
尽管现场过程使您可以透明地实现加密和解密而无需更改应用程序,但是这种灵活性确实会影响性能。 注册现场过程程序会给每个从该列写入或读取值的接口增加开销(类似于外部程序调用的开销)。 现场过程程序本身执行的操作也将影响性能开销。 例如,加密和解密处理因它们需要的CPU周期而臭名昭著。 因此,性能测试应该是现场过程实施的关键部分。
如何使用FieldProc
作为应用程序开发人员,您可以简单地使用FIELDPROC
子句将字段过程注册到表中。 在以下示例中,将使用通过现场过程加密的敏感数据(工资)创建一个雇员表:
Create TABLE employee
(
ID char(10),
salary decimal(10,2) FieldProc FP.userpgm
)
或者可以更改现有的employee表以加密salary列中的敏感数据:
ALTER TABLE employee alter column salary set FieldProc FP.userpgm
//We can alter to add a FieldProc on both SQL table and Native table.
当然,也可以删除该字段过程,并且可以使用另一个字段过程来加密敏感数据:
ALTER TABLE FP.userpgm alter column salary drop FieldProc
// drop the old field procedure
ALTER TABLE employee alter column salary set FieldProc FP.userpgm2
//then add the new one
受支持的列属性的限制
DB2 i 7.1版支持使用字段过程对任何数据类型进行加密,但ROWID, IDENTITY
和ROW CHANGE TIMESTAMP
除外。
注意:每个表中只能在FieldProc中添加一个LOB列。
调用现场过程时
在三种常见情况下,将调用为列指定的字段过程:
- 在字段定义的情况下,当执行命名过程的
CREATE TABLE
或ALTER TABLE
语句时。 在此调用期间,期望该过程确定列的数据类型和属性的有效性,验证文字列表并提供列的字段描述。 - 对于字段编码,当要对字段值进行字段编码时。 对于通过SQL
INSERT
或MERGE
语句插入的任何值,通过本机写入插入的任何值,或者通过更新更改的任何值,或者将数据复制到注册了字段过程的目标列时,都会发生这种情况。 - 在字段解码的情况下,当存储的值将被字段解码回其原始值时。 对于通过SQL
SELECT
或FETCH
语句,通过本机读取或将数据复制到目标列而未注册字段过程的任何值,都会发生这种情况。
如何编写现场程序
下一节提供了三个代码示例,以说明典型的FieldProc程序实现。
在介绍现场程序之前,有必要解释下面列出的几个关键新术语。 有关详细信息,请参阅《 SQL编程指南》 。
- 外部格式:将从数据库引擎的程序接收或返回的列数据的格式。
- 内部格式:数据库引擎内部存储的列数据的格式。
- 字段编码:字段过程对值执行的转换。
- 字段解码:同一例程,用于在获取值时撤消转换。
- 字段定义:创建或更改表时,还会调用字段过程,以定义DB2的数据类型和编码值的属性。
注意:如果数据类型具有任何允许SQL数据类型,则用户定义的数据类型可以是有效字段。 DB2在将列的值传递给字段过程之前,将其值强制转换为源类型。
- 列值描述符(CVD):在字段编码过程中,CVD描述了要编码的值以及由字段过程提供的解码后的值。 在字段定义期间,它描述了
CREATE TABLE
或ALTER TABLE
语句中定义的列。 - 现场过程参数列表:现场过程参数列表将一般信息传达给现场过程,告知要执行的操作,并允许现场过程发出错误信号。 DB2为传递给现场过程的所有参数提供存储,因此,参数按地址传递给现场过程。
- 列表中包含以下8个参数:
- 参数1:一个小的(2字节)整数,描述要执行的功能。 此参数仅输入,并支持以下值:
- 0-场编码
- 4-场解码
- 8-栏位定义
- 参数2:这是定义现场过程参数值列表(FPPVL)的结构。 对于功能代码8,此参数为输入/输出。 对于功能代码0和4,此参数包含功能代码8调用的输出。 该参数仅输入。
- 参数3:这是由列值描述符(CVD)定义的解码数据属性。 这是在
CREATE TABLE
或ALTER TABLE
时指定的列属性。 该参数仅输入。 - 参数4:这是解码的数据。 此参数的用法取决于功能代码。
- 参数5:这是由字段值描述符(FVD)定义的
Internal Encoded Data
属性。 - 参数6:这是由字段值描述符(FVD)定义的编码数据。 此参数的用法取决于功能代码。
- 参数7:
SQLSTATE
是character(5)。 此参数是输入/输出。注意:此参数从设置为
00000
的数据库中传入,并且可以由现场过程设置为现场过程的结果状态。 通常,现场过程不会设置SQLSTATE
,但可以使用它来向数据库发出信号,如下所示:38xxx
功能代码检测到错误情况,从而导致SQL错误。xxx
可能是几个可能的字符串之一。 现场过程不支持警告。 - 参数8:消息文本区域为
VARCHAR(1000)
。 此参数是输入/输出。注:在调用字段过程之前,DB2将此参数设置为空字符串。 它是一个
VARCHAR(1000)
值,当现场过程发出SQLSTATE
错误信号时,现场过程可以使用该值将消息文本发送回去。 它由数据库在输入到现场过程时初始化,并且可以由现场过程使用描述性信息进行设置。 除非字段过程设置了SQLSTATE
参数,否则DB2将忽略消息文本。 消息文本假定在作业CCSID中。
- 参数1:一个小的(2字节)整数,描述要执行的功能。 此参数仅输入,并支持以下值:
清单1.此示例显示了如何使用加密/解密程序或第三方加密/解密函数进行加密。 该程序会加密和解密仅包含数字的16个字符串。
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <SQLFP.h>
void DESC(unsigned char *data,
unsigned char *mkey, char ctag[2])
{
... //user created or third party encryption algorithm.
}
main(int argc, void *argv[])
{
short *funccode = argv[1];
sqlfpFieldProcedureParameterList_T *optionalParms = argv[2];
char *sqlstate = argv[7];
sqlfpMessageText_T *msgtext = argv[8];
sqlfpOptionalParameterValueDescriptor_T *optionalParmPtr;
char KEY[16]="0123456789ABCDEF";
if (*funccode == 8) /* create time */
{
sqlfpParameterDescription_T *inDataType = argv[3];
sqlfpParameterDescription_T *outDataType = argv[5];
if (inDataType->sqlfpSqlType !=452 &&
inDataType->sqlfpSqlType !=453 ) /* only support fixed length */
memcpy(sqlstate,"38002",5);
/* do something here to determine the result data type */
/* ..... */
/* in this example input and output types are exactly the same */
/* so just copy */
memcpy(outDataType, inDataType, sizeof(sqlfpParameterDescription_T));
}
else if (*funccode == 0) /* encode */
{
memcpy((char *)argv[6], (char *)argv[4], 16);
DESC((char*)argv[6], KEY, "0");
}
else if (*funccode == 4) /* decode */
{
memcpy((char *)argv[4], (char *)argv[6], 16);
memcpy((char *)argv[4], (char *)argv[6], 16);
DESC((char *)argv[4], KEY, "1");
}
else /* unsupported option -- error */
memcpy(sqlstate, "38003",5);
}
清单2.此样本显示了如何使用IBM i API进行加密和解密。 该程序可用于加密和解密固定长度的char,varchar或clob。
<!-- In this example, there are some points which need attention: -->
<!-- a. The input/output data type could be a fixed char or varchar or clob. -->
<!-- But the block size of the encryption should be 16x(multiple time of 16).-->
<!-- So, we should define enough space to store the encryption result. -->
<!-- b. Avoid using pad option. Pad option will add a count of the pad characters -->
<!-- in the end of original string.-->
<!-- It may conduct one more block of the encryption result and the -->
<!-- column does not have enough space to store. -->
<!-- c. For better performance, ACTGRP(*CALLER), TERASPACE(*YES) and STGMDL(*INHERIT)-->
<!-- were recommended when you compile your program.-->
<!-- d. More information about IBM i Cryptographic Services APIs, -->
<!-- please refer to the link of the IBM i infocenter below:-->
<!-- http://publib.boulder.ibm.com/infocenter/iseries/7.1m0/index.jsp -->
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <QC3CCI.H>
#include <QUSEC.H>
#include <QC3DTAEN.H>
#include <QC3DTADE.H>
#include <SQLFP.h>
<!-- --------------------- -->
<!-- SQL data type define. -->
<!-- --------------------- -->
<!-- SQL data type CLOB -->
#define SQL_CLOB_1 408
#define SQL_CLOB_2 409
<!-- SQL data type VARCHAR -->
#define SQL_VARCHAR_1 448
#define SQL_VARCHAR_2 449
<!-- SQL data type CHAR -->
#define SQL_CHAR_1 452
#define SQL_CHAR_2 453
<!-- ------------------------------ -->
<!-- Varlength SQL data type define. -->
<!-- ------------------------------ -->
typedef _Packed struct
{
unsigned short int len;
char data[512];
}T_VARCHAR;
typedef _Packed struct
{
unsigned long len;
char data[512];
}T_CLOB;
Qc3_Format_ALGD0200_T *ALGD0200;
Qc3_Format_KEYD0200_T *KEYD0200;
Qus_EC_t ERRCODE;
main(int argc, void *argv[])
{
T_VARCHAR VarCharStr;
T_CLOB ClobStr;
short *funccode = argv[1];
sqlfpFieldProcedureParameterList_T *optionalParms = argv[2];
char *sqlstate = argv[7];
sqlfpMessageText_T *msgtext = argv[8];
sqlfpOptionalParameterValueDescriptor_T *optionalParmPtr;
char Clear_Data[512];
char Encrypted_Data[512];
char Decrypted_Data[512];
int InputDataLen;
int EncryptedDataLen;
int DecryptedDataLen;
int RtnLen;
int i;
char Qc3_Any_CSP_Flag = Qc3_Any_CSP;
ALGD0200 = (Qc3_Format_ALGD0200_T *)malloc(sizeof(Qc3_Format_ALGD0200_T));
ALGD0200->Block_Cipher_Alg = Qc3_AES;
ALGD0200->Block_Length = 16;
ALGD0200->Mode = Qc3_ECB;
ALGD0200->Pad_Option = Qc3_No_Pad;
ALGD0200->Pad_Character = '\x00' ;
ALGD0200->MAC_Length = 0;
ALGD0200->Effective_Key_Size = 0;
ALGD0200->Reserved = '\x00';
memset(ALGD0200->Init_Vector,'\x00',32);
KEYD0200 = (Qc3_Format_KEYD0200_T *)malloc(sizeof(Qc3_Format_KEYD0200_T) +16);
KEYD0200->Key_Type = Qc3_AES ;
KEYD0200->Key_String_Len = 16;
KEYD0200->Key_Format = Qc3_Bin_String;
memcpy((char *)KEYD0200->Reserved + 3, "0123456789ABCDEF", 16);
if (*funccode == 8) /* create time */
{
sqlfpParameterDescription_T *inDataType = argv[3];
sqlfpParameterDescription_T *outDataType = argv[5];
/* do something here to determine the result data type */
/* ..... */
/* in this example input and output types are exactly the same */
/* so just copy */
memcpy(outDataType, inDataType, sizeof(sqlfpParameterDescription_T));
}
else if (*funccode == 0) /* encode */
{
sqlfpParameterDescription_T *inDataType = argv[3];
InputDataLen = inDataType->sqlfpByteLength;
if (inDataType->sqlfpSqlType == SQL_VARCHAR_1 ||
inDataType->sqlfpSqlType == SQL_VARCHAR_2)
{
memcpy((char *)&VarCharStr, (char *)argv[4], InputDataLen+2);
InputDataLen = VarCharStr.len;
memcpy((char *)Clear_Data, (char *)VarCharStr.data, InputDataLen);
if (InputDataLen % 16 > 0 || InputDataLen == 0)
{
memset((char *)Clear_Data + InputDataLen, '\x00',
16 - InputDataLen % 16);
InputDataLen = ((int)(InputDataLen / 16) + 1) * 16;
}
}
else if (inDataType->sqlfpSqlType == SQL_CLOB_1 ||
inDataType->sqlfpSqlType == SQL_CLOB_2)
{
memcpy((char *)&ClobStr, (char *)argv[4], InputDataLen+4);
InputDataLen = ClobStr.len;
memcpy((char *)Clear_Data, (char *)ClobStr.data, InputDataLen);
if (InputDataLen % 16 > 0 || InputDataLen == 0)
{
memset((char *)Clear_Data + InputDataLen, '\x00',
16 - InputDataLen % 16);
InputDataLen = ((int)(InputDataLen / 16) + 1) * 16;
}
}
else
memcpy((char *)Clear_Data, (char *)argv[4], InputDataLen);
memset(Encrypted_Data,'\x00',sizeof(Encrypted_Data));
EncryptedDataLen = sizeof(Encrypted_Data);
Qc3EncryptData(Clear_Data,
&InputDataLen,
Qc3_Data,
(char *)ALGD0200,
Qc3_Alg_Block_Cipher,
(char *)KEYD0200,
Qc3_Key_Parms,
&Qc3_Any_CSP_Flag,
" ",
Encrypted_Data,
&EncryptedDataLen,
&RtnLen,
&ERRCODE);
if (inDataType->sqlfpSqlType == SQL_VARCHAR_1 ||
inDataType->sqlfpSqlType == SQL_VARCHAR_2)
{
VarCharStr.len = RtnLen;
memcpy((char *)VarCharStr.data, (char *)Encrypted_Data, RtnLen);
memcpy((char *)argv[6], (char *)&VarCharStr, RtnLen+2);
}
else if (inDataType->sqlfpSqlType == SQL_CLOB_1 ||
inDataType->sqlfpSqlType == SQL_CLOB_2)
{
ClobStr.len = RtnLen;
memcpy((char *)ClobStr.data, (char *)Encrypted_Data, RtnLen);
memcpy((char *)argv[6], (char *)&ClobStr, RtnLen+4);
}
else
memcpy((char *)argv[6], (char *)Encrypted_Data, RtnLen);
}
else if (*funccode == 4) /* decode */
{
sqlfpParameterDescription_T *inDataType = argv[3];
InputDataLen = inDataType->sqlfpByteLength;
if (inDataType->sqlfpSqlType == SQL_VARCHAR_1 ||
inDataType->sqlfpSqlType == SQL_VARCHAR_2)
{
memcpy((char *)&VarCharStr, (char *)argv[6], InputDataLen+2);
InputDataLen = VarCharStr.len;
memcpy((char *)Encrypted_Data, (char *)VarCharStr.data, InputDataLen);
}
else if (inDataType->sqlfpSqlType == SQL_CLOB_1 ||
inDataType->sqlfpSqlType == SQL_CLOB_2)
{
memcpy((char *)&ClobStr, (char *)argv[6], InputDataLen+4);
InputDataLen = ClobStr.len;
memcpy((char *)Encrypted_Data, (char *)ClobStr.data, InputDataLen);
}
else
memcpy((char *)Encrypted_Data, (char *)argv[6], InputDataLen);
memset(Decrypted_Data,'\x00',sizeof(Decrypted_Data));
DecryptedDataLen = sizeof(Decrypted_Data);
Qc3DecryptData(Encrypted_Data,
&InputDataLen,
(char *)ALGD0200,
Qc3_Alg_Block_Cipher,
(char *)KEYD0200,
Qc3_Key_Parms,
&Qc3_Any_CSP_Flag,
" ",
Decrypted_Data,
&DecryptedDataLen,
&RtnLen,
&ERRCODE);
if (inDataType->sqlfpSqlType == SQL_VARCHAR_1 ||
inDataType->sqlfpSqlType == SQL_VARCHAR_2)
{
VarCharStr.len = strlen(Decrypted_Data);
memcpy((char *)VarCharStr.data, (char *)Decrypted_Data, VarCharStr.len);
memcpy((char *)argv[4], (char *)&VarCharStr, VarCharStr.len+2);
}
else if (inDataType->sqlfpSqlType == SQL_CLOB_1 ||
inDataType->sqlfpSqlType == SQL_CLOB_2)
{
ClobStr.len = strlen(Decrypted_Data);
memcpy((char *)ClobStr.data, (char *)Decrypted_Data, ClobStr.len);
memcpy((char *)argv[4], (char *)&ClobStr, ClobStr.len+4);
}
else
memcpy((char *)argv[4], (char *)Decrypted_Data, RtnLen);
}
else /* unsupported option -- error */
memcpy(sqlstate, "38003",5);
}
清单3.此示例是FieldProc
的RPG版本的代码示例。 它会将lob或varchar列中的字符反转为加密方法。
D FuncCode S 2B 0
D p_FuncCode S *
D OptParms DS LikeDs(SQLFOPVD)
D*
D EnCodTyp DS LikeDs(SQLFPD)
D*
D DeCodTyp DS LikeDs(SQLFPD)
D*
D EnCodDta S 512
D DeCodDta S 512
D*
D SqlState S 5
D SqMsgTxt DS LikeDs(SQLFMT)
D*
D En_Lob_Ds Ds Qualified
D Len 5B 0
D Data 1 dim(512)
D
D De_Lob_Ds Ds LikeDs(En_Lob_Ds)
D
D En_VChar_Ds Ds Qualified
D Len 2B 0
D Data 1 dim(512)
D
D De_VChar_Ds Ds LikeDs(En_VChar_Ds)
D
D i S 10I 0
D
/COPY QSYSINC/QRPGLESRC,SQLFP
C *Entry Plist
C Parm FuncCode
C Parm OptParms
C Parm DeCodTyp
C Parm DeCodDta
C Parm EnCodTyp
C Parm EnCodDta
C Parm SqlState
C Parm SqMsgTxt
/Free
If FuncCode = 8 ;
// do something here to determine the result data type
// .....
// in this example input and output types are exactly the same
// so just copy
EnCodTyp = DeCodTyp ;
ElseIf FuncCode = 0 ; // encode
If DeCodTyp.SQLFST = 408 or DeCodTyp.SQLFST = 409 ; // clob
// in this example, reverse the characters as encryption
De_Lob_Ds = DeCodDta ;
En_Lob_Ds.Len = De_Lob_Ds.Len ;
i = De_Lob_Ds.Len ;
DoW i > 0 ;
En_Lob_Ds.Data(De_Lob_Ds.Len-i+1) = De_Lob_Ds.Data(i) ;
i = i - 1 ;
EndDo ;
EnCodDta = En_Lob_Ds ;
ElseIf DeCodTyp.SQLFST = 448 or DeCodTyp.SQLFST = 449 ; // varchar
// in this example, reverse the characters as encryption
De_VChar_Ds = DeCodDta ;
En_VChar_Ds.Len = De_VChar_Ds.Len ;
i = De_VChar_Ds.Len ;
DoW i > 0 ;
En_VChar_Ds.Data(De_VChar_Ds.Len-i+1) = De_VChar_Ds.Data(i) ;
i = i - 1 ;
EndDo ;
EnCodDta = En_VChar_Ds ;
Else ; // other data type, just put the same value.
EnCodDta = DeCodDta ;
EndIf ;
SqlState = '00000' ;
ElseIf FuncCode = 4 ; // decode
If EnCodTyp.SQLFST = 408 or EnCodTyp.SQLFST = 409 ; // clob
// in this example, reverse the characters as decryption
En_Lob_Ds = EnCodDta ;
De_Lob_Ds.Len = En_Lob_Ds.Len ;
i = En_Lob_Ds.Len ;
DoW i > 0 ;
De_Lob_Ds.Data(En_Lob_Ds.Len-i+1) = En_Lob_Ds.Data(i) ;
i = i - 1 ;
EndDo ;
DeCodDta = De_Lob_Ds ;
ElseIf EnCodTyp.SQLFST = 448 or EnCodTyp.SQLFST = 449 ; // varchar
En_VChar_Ds = EnCodDta ;
De_VChar_Ds.Len = En_VChar_Ds.Len ;
i = En_VChar_Ds.Len ;
DoW i > 0 ;
De_VChar_Ds.Data(En_VChar_Ds.Len-i+1) = En_VChar_Ds.Data(i) ;
i = i - 1 ;
EndDo ;
DeCodDta = De_VChar_Ds ;
Else ; // other data type, just put the same value.
DeCodDta = EnCodDta ;
EndIf ;
SqlState = '00000' ;
Else ;
SqlState = '38003' ;
EndIf ;
*InLR = *On ;
Return ;
/End-Free
使用FieldProc时的特殊注意事项
在采用现场程序支持时,应考虑一些准则。 关键项目包括:
- 字段过程必须是ILE * PGM对象。 请注意,不支持* SRVPGM,OPM * PGM和JAVA对象。
- 现场过程中不允许使用SQL。
- 现场过程必须是确定性的。 使用不确定的现场过程非常危险,因为编码后的数据可能无法解码回其原始值。
- 当将更改物理文件(CHGPF)命令与
SRCFILE
参数一起使用来更改物理文件的字段定义时,CHGPF命令将删除该物理文件上所有已注册的字段过程,而不会发出任何警告消息。
有关更多限制,请参考SQL参考以获取详细信息。
结论
本文简要介绍了IBM i 7.1中的现场过程支持所提供的功能。 利用现场过程支持,您可以轻松地对数据库中存储的数据执行透明的列级加密。 您可以将FieldProc
用于任何类型的编码方案-它不仅限于加密。
您可以选择多种加密方法。 例如,通常使用AES的FieldProc
或三重DES等。 当然,您也可以选择第三方加密产品,例如Linoma,Patrick Townsend和nuBridges,作为一种简单而完整的解决方案。 但是,当您实现IBM i应用程序时,现场过程支持是一种强大的数据加密解决方案。
致谢
非常感谢帮助阅读本文的罗切斯特专家。 特别感谢肯特·米利根(Kent Milligan)的宝贵意见。
翻译自: https://www.ibm.com/developerworks/data/library/techarticle/dm-1101encryptenhance/index.html