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);
}
}
您正在使用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 字节数组现在可以正常工作。