Download the files for this article.
This is part 8 of a series. If you have not read part 7, you
can find it here.
Thats Much Better
In part 7, we found that the performance impact of using EncryptByCert
and DecryptByCert was unacceptable for any high-volume, transactional data.
Enter EncryptByKey and DecryptByKey. The way you use these
functions is very similar to EncryptByCert and DecryptByCert. To see an example
of a simple use of EncryptByKey and DecryptByKey, see this
script. Most of the code is setup, but the fundamental calls are here
(using simplified variable names):
SELECT @v = EncryptByKey(Key_GUID(N'TestKey1'), @s1)
SELECT @s2 = DecryptByKey(@v)
The performance results are highlighted later in this
article.
Varbinary Length Requirements
How do you know how much space is required to store various
types of encrypted data? I wrote a
simple script that tests the results for a number of different data types.
The table below shows the results.
|
Data Type
|
varbinary Length
|
|
bit
|
52
|
|
tinyint
|
52
|
|
smallint
|
52
|
|
integer
|
68
|
|
bigint
|
68
|
|
String Length
|
varbinary Length
|
|
ASCII
|
UNICODE
|
|
1
|
52
|
52
|
|
10
|
68
|
68
|
|
100
|
148
|
260
|
|
1,000
|
1,060
|
2,052
|
|
(max UNICODE) 3,971
|
4,020
|
7,988
|
|
(max ASCII) 7,943
|
7,988
|
n/a
|
In Part 6
of this series, I showed that EncryptByCert stores a maximum of 117 ASCII
characters or 58 UNICODE characters. In the case of EncryptByKey, the maximums
are 7,493 ASCII and 3,971 UNICODE. These are much better, but you
still need to be aware of the limitations.
The Indexing Issue
To be able to create an index on a varbinary column, it
cannot be more than 900 bytes wide. But wait
if EncryptByKey creates a
different ciphertext each time it is called for the same string, how will I
look things up, and how will my index be of any value? The answer to the first question
is that we will use message authentication codes, which I will cover in a later
article. The second answer is that an index on the varbinary column that stores
the result of EncryptByKey would be worthless. So ignore the 900-byte limit on varbinary,
you wont build an index on this column anyway.
Performance Testing
What I did was build a test similar to the test in part 7 that
gives an indication of the performance hit when using EncryptByKey and DecryptByKey
and also compares that performance to EncyptByCert and DecryptByCert. Once
again, all of the code is included, so please do download the code and try it
out yourself. I did have access this time to a dual-proc system, so I have
results from that to add to the mix. If you run these tests, please share your
results in the forum.
The test results here are from three different desktop-class
systems. The first is a sandbox I set up to try SQL Server 2005 before it was
released; the second is my primary development system; the third is a dual-proc
development system loaned to me by our architect.
Finally, a Compelling Reason to Use UDTs
I have never built a UDT in SQL Server. I have never
found a good reason to build one. Why create a UDT called CardID where it just
maps to an integer? That would be complexity without any payback.
When dealing with encrypted values and Ecrypt/DecryptByKey, UDTs
become very valuable. For instance, what is the size of varbinary required to
store an encrypted smallint? Its important that I get it right, but its not
important that I remember (or give myself the chance of getting it wrong). For
these tests, I created a UDT called EncryptedTinyint that maps to a varbinary(52).
Now that adds value.
The Tests
The tests are fairly simple. They create three tables, one
with no encryption (CardNoEncrypt), one with ByCert encryption (CardEncryptByCert),
and one with ByKey encryption (CardEncryptByKey).
CREATE TABLE dbo.CardNoEncrypt (
CardID int NOT NULL IDENTITY,
CardNumber1 bigint NOT NULL,
CardNumber2 bigint NOT NULL,
CardNumber3 bigint NOT NULL,
CardNumber4 bigint NOT NULL,
SecurityCode1 smallint NOT NULL,
SecurityCode2 smallint NOT NULL,
SecurityCode3 smallint NOT NULL,
SecurityCode4 smallint NOT NULL,
SecureString1 varchar(36) NOT NULL,
SecureString2 varchar(36) NOT NULL,
SecureString3 varchar(36) NOT NULL,
SecureString4 varchar(36) NOT NULL,
CONSTRAINT CardNoEncrypt_PK
PRIMARY KEY CLUSTERED (CardID)
) ON [SqlCredit_FG1];
CREATE TABLE dbo.CardEncryptByCert (
CardID int NOT NULL IDENTITY,
CardNumber1 EncryptedByCert NOT NULL,
CardNumber2 bigint NOT NULL,
CardNumber3 bigint NOT NULL,
CardNumber4 bigint NOT NULL,
SecurityCode1 EncryptedByCert NOT NULL,
SecurityCode2 smallint NOT NULL,
SecurityCode3 smallint NOT NULL,
SecurityCode4 smallint NOT NULL,
SecureString1 EncryptedByCert NOT NULL,
SecureString2 varchar(36) NOT NULL,
SecureString3 varchar(36) NOT NULL,
SecureString4 varchar(36) NOT NULL,
CONSTRAINT CardEncrypt_PK
PRIMARY KEY CLUSTERED (CardID)
) ON [SqlCredit_FG2];
CREATE TABLE dbo.CardEncryptByKey (
CardID int NOT NULL IDENTITY,
CardNumber1 EncryptedBigint NOT NULL,
CardNumber2 bigint NOT NULL,
CardNumber3 bigint NOT NULL,
CardNumber4 bigint NOT NULL,
SecurityCode1 EncryptedSmallint NOT NULL,
SecurityCode2 smallint NOT NULL,
SecurityCode3 smallint NOT NULL,
SecurityCode4 smallint NOT NULL,
SecureString1 EncryptedSecureString NOT NULL,
SecureString2 varchar(36) NOT NULL,
SecureString3 varchar(36) NOT NULL,
SecureString4 varchar(36) NOT NULL,
CONSTRAINT CardEncrypt_PK
PRIMARY KEY CLUSTERED (CardID)
) ON [SqlCredit_FG3];
For this test, there are no indexes other than the clustered
primary keys.
Again, we encrypt only three columns (a bigint, a smallint,
and a varchar(36)).
Test Steps:
-
Insert n records into each table.
-
Read n random records by ID.
Each test is separated by a five-second delay to allow the system
to settle between tests.
To run the tests, run the SQL scripts in this order:
The Test script has one important parameter that maps to the
n, above: @createMax. The tests were run with it set to 10,000, but when
you run it on your own system, set it low to start with.
The Systems
Common
SQL Server 2005 SP2
Windows XP Professional
System 1
CPU: Single P4, 2.66 GHz
Memory: 2 GB
Hard Drives: One physical drive
System 2
CPU: Single P4, 3.0 GHz HT
Memory: 1.5 GB
Hard Drives: Two physical drives
(SQL, OS, and logs on C:, data on D:)
System 3
CPU:
Dual 3.4 GHz Xeon
Memory:
2 GB
Hard Drives: One physical drive
The Results
System 1 Processor Graph:

System 1 Statistics:
|
Action
|
Encryption
|
ms Duration
|
Cost vs.
Unencrypted
|
|
Create
|
None
|
11,233
|
-
|
|
|
Cert
|
15,030
|
1.33 : 1
|
|
|
Key
|
11,340
|
1.01 : 1
|
|
Read
|
None
|
423
|
-
|
|
|
Cert
|
362,486
|
856.94 : 1
|
|
|
Key
|
2,720
|
6.43 : 1
|
|
Total
|
None
|
11,656
|
-
|
|
|
Cert
|
377,516
|
32.39 : 1
|
|
|
Key
|
14,060
|
1.21 : 1
|
System 2 Processor Graph:

For these tests, I used filegroups
split between the two physical drives.
System 2 Statistics:
|
Action
|
Encryption
|
ms Duration
|
Cost vs.
Unencrypted
|
|
Create
|
None
|
2,740
|
-
|
|
|
Cert
|
12,336
|
4.50 : 1
|
|
|
Key
|
5,753
|
2.10 : 1
|
|
Read
|
None
|
466
|
-
|
|
|
Cert
|
354,290
|
760.28 : 1
|
|
|
Key
|
2,440
|
4.23 : 1
|
|
Total
|
None
|
3,206
|
-
|
|
|
Cert
|
366,626
|
114.36 : 1
|
|
|
Key
|
8,193
|
2.56 : 1
|
System 3 Processor Graph:

System 3 Statistics:
|
Action
|
Encryption
|
ms Duration
|
Cost vs.
Unencrypted
|
|
Create
|
None
|
3,826
|
-
|
|
|
Cert
|
12,050
|
3.15 : 1
|
|
|
Key
|
7,063
|
1.85 : 1
|
|
Read
|
None
|
343
|
-
|
|
|
Cert
|
293,016
|
854.27 : 1
|
|
|
Key
|
2,313
|
6.74 : 1
|
|
Total
|
None
|
4,169
|
-
|
|
|
Cert
|
305,066
|
73.17 : 1
|
|
|
Key
|
9,376
|
2.25 : 1
|
Certainly, encryption and decryption by key are much better
than by cert. I am very pleased with these ByKey results because I think the
cost is low enough that performance becomes a non-issue.
Test Changes
-
The EncryptByCert stored procedures now use functions to encrypt
and decrypt rather than the view used in the previous test.
-
The EncryptByCert stored procedures now call Cert_ID() once and
pass that value around rather than calling CertID() inside of every encrypt and
decrypt call.
For Next Time
I had planned to start right into message authentication
codes this month, but the performance of various functions has captured my
attention, so that will have to wait a bit.
References
Download the files for this article.
»
See All Articles by Columnist Rob Garrison