SQL Server:CLR RijndaelManaged字节不能从varbinary(max)正确解密

时间:2019-03-27 04:47:41

标签: c# sql-server clr

我试图使用存储过程将文档加密/解密到表中,所以我创建了一个CLR程序集,该程序集具有使用RijndaelManaged类的加密/解密功能。我可以加密字节,但是当我解密字节并保存文档时,我注意到在编码方面存在差异,这会破坏文档。我直接将varbinary(max)字节发送到加密/解密函数,因此我不确定是什么导致了不同的编码。我想知道如何才能以正确的编码将其解密?

这是我的程序集的样子:

    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

Before and After byte comparison

1 个答案:

答案 0 :(得分:1)

问题在于您的解密方法中所有流处理代码中的“某处”。我这么说是因为我不会深入挖掘并找到确切的错误。跳出来的第一件事是您的加密和解密方法看起来并不“对称”-彼此之间做的大致相同(但某些操作相反)。对于成对的加密/解密方法 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 如果要建立一对加密/解密方法,我通常的建议是 slowly simply 并确保该对可以在每个阶段进行往返,然后再添加更多复杂性。

第一步将是“返回输入缓冲区,忽略键和IV”。编写一些单元测试,以确认它具有一些适当大小的缓冲区以及特定的键和IV的往返行程。

然后只给实现增加一个复杂度,并检查单元测试是否仍然通过,并进行迭代,直到方法完成您想要/需要的操作为止。

如果您需要“用一种语言加密,用另一种语言解密”,则实际上我建议对这两种语言都进行两次,以使它们都具有两种方法。然后验证您的实现之间在每个阶段的输出 匹配。