简体   繁体   English

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

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

I'm trying to encrypt/decrypt documents into a table using a stored procedure, so I created a CLR assembly with encrypt/decrypt functions that use the RijndaelManaged class. 我试图使用存储过程将文档加密/解密到表中,所以我创建了一个具有RijndaelManaged类的加密/解密函数的CLR程序集。 I'm able to encrypt the bytes, but when I decrypt the bytes and save the document, I'm noticing there's a difference in encoding which breaks the document. 我可以加密字节,但是当我解密字节并保存文档时,我注意到在编码方面存在差异,这会破坏文档。 I'm sending the varbinary(max) bytes directly to the encrypt/decrypt function so I'm not sure what's causing a different encoding. 我将varbinary(max)字节直接发送到加密/解密函数,所以我不确定是什么导致了不同的编码。 I'm wondering how I can get this to decrypt in the correct encoding? 我想知道如何才能以正确的编码将其解密?

Here's what my assembly looks like: 这是我的程序集的样子:

    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;
    }

Here's what my functions look like: 这是我的函数:

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

And for example, how this is executed: 例如,这是如何执行的:

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

字节比较之前和之后

The problem is "somewhere" within all of that stream handling code in your decrypt method. 问题出在您的解密方法中所有流处理代码中的“某处”。 I say that because I'm not going to dig in and find the exact fault. 我这么说是因为我不会深入挖掘并找到确切的错误。 The first thing that leapt out is that your encrypt and decrypt methods don't look "symmetrical" - doing approximately the same things as each other (but some operations reversed). 跳出来的第一件事是您的加密和解密方法看起来并不“对称”-彼此之间做的大致相同(但某些操作相反)。 That's usually a bad sign with pairs of encryption/decryption methods 1 . 对于成对的加密/解密方法1,通常是一个不好的信号。

So if I make decrypt look like encrypt and don't do all of the mucking about with streams: 因此,如果我使解密看起来像加密,而不要对流进行所有处理:

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);
}

(I skipped having an output variable too - I didn't see the need for it, nor comments just telling us what the code is doing, which we can determine by reading the code). (我跳过具有output变量太多-我没有看到它存在必要性,也没有评论只是告诉我们什么代码是干什么的,我们可以通过阅读代码确定)。

Now this (paired with EncryptBytesToBytes in your question) can successfully round-trip the sample data: 现在,这(与您的问题中的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();
}

By eye, inp and oup contain the same data. 肉眼看, inpoup包含相同的数据。

(Insert usual caveats about ECB being a terrible mode to use unless it's been chosen for very specific good reasons) (插入有关ECB是一种可怕模式的常见警告,除非出于非常明确的充分理由选择了它)


1 My usual recommendation if you're going to build up a pair of encryption/decryption methods is to do is slowly and simply and make sure that the pair can round-trip at each stage before you add more complexity. 1如果要建立一对加密/解密方法,我通常的建议是缓慢简单 ,并确保该对可以在每个阶段往返,然后再增加更多复杂性。

The first stage would just be "returns the input buffer, ignores the key and IV". 第一阶段就是“返回输入缓冲区,忽略键和IV”。 Write some unit tests that confirms it round trips with some decent size buffers and a specific key and IV. 编写一些单元测试,以确认它具有一些适当大小的缓冲区以及特定的键和IV的往返行程。

Then add just a little more complexity to the implementation and check that the unit tests still pass, and iterate until the methods do what you want/need them to do. 然后,为实现增加一点点复杂度,并检查单元测试是否仍然可以通过,并进行迭代,直到方法完成您希望/需要它们执行的操作为止。

If you need "encrypt in one language, decrypt in the other", I'd actually recommend doing all of this twice, in both languages so that they both have both sets of methods. 如果您需要“用一种语言加密,然后用另一种语言解密”,则实际上我建议对这两种语言都进行两次以上操作,以使它们都具有两种方法。 Then verify that the outputs at each stage match between your implementations. 然后,验证每个阶段的输出在您的实现之间是否匹配

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM