SQL 服务器:CLR RijndaelManaged 字节未从 varbinary(max) 正确解密

SQL Server : CLR RijndaelManaged bytes not decrypting correctly from varbinary(max)

我正在尝试使用存储过程将 encrypt/decrypt 文档放入 table,因此我创建了一个 CLR 程序集,其中包含 encrypt/decrypt 使用 RijndaelManaged class 的函数.我能够加密字节,但是当我解密字节并保存文档时,我注意到编码存在差异,这会破坏文档。我将 varbinary(max) 字节直接发送到 encrypt/decrypt 函数,所以我不确定是什么导致了不同的编码。我想知道如何才能以正确的编码对其进行解密?

这是我的程序集的样子:

    public static byte[] AES_EncryptBytes(byte[] input, string pass)
    {
        try
        {
            return EncryptBytesToBytes(input, System.Text.Encoding.UTF8.GetBytes(pass));
        }
        catch (Exception)
        {
            return null;
        }
    }

    public static byte[] AES_DecryptBytes(byte[] input, string pass)
    {
        try
        {
            return DecryptBytesFromBytes(input, System.Text.Encoding.UTF8.GetBytes(pass));
        }
        catch (Exception)
        {
            return null;
        }
    }

    private static byte[] EncryptBytesToBytes(byte[] Input, byte[] Key)
    {
        return EncryptBytesToBytes(Input, Key, null);
    }

    private static byte[] EncryptBytesToBytes(byte[] Input, byte[] Key, byte[] IV)
    {
        // Check arguments.
        if ((Input == null) || (Input.Length <= 0))
        {
            throw (new ArgumentNullException("plainText"));
        }

        if ((Key == null) || (Key.Length <= 0))
        {
            throw (new ArgumentNullException("Key"));
        }

        // Create an RijndaelManaged object
        // with the specified key and IV.
        RijndaelManaged rijAlg = new RijndaelManaged();
        rijAlg.Key = Key;

        if (!(IV == null))
        {
            if (IV.Length > 0)
            {
                rijAlg.IV = IV;
            }
            else
            {
                rijAlg.Mode = CipherMode.ECB;
            }
        }
        else
        {
            rijAlg.Mode = CipherMode.ECB;
        }

        byte[] encrypted = null;
        // Create a decrytor to perform the stream transform.
        ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);

        encrypted = encryptor.TransformFinalBlock(Input, 0, Input.Length);
        // Return the encrypted bytes from the memory stream.
        return encrypted;
    }

    private static byte[] DecryptBytesFromBytes(byte[] cipherText, byte[] Key)
    {
        return DecryptBytesFromBytes(cipherText, Key, null);
    }

    private static byte[] DecryptBytesFromBytes(byte[] cipherText, byte[] Key, byte[] IV)
    {
        // Check arguments.
        if ((cipherText == null) || (cipherText.Length <= 0))
        {
            throw (new ArgumentNullException("cipherText"));
        }

        if ((Key == null) || (Key.Length <= 0))
        {
            throw (new ArgumentNullException("Key"));
        }

        // Create an RijndaelManaged object
        // with the specified key and IV.
        RijndaelManaged rijAlg = new RijndaelManaged();
        rijAlg.Key = Key;

        if (!(IV == null))
        {
            if (IV.Length > 0)
            {
                rijAlg.IV = IV;
            }
            else
            {
                rijAlg.Mode = CipherMode.ECB;
            }
        }
        else
        {
            rijAlg.Mode = CipherMode.ECB;
        }

        byte[] output = null;
        // Create a decrytor to perform the stream transform.
        ICryptoTransform decryptor = rijAlg.CreateDecryptor(rijAlg.Key, rijAlg.IV);
        // Create the streams used for decryption.
        MemoryStream msDecrypt = new MemoryStream(cipherText);
        CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read);

        StreamReader srDecrypt = new StreamReader(csDecrypt);
        // Read the decrypted bytes from the decrypting stream
        // and place them in a string.
        MemoryStream ms = new MemoryStream();

        while (!srDecrypt.EndOfStream)
        {
            ms.WriteByte((byte)(srDecrypt.Read()));
        }

        ms.Position = 0;
        output = ms.ToArray();
        return output;
    }

我的函数如下所示:

CREATE FUNCTION [dbo].EncryptBytes
     (@Input VARBINARY(MAX), @KEY [NVARCHAR](100))
RETURNS VARBINARY(MAX) 
WITH EXECUTE AS CALLER
AS 
EXTERNAL NAME [DocumentsEncryption].[AES_EncryptDecrypt.AES_EncryptDecryptLibrary].AES_EncryptBytes
GO

CREATE FUNCTION [dbo].[DecryptBytes]
    (@Input VARBINARY(MAX), @KEY [NVARCHAR](100))
RETURNS VARBINARY(MAX) 
WITH EXECUTE AS CALLER
AS
EXTERNAL NAME [DocumentsEncryption].[AES_EncryptDecrypt.AES_EncryptDecryptLibrary].[AES_DecryptBytes]
GO

例如,这是如何执行的:

DECLARE @DocumentStream VARBINARY(MAX)
--these bytes below represent a document
SET @DocumentStream = 0x255044462D312E350D25E2E3CFD30D0A

DECLARE @EncryptionKey NVARCHAR(100)
SET @EncryptionKey = 'ayb&e#i&BWLGMe2V'

DECLARE @EncryptedDocumentStream VARBINARY(MAX)
SET @EncryptedDocumentStream  = dbo.[EncryptBytes](@DocumentStream, @EncryptionKey)

DECLARE @DecryptedDocumentStream VARBINARY(MAX)
SET @DecryptedDocumentStream = dbo.[DecryptBytes](@EncryptedDocumentStream,@EncryptionKey)

--@DecryptedDocumentStream will return the decrypted bytes but the encoding is wrong
SELECT @DecryptedDocumentStream
--This will return:               0x255044462D312E350D25FDFDFDFD0D0A
--Instead of the original bytes:  0x255044462D312E350D25E2E3CFD30D0A

问题出在解密方法中所有流处理代码中 "somewhere"。我这么说是因为我不打算深入研究并找出确切的错误。跳出的第一件事是您的加密和解密方法看起来不像 "symmetrical" - 彼此做的事情大致相同(但有些操作相反)。对于成对的 encryption/decryption 方法 1.

这通常是一个不好的迹象

因此,如果我使解密看起来像加密并且不对流进行所有处理:

private static byte[] DecryptBytesFromBytes(byte[] cipherText, byte[] Key, byte[] IV)
{
  if ((cipherText == null) || (cipherText.Length <= 0))
  {
    throw (new ArgumentNullException("cipherText"));
  }

  if ((Key == null) || (Key.Length <= 0))
  {
    throw (new ArgumentNullException("Key"));
  }

  RijndaelManaged rijAlg = new RijndaelManaged();
  rijAlg.Key = Key;

  if (!(IV == null))
  {
    if (IV.Length > 0)
    {
      rijAlg.IV = IV;
    }
    else
    {
      rijAlg.Mode = CipherMode.ECB;
    }
  }
  else
  {
    rijAlg.Mode = CipherMode.ECB;
  }

  ICryptoTransform decryptor = rijAlg.CreateDecryptor(rijAlg.Key, rijAlg.IV);
  return decryptor.TransformFinalBlock(cipherText, 0, cipherText.Length);
}

(我也跳过了 output 变量 - 我没有看到它的必要性,也没有评论只是告诉我们 什么 代码在做什么,这我们可以通过阅读代码来确定)。

现在这个(与你问题中的 EncryptBytesToBytes 配对)可以成功往返样本数据:

static void Main()
{
  var inp = new byte[] { 0x25, 0x50, 0x44, 0x46, 0x2D, 0x31, 0x2E, 0x35,
                         0x0D, 0x25, 0xE2, 0xE3, 0xCF, 0xD3, 0x0D, 0x0A };
  var key = "ayb&e#i&BWLGMe2V";

  var oup = AES_DecryptBytes(AES_EncryptBytes(inp, key), key);
  Console.ReadLine();
}

肉眼看来,inpoup包含相同的数据。

(插入关于 ECB 是一种糟糕模式的常见警告,除非它是出于 非常 特定的充分理由而被选择)


1如果你要构建一对 encryption/decryption 方法,我通常的建议是慢慢地simply 并确保这对可以在每个阶段往返,然后再增加更多的复杂性。

第一阶段就是 "returns the input buffer, ignores the key and IV"。编写一些单元测试来确认它使用一些合适大小的缓冲区和特定的密钥和 IV 进行往返。

然后在实现中增加 一点 的复杂性并检查单元测试是否仍然通过,然后迭代直到方法完成您 want/need 他们要做的事情.

如果您需要 "encrypt in one language, decrypt in the other",我实际上建议用两种语言将所有这些都做两次,以便它们都有两套方法。然后验证每个阶段的输出匹配在您的实施之间。