C# 充气城堡 AES 解密 + GZ 解压缩 - 可变长度数据失败

本文关键字:失败 数据 解压缩 GZ 城堡 AES 解密 | 更新日期: 2023-09-27 17:57:05

使用以下代码 - 我通常能够解密我正在传递的令牌。 解码为 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 和密钥从令牌中提取并进行非对称解密,然后对称解密 enc 文本,然后再传递给 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解压缩将失败时,会出现我的问题。 在某些令牌上,如果我传递完整的压缩 JsonByte 数组(末尾为 null),它会抱怨 CRC 错误(为什么我在解压缩周围有 try/catch),所以然后我传递它修剪后的字节数组。 但对于具有更多组的其他令牌,它会使用全字节数组解压缩。

我有一个类似的加密例程,发现如果 a 提醒用户名从 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);
            }
        }

C# 充气城堡 AES 解密 + GZ 解压缩 - 可变长度数据失败

您正在使用ZeroBytePadding虽然CTR模式根本不需要任何填充,但您也可以直接使用SicCipher实例。

零字节

填充将从数据末尾剥离所有零值字节,这就是为什么您最终可能会得到数据受损的原因。

零字节填充不是确定性的,除非符合以下条件,否则不应使用:

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

我可能刚刚破解了它。 对于较大的有效负载令牌,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);
}

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