SQL Server 2005 encryption solution has some limitations and one of them that has raised a few questions is the limited amount of plaintext that can be encrypted. I hope this article helps answer this question.

What is the limit on the data length that can be encrypted?
 The answer to this question is a little bit more complex than a simple number: the limit on the input (plaintext) really depends on the output (ciphertext), and it depends on the key you are using and on whether you are using optional features of the encryption builtins.

You can use the following formula to predict the length of the ciphertext that will result from calling EncryptByKey based on the plaintext length, the key algorithm used and whether the optional authenticator parameter is being used or not. Note: this formula is valid only for the DES and AES key families, it is not valid for RC4, and I would recommend to not even use RC4 at all)

@CipherLen = FLOOR( (8 +@PTLen + (@UsesAuth * @HASHLEN) ) / @BLOCK) + 1 )  *  @BLOCK + 16 + @BLOCK + 4

  Where:
@CipherLen:     The ciphertext length in bytes
@PTLen:           The plaintext length in bytes
@UsesAuth:      1 if using the optional authenticator parameter, 0 otherwise
@HASHLEN:   20 bytes for SHA1 (The authenticator parameter adds a SHA1 hash to the plaintext)
@BLOCK:       The length in bytes per block. 8 for the DES family, 16 for AES

Now let’s explain every part of the formula to make it easier to understand:
FLOOR( (
8                                     Encrypted internal header
+@PTLen                      Plaintext length
+ (@UsesAuth * @HASHLEN)            If the authenticator parameter is used, the plaintext will include a hash derived from it.
 ) / @BLOCK) + 1 )  *  @BLOCK     This portion of the formula as a whole predicts the length of the ciphertext, including padding. Note: If the plaintext length fits exactly in one block, there will be an additional block of padding. This part of the formula reflects this behavior
   + 16     Key GUID (not encrypted).    This information is used during decryption to identify the key that needs to be used
   + @BLOCK                Initialization vector (IV)
   + 4                               Internal header that describes the BLOB version number

  Once we have reviewed this formula, we can discuss the limitations on the encryption builtins: The output (ciphertext) is limited to up to 8000 bytes; what does this mean? This means that the plaintext limit is a little bit below the 8000 bytes (i.e. for AES encryption, using the authenticator parameter it should be around 7920 bytes ). If you try to encrypt a larger plaintext, the builtin will fail (return null) as it won’t be able to fit all the output in the available buffer.

   For certificates, the way to calculate the maximum length of plaintext we can encrypt, as well as the ciphertext length, is easier because it is based on key modulus.
@Cipher_len  = key_modulus (regardless of the plaintext length)
@Max_Plaintext_len = key_modulus – 11

   By default, the self-signed certificates created by SQL Server 2005 have a 1024 bit modulus = 128 bytes, therefore the cipher texts are always 128 bytes and the maximum plaintext that can be encrypted is 117 bytes. The same formula applies for any certificate you may want to export into the system (the limit is 3456 bits for the private keys modulus).

  For more information on this one, I recommend you to go to http://msdn.microsoft.com/library/default.asp?url=/library/en-us/seccrypto/security/cryptencrypt.asp and look at the remarks section for CryptEncrypt.

  Should I always use this formula to calculate the size of my encrypted columns?
   I strongly recommend against considering the result of the formula as a hardcoded length for your encrypted columns. I would suggest to estimate what would be the expected maximum ciphertext length (either by creating a sample using EncryptByKey directly on your system or by using the formula) and add a little bit of extra space. My personal suggestion would be to reserve at least 1 extra block (8 bytes for DES family, 16 bytes for AES), but it would really depend on the specific scenario.

  The formula I shared here is a way to predict the length of the plaintext, but there is no guarantee that this formula will work for future algorithms or even for the existing algorithms in future versions of the product. As you have noticed, SQL Server encryption includes a few fields that are not part of the ciphertext itself, and in future versions of the product there may be a need to include new fields.

  
  But I really need to encrypt BLOBs larger than 8000 bytes! Is there anything I can do?
Using the native solution, there is nothing you can do. Your application will need to slice the input before encrypting it.
  I have created a small sample code that creates a UDF (user defined function) that is essentially a wrapper around EncryptByKey. It basically partitions the data in different pieces, encrypts them, and then pastes them together in a single BLOB that can be decrypted by its counterpart wrapper UDF for DecryptByKey. I created two different set of UDFs, one for symmetric keys and one for certificates.

  The main idea behind this demo is to split the BLOB we want to encrypt in smaller segments that we can encrypt and then concatenate their ciphertexts in such a way that we can recover the individual segments, decrypt them, and paste them again in a single LOB (the original plaintext).

Demo

/***********************************************************************
*      This posting is provided "AS IS" with no warranties,
* and confers no rights.
*
* Authors:  Raul Garcia,
*    Tanmoy Dutta,
*   Pankaj Kamat,
*   Laurentiu Cristofor
* Date:   11/07/2005
* Description: 
*
*   Create a scalar functions that allow encryption and decryption
* of large objects (> 8000 bytes).
*
*     (c) 2005 Microsoft Corporation. All rights reserved.
*
**********************************************************************/

CREATE DATABASE demo_LobEncryption
go

USE demo_LobEncryption
go

--------------------------------------------------------------------
-- Create a new Encryption function – EncryptLob.
--
-- This is a wrapper around the EncryptByKey builtin
-- In order to keep this demo simple, it doesn't support
-- all the options of that builtin
--
-- PARAMETERS
--       @key_guid:        Symmetric key GUID,
--                  equivalent to EncryptByKey’s first argument.
--                         This key needs to be already opened
--                  before calling the function
--       @lob:             The plaintext to encrypt.
--                  Limits for the datalength are defined
--                  by the varbinary(max) data type
-- RETURN VALUE
--       varbinary(max):   If the encryption succeeded,
--                  it will return the encrypted data
--     as described in NOTES
--     NULL is returned if there is any error
-- NOTES:
--       The plaintext will be split in blocks that can be
--  encrypted individually and then will be concatenated together:
--       ([2 bytes - ciphertext block length][ciphertext block])
--  per plaintext block
CREATE FUNCTION dbo.EncryptLob(
@key_guid uniqueidentifier,
@lob varbinary(max) )
returns varbinary(max)
as
BEGIN
 declare @PtLimit int
 declare @Total  int
 declare @CurPos  int
 declare @Aux  varbinary(8000)
 declare @AuxLen  int
 declare @PlaintextColumnLimit int
 declare @Cipher  varbinary(max)

 -- Limit for the PT block
 SET @PtLimit = 7800
 SET @CurPos = 1
 SET @Total = datalength( @lob )
 SET @Cipher = null

 ---- Simple validation of the input parameters

 if(
            @lob is not null
            AND encryptbykey( @key_guid, 0x00 ) is not null 
            -- Can we encrypt with the key specified by the GUID
          )
 BEGIN
  WHILE @CurPos <= @Total
  BEGIN
   -- Get a new PT block
   SELECT @Aux = substring( @Lob, @CurPos, @PtLimit )
   SET @CurPos = @CurPos + @PtLimit

   -- Encrypt the PT block
   SET @Aux = encryptbykey(@key_guid, @Aux)
   -- If any block failed to decrypt,
   -- we should just return NULL (failed)
   if( @Aux is null )
    return null

   -- Get the ciphertext length
   -- 2 bytes should be enough
   SET @AuxLen = datalength( @Aux )
   SET @Aux = convert( binary(2), @AuxLen ) + @Aux

   -- Only set the @Cipher for the first block,
   -- otherwise concatenate the existing data
   -- with the new cipher block
   if( @Cipher is null )
    SET @Cipher = @Aux
   ELSE
    SET @Cipher = @Cipher + @Aux
  END
 END

 return @Cipher
END
go
-------------------------------------------------------------------
-- Create a new Decryption function - DecryptLob.
--
-- This is a wrapper around the decryptByKey builtin.
-- It is the counterpart of the EncryptLob function.
--
-- PARAMETERS
--       @Cipher:          A ciphertext that was generated by
--     EncryptLob.
--                  The decryption key needs to be already
--                  opened before calling the function
-- RETURN VALUE
--       varbinary(max):   If the decryption succeeded, it will
--                  return the plaintext as varbinary(max)
--     NULL is returned if there is any error
--
-- NOTES:
--       The ciphertext will be split in blocks that can be
--  decrypted individually and then concatenated together:
--       ([2 bytes - ciphertext block length][ciphertext block])
--  per plaintext block
CREATE FUNCTION dbo.DecryptLob( @Cipher varbinary(max) )
returns varbinary(max)
as
BEGIN
 declare @PtLimit int
 declare @Total  int
 declare @CurPos  int
 declare @Aux  varbinary(8000)
 declare @AuxLen  int
 declare @LobPt  varbinary(max)

 SET @CurPos = 1
 SET @Total = datalength( @Cipher )
 SET @LobPt = null

 -- No op for null or empty data
 if( @Cipher is not null  
   AND @Total > 0
    )
 BEGIN
  WHILE @CurPos <= @Total
  BEGIN
   -- Read the next cipher block length
   SELECT @AuxLen = substring( @Cipher, @CurPos, 2 )
   SET @CurPos = @CurPos + 2

   -- Length field validation.
   -- Any unexpected length will result in error
   if( @AuxLen <= 0 OR @AuxLen > 8000 )
    return null

   -- Get the next cipher block
   SELECT @Aux = substring( @Cipher, @CurPos, @AuxLen )
   SET @CurPos = @CurPos + @AuxLen

   -- If there is any discrepancy, 
   -- it is either a data truncation error
   -- (cipher was truncated)
   -- or a data corruption error.
   -- We will fail the whole operation.
   if( datalength( @Aux ) <> @AuxLen )
    return null

   -- Decrypt the current cipher block
   SET @Aux = decryptbykey( @Aux )

   -- ... and make sure we could decrypt it.
   -- Again, any error here will terminate 
   -- the operation and return null
   if( @Aux is null )
    return null

   -- Either set (if first block) or concatenate
   -- to the available PT we already decrypted
   if( @LobPt is null )
    SET @LobPt = @Aux
   ELSE
    SET @LobPt = @LobPt + @Aux
  END
 END

 return @LobPt
END
go

---------------------- Test
CREATE SYMMETRIC KEY key1 WITH ALGORITHM = AES_192 ENCRYPTION BY PASSWORD = '|\/|y
p@22\/\/0rD'
go

OPEN SYMMETRIC KEY key1 DECRYPTION BY PASSWORD = '|\/|y p@22\/\/0rD'
go

--------------- Quick Demo
declare @x varbinary(max)
declare @y varbinary(max)
declare @z varchar(max)
declare @i int
set @i = 0
SET @x = convert( varbinary(max), '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
while @i < 15 -- PT will be a little bit less than 2MB
BEGIN
 SET @i = @i + 1
 SET @x = @x + @x
END
SELECT @y = dbo.EncryptLob( key_guid('key1'), @x )
SELECT datalength( @y ) as 'Ciphertext length'
if( @y is not null )
BEGIN
 SELECT @z = convert( varchar(max), dbo.DecryptLob( @y ))
 
 if( @z is not null )
 BEGIN
  SELECT datalength( @x) as 'original PT len', datalength(@z) as 'decrypted PT len', substring( @z, datalength(@z) - 16, 100) as 'Last 16 chars'
  if( @z = @x )
   print 'decrypted PT == original PT'
  else
   print 'Unexpected error. PTs didn''t match'
 END
 ELSE
  print 'Failed to decrypt. Make sure the key is opened'
END
ELSE
 print 'Failed to encrypt. Make sure the key is opened'
go

--------------------------------------------------------------------
--------------------------------------------------------------------
--------------------------------------------------------------------
-- Create a new Encryption function - EncryptLobByCert.
--
-- This is a wrapper around EncryptByCert builtin
-- In order to keep this demo simple, it doesn't support
-- all the options EncryptByCert supports.
--
-- PARAMETERS
--       @cert_id:         Cert ID, equivalent to EncryptByCert
--                   first argument.
--       @lob:             The plaintext to encrypt.
--                   Limits for the datalength are defined
--                         by the varbinary(max) data type
-- RETURN VALUE
--       varbinary(max):   If the encryption succeeded,
--                    it will return a byte stream that
--                    with the encrypted data. The byte stream
--                    format is described in NOTES     
--    NULL if there is any error
-- NOTES:
--       The plaintext will be split in blocks that can be
--  encrypted individually and then concatenated together:
--  ([2 bytes - ciphertext block length][ciphertext block])
--  per plaintext block
--  Remember that asymmetric key encryption/decryption is orders
--  of magnitude more expensive than symmetric key
--  encryption/decryption.
--   We recommend avoiding the use of asymmetric key encryption for
-- large amounts of data, but these functions may be used to
-- protect data < 1000 bytes that are larger than what
-- the SQL builtins are currently supporting
-- (for example, data larger than 117 bytes)
CREATE FUNCTION dbo.EncryptLobByCert( @cert_id int, @lob varbinary(max) )
returns varbinary(max)
as
BEGIN
 declare @PtLimit int
 declare @Total  int
 declare @CurPos  int
 declare @Aux  varbinary(8000)
 declare @AuxLen  int
 declare @PlaintextColumnLimit int
 declare @Cipher  varbinary(max)

 SET @Aux = encryptbycert( @cert_id, 0x00 )
 if( @Aux is null ) -- Return null if we cannot encrypt by the specified cert
  return null

 -- Calculate the limit for the PT block
 SET @PtLimit = datalength( @Aux ) - 11

 SET @CurPos = 1
 SET @Total = datalength( @lob )
 SET @Cipher = null

 ---- Simple validation of the input parameters
 WHILE @CurPos <= @Total
 BEGIN
  -- Get a new PT block
  SELECT @Aux = substring( @Lob, @CurPos, @PtLimit )
  SET @CurPos = @CurPos + @PtLimit

  -- Encrypt the PT block
  SET @Aux = encryptbycert( @cert_id, @Aux)
  -- If any block failed to decrypt,
  -- we should just return NULL (failed)
  if( @Aux is null )
   return null

  -- Get the ciphertext length
  -- 2 bytes should be enough
  SET @AuxLen = datalength( @Aux )
  SET @Aux = convert( binary(2), @AuxLen ) + @Aux

  -- Only set the @Cipher for the first block,
  -- otherwise concatenate the existing data with
  -- the new cipher block
  if( @Cipher is null )
   SET @Cipher = @Aux
  ELSE
   SET @Cipher = @Cipher + @Aux
 END

 return @Cipher
END
go


---------------------------------------------------------------------
-- Create a new Decryption function - DecryptLobByCert.
--
-- This is a wrapper around DecryptByCert builtin.
-- It is the counterpart of EncryptLobByCert function.
--
-- PARAMETERS
--       @cert_id:         Cert ID, equivalent to EncryptByCert
--                     first argument.
--       @Cipher:          A ciphertext that was generated by
--                     EncryptLobByCert.
--       @Password:        Password for the private key.
--                     If encrypted by DBMK, you should use null
--        for this parameter.
--
-- RETURN VALUE
--       varbinary(max):   If the decryption succeeded, it will
--                     return the plaintext as varbinary(max)
--
-- NOTES
--       The plaintext will be split in blocks that can be
-- encrypted individually and then concatenated together:
-- ([2 bytes - ciphertext block length][ciphertext block])
-- per plaintext block
CREATE FUNCTION dbo.DecryptLobByCert(
@cert_id int,
@Cipher varbinary(max),
@Password nvarchar(4000) )
returns varbinary(max)
as
BEGIN
 declare @PtLimit  int
 declare @Total  int
 declare @CurPos  int
 declare @Aux  varbinary(8000)
 declare @AuxLen  int
 declare @LobPt  varbinary(max)

 SET @CurPos = 1
 SET @Total = datalength( @Cipher )
 SET @LobPt = null

 -- No op for null or empty data
 if( @Cipher is not null  
   AND @Total > 0
    )
 BEGIN
  WHILE @CurPos <= @Total
  BEGIN
   -- Read the next cipher block length
   SELECT @AuxLen = substring( @Cipher, @CurPos, 2 )
   SET @CurPos = @CurPos + 2

   -- Length field validation. Any unexpected
   -- length will result in error
   if( @AuxLen <= 0 OR @AuxLen > 8000 )
    return null

   -- Get the next cipher block
   SELECT @Aux = substring( @Cipher, @CurPos, @AuxLen )
   SET @CurPos = @CurPos + @AuxLen

   -- If there is any discrepancy,
   -- it is either a data truncation error
   -- (cipher was truncated)
   -- or a data corruption error.
   -- We will fail the whole operation.
   if( datalength( @Aux ) <> @AuxLen )
    return null

   -- Decrypt the current cipher block
   SET @Aux = decryptbycert( @cert_id, @Aux, @Password )

   -- ... and make sure we could decrypt it.
   -- Again, any error here will terminate
   -- the operation and return null
   if( @Aux is null )
    return null

   -- Either set (if first block) or concatenate
   -- to the available PT we already decrypted
   if( @LobPt is null )
    SET @LobPt = @Aux
   ELSE
    SET @LobPt = @LobPt + @Aux
  END
 END

 return @LobPt
END
go

------------ Test
CREATE CERTIFICATE certTest ENCRYPTION BY PASSWORD = 'c3R+
p@zz\/\/0Rd!' WITH SUBJECT = 'Demo - LOB encryption'
go

declare @x varbinary(max)
declare @y varbinary(max)
declare @z varchar(max)
declare @i int
set @i = 0
SET @x = convert( varbinary(max), '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
while @i < 4 -- PT will be a little bit more than 1k
BEGIN
 SET @i = @i + 1
 SET @x = @x + @x
END
SELECT @y = dbo.EncryptLobByCert( cert_id('CertTest'), @x )
SELECT datalength( @y ) as 'Ciphertext length'
--select @y as CipherTextLob
if( @y is not null )
BEGIN
 SELECT @z = convert( varchar(max), dbo.DecryptLobByCert( cert_id('CertTest'), @y, N'c3R+
p@zz\/\/0Rd!' ))
 
 if( @z is not null )
 BEGIN
  SELECT datalength( @x) as 'original PT len', datalength(@z) as 'decrypted PT len', substring( @z, datalength(@z) - 16, 100) as 'Last 16 chars'
  if( @z = @x )
   print 'decrypted PT == original PT'
  else
   print 'Unexpected error. PTs didn''t match'
 END
 ELSE
  print 'Failed to decrypt. Make sure the key is opened'
END
ELSE
 print 'Failed to encrypt. Make sure the key is opened'
go

------ Test2
CREATE MASTER KEY ENCRYPTION BY PASSWORD = 'D8|\/||< p455\/\/oRx!'
go

CREATE CERTIFICATE certTest2 WITH SUBJECT = 'Demo - LOB encryption DBMK protected'
go


declare @x varbinary(max)
declare @y varbinary(max)
declare @z varchar(max)
declare @i int
set @i = 0
SET @x = convert( varbinary(max), '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
while @i < 4 -- PT will be a little more than 1k
BEGIN
 SET @i = @i + 1
 SET @x = @x + @x
END
SELECT @y = dbo.EncryptLobByCert( cert_id('CertTest2'), @x )
SELECT datalength( @y ) as 'Ciphertext length'
--select @y as CipherTextLob
if( @y is not null )
BEGIN
 -- Use null for the password parameter
 SELECT @z = convert( varchar(max), dbo.DecryptLobByCert( cert_id('CertTest2'), @y, null ))
 
 if( @z is not null )
 BEGIN
  SELECT datalength( @x) as 'original PT len', datalength(@z) as 'decrypted PT len', substring( @z, datalength(@z) - 16, 100) as 'Last 16 chars'
  if( @z = @x )
   print 'decrypted PT == original PT'
  else
   print 'Unexpected error. PTs didn''t match'
 END
 ELSE
  print 'Failed to decrypt. Make sure the key is opened'
END
ELSE
 print 'Failed to encrypt. Make sure the key is opened'
go
----------------------- CLEANUP
use master
go

DROP DATABASE demo_LobEncryption
go
----------------------- END OF DEMO -----------------------