C#Bouncy Castle AES Decryption + GZ Decompression - 变长的数据失败

时间:2016-07-14 23:32:59

标签: java c# encryption cryptography bouncycastle

使用以下代码 - 我通常能够解密我传递的令牌。解码为json时的标记字符串如下所示:

\"id\":\"9efef759-15a3-4cd0-b1f1-fceab7ad0a6e\",
\"exp\":\"2016-07-23T15:27:50.758+12:00\", 
\"iv\":\"OOqNpy9puM5jPjTwrWHSNb+d5NYDEwIq2pZFqx6mraI14Kkh0bzEWADoU2d/KGu6cp9/FrVt4epheIP5Fw9qUFrdVcNYjLO5HWdJ0V5GhpdLJlFbMnFy4vS1rJ+4X1qTNZrqPwZh2deLceoHmxnqw7ml8JVFeIaz9H8BQXkgcNo=\",
\"ver\":\"1\",
\"iat\":\"2016-07-13T15:27:50.758+12:00\",
\"key\":\"d7R9blmqBYMywOEdYpRbd+gvKPfOqmxsRQMlDipkuGoWZobJ0dnK0MGBFAXq4wOdHbHVbfisjqm+6HoRSZ2w0KcfY+enPoKL5yptvlULkwpDtATEP8pnRmCh6ycWntbanL1gJI7RoNWTkomItBp/yODdL5kSMue76xAtIzc9+no=\",
\"sig\":\"X6A58tRDSUC5HJEP1VVmQjo17Qk2rJC9pYZiV5ccIjdcLmz7HPIkpm0ZCsFcQX4ps1k32asSojqOyegYFIdDqHypdrV9c5sHchIrp6Ak8MOjNTpy+SweTGPzkjlEHCMkWLVHjrkBq9mmoMk2o0sYyZes+/ARuYB8IjtAINtbAQE=\",
\"enc\":\"n+exbDhicBLuUtbYPXrrKESIktgyaidSreD5FWAxErGJeOyjTWv9QOqCGfEou5yJq2njCddf0mu0JOEP9i1mlhe1MUUa1hE4J+qnqxre+tSxWRNszHQL8Pk+0FV6cZ1nqk+aCfw9VOjlOLYXYmNF0NSZBqQIqzpobM3twHIf5u7pvJkvbnfP8Db0S83ZchNgMWyH1t+UEb+jbpcg1Um3U7Yb8Q==\"

从令牌中提取IV和密钥并进行非对称解密,然后在将文本传递给gzip解压缩之前对其进行对称解密。

internal virtual UserObj decrypt(string jsonToken, UserObj cls, System.Security.Cryptography.AsymmetricAlgorithm certPrivateKey)
{

   Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair bcPrivateKey;
   try
   {
        //Make a bouncyCastle private key for feeding to the rsa Engine.
        bcPrivateKey = Org.BouncyCastle.Security.DotNetUtilities.GetKeyPair(certPrivateKey);
        // Attempt to unmarshal the JSON token
        Token token = encoder.unmarshalJsonString<Token>(jsonToken);

        // Asymmetrically encrypt the symmetric encryption iv
        Org.BouncyCastle.Crypto.Engines.RsaEngine rsaCipher = new Org.BouncyCastle.Crypto.Engines.RsaEngine();
        Org.BouncyCastle.Crypto.Encodings.Pkcs1Encoding rsaEncoder = new Org.BouncyCastle.Crypto.Encodings.Pkcs1Encoding(rsaCipher);

         rsaEncoder.Init(false, bcPrivateKey.Private);

         // Asymmetrically decrypt the symmetric encryption key
         byte[] encryptedAesKeyBytes = encoder.fromBase64String(token.Get(Token.ENCRYPTED_KEY));
         byte[] aesKeyBytes = rsaEncoder.ProcessBlock(encryptedAesKeyBytes, 0, encryptedAesKeyBytes.Length);

         // Asymmetrically decrypt the symmetric encryption IV
         byte[] encryptedAesIvBytes = encoder.fromBase64String(token.Get(Token.IV));
         byte[] aesIvBytes = rsaEncoder.ProcessBlock(encryptedAesIvBytes, 0, encryptedAesIvBytes.Length);

         //Setting equivalent excyption to "AES/CTR/NoPadding"
         Org.BouncyCastle.Crypto.Engines.AesEngine aes = new Org.BouncyCastle.Crypto.Engines.AesEngine();
         Org.BouncyCastle.Crypto.Modes.SicBlockCipher blockCipher = new Org.BouncyCastle.Crypto.Modes.SicBlockCipher(aes);
         Org.BouncyCastle.Crypto.Paddings.PaddedBufferedBlockCipher aesCipher = new Org.BouncyCastle.Crypto.Paddings.PaddedBufferedBlockCipher(blockCipher, new Org.BouncyCastle.Crypto.Paddings.ZeroBytePadding());

         Org.BouncyCastle.Crypto.Parameters.KeyParameter keyParam2 = new Org.BouncyCastle.Crypto.Parameters.KeyParameter(aesKeyBytes);

         // Symmetrically decrypt the data
         Org.BouncyCastle.Crypto.Parameters.ParametersWithIV keyParamWithIv = new Org.BouncyCastle.Crypto.Parameters.ParametersWithIV(keyParam2, aesIvBytes, 0, TokenEncryptor.IV_SIZE_BYTES);
         //
         // Symmetrically decrypt the data
         aesCipher.Init(false, keyParamWithIv);
         string encryptedData = token.Get(Token.ENCRYPTED_DATA);
         byte[] inputBytes = encoder.fromBase64String(encryptedData);
         byte[] compressedJsonBytes = new byte[aesCipher.GetOutputSize(inputBytes.Length)];
         //Do the decryption.  length is the proper size of the compressed data, compressedJsonBytes will
         //contain extra nulls at the end.
         int length = aesCipher.ProcessBytes(inputBytes, compressedJsonBytes, 0);

         //String to look at the compressed data (debug)
         string compressed = encoder.toBase64String(compressedJsonBytes);
         byte[] compressedJsonBytesProperSize = new byte[length];
         Array.Copy(compressedJsonBytes, compressedJsonBytesProperSize, length);

         //String to look at the compressed data (debug)
         compressed = encoder.toBase64String(compressedJsonBytesProperSize);
         byte[] jsonBytes = null;
         try
         {
               jsonBytes = encoder.decompress(compressedJsonBytesProperSize);
         }
         catch (Exception)
         {
               jsonBytes = encoder.decompress(compressedJsonBytes);
         }
         string tmep = System.Text.Encoding.UTF8.GetString(jsonBytes, 0, jsonBytes.Length);
         UserObj dataObj = encoder.fromJsonBytes<UserObj>(jsonBytes);

         return dataObj;

    }
    catch (Exception e)
    {
         throw new Exceptions.TokenDecryptionException(e);
    }
}

最终解密的令牌看起来像这样:

\"domain\":\"GLOBAL\",
\"user\":\"someuser\",
\"groups\":[\"GROUP1\",\"GROUP2\",\"GROUP3\"],
\"branchId\":\"0000\"

我的问题发生在取决于组中的项目数量时,GZ解压缩将失败。在一些令牌上,如果我传递完整的compressedJsonByte数组(在末尾有空值),它会抱怨CRC错误(为什么我在解压缩时有try / catch),所以然后我将修剪后的字节数组传递给它。但是对于具有更多组的其他令牌,它使用完整的字节数组进行解压缩。

我有一个类似的加密例程,发现如果用17到19个字符提醒用户名,其他一切都相同,我需要使用未修剪的字节数组进行解压缩。但此后发现问题进展得更深。

任何帮助将不胜感激。我希望它有一个解压缩问题,但我怀疑解密中的某些东西可能会捏造输出字节数组的末尾。

我无法更改解密类型,因为它来自外部实体,而且它们的一面是用Java编写的。

作为参考,解压缩程序是:

        public virtual byte[] decompress(byte[] compressedData)
        {
            try
            {
                //Push to a file for debug
                System.IO.FileStream fs = new System.IO.FileStream(@"C:\temp\file.gz", System.IO.FileMode.OpenOrCreate);
                fs.Write(compressedData,0,compressedData.Length);
                fs.Flush();
                fs.Close();

                byte[] outputBytes = new byte[4096];
                byte[] buffer = new byte[4096];
                    using (System.IO.MemoryStream msInput = new System.IO.MemoryStream(compressedData))
                    {
                        System.IO.MemoryStream msOutput = new System.IO.MemoryStream();
                        //using (System.IO.Compression.GZipStream gzs = new System.IO.Compression.GZipStream(msInput, System.IO.Compression.CompressionMode.Decompress))
                        using (ZLibNet.GZipStream gzs = new ZLibNet.GZipStream(msInput, ZLibNet.CompressionMode.Decompress))
                        {

                            int nRead;

                            bool canR = gzs.CanRead;
                            while ((nRead = gzs.Read(buffer, 0, buffer.Length)) > 0)
                            {
                                msOutput.Write(buffer, 0, nRead);

                            }
                        }
                        outputBytes = msOutput.ToArray();
                        if (outputBytes.Length == 0)
                            throw new Exception("Could not decompress");
                    }
                return outputBytes;
            }
            catch (Exception e)
            {
                throw new Exceptions.ServiceException(e);
            }
        }

2 个答案:

答案 0 :(得分:2)

您使用ZeroBytePadding而点击率模式根本不需要任何填充,您也可以直接使用SicCipher实例。

零字节填充将从数据末尾删除所有零值字节,这就是为什么你可能最终得到受损数据。

零字节填充不是确定性的,除非您要么:

,否则不应使用
  • 确保数据不以零字节或位结尾;
  • 能够以其他方式确定明文长度。

答案 1 :(得分:-1)

我可能刚刚破解了它。对于较大的有效负载令牌,ProcessBytes可以一次通过。

对于较小的那些,我花了很长时间才注意到ProcessBytes报告的长度小于inputBytes数组的长度。

我在进程字节后尝试了一个DoFinal:

int length = aesCipher.ProcessBytes(inputBytes, compressedJsonBytes, 0);
int length2= aesCipher.DoFinal(compressedJsonBytes, length);

对于我编码的令牌,它有效......但是对于给定的令牌,它发布了一个Org.BouncyCastle.Crypto.DataLengthException:解密时最后一个块不完整

所以最后我尝试了

int length2 = aesCipher.ProcessBytes(inputBytes,length,inputBytes.Length-length,compressedJsonBytes,length);

处理剩余数据 - 这很有效。 所以我的结果代码现在看起来如下 - 替换单行:

int length = aesCipher.ProcessBytes(inputBytes, compressedJsonBytes, 0);

使用:

int length = 0;
while (length < inputBytes.Length)
{
    length += aesCipher.ProcessBytes(inputBytes, length, inputBytes.Length-length, compressedJsonBytes, length);
}

这似乎已经消除了采用全长压缩字节数组的需要。 UncompressedProperSize字节数组现在可以正常工作。