当密钥错误时,CryptoStream FlushFinalBlock 是否保证抛出异常?

发布于 2025-01-17 06:07:58 字数 312 浏览 2 评论 0原文

我知道当填充错误时,CryptoStreamFlushFinalBlock 方法会抛出异常,这可能意味着密钥错误。

我想要弄清楚的是,这是否是一种测试密钥本身真实性的防弹方法,即,如果给定的话,FlushFinalBlock是否保证抛出钥匙错误。或者,换句话说,错误的密钥是否有可能产生不引发异常但只是乱码输出的结果?如果有可能的话,这种情况的可能性有多大? (我专门询问 AES256)。

假设我不知道加密数据是什么,并且无法控制其加密,因此除了通过解密尝试之外,我无法验证它。

I know CryptoStream's FlushFinalBlock method throws when the padding is wrong which likely means the key was wrong.

What I'm trying to figure out is whether this is a bullet-proof method of testing the authenticity of the key itself, i.e, whether FlushFinalBlock is guaranteed to throw if given the wrong key. Or, put differently, is it possible for the wrong key to yield a result that throws no exception but is just garbled output? If it is possible what is the rough likelihood of this? (I'm asking about AES256 specifically).

Assume I don't know what the encrypted data is and have no control over its encryption, so I have no way to validate it other than through a decryption attempt.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

撑一把青伞 2025-01-24 06:07:58

直接回答这个问题:使用错误的密钥(即加密中未使用的密钥)解密密文并不一定会导致异常。

以下 C# 代码显示了使用三个密钥对密文进行解密(密文是使用 AES/CBC 和 PKCS#7 填充生成的):

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
...
static void Main(string[] args)
{
    byte[] iv = Convert.FromHexString("4B4D907F815EAB0EA3E8A2140968C395");
    byte[] ciphertext = Convert.FromHexString("4A9BE2E236868EC0D04F0D280A2876920F79C10969F32D751FF1976E6446F7BBF2469957130E4EE1CC56C386426E1C5C");

    // 1. 
    testCase(1, Convert.FromHexString("5DBF2259D534802EA0C8B24FD6CA876C345AE4C4AE2E000BCB05B33E7465FAEF"), iv, ciphertext);

    // 2. 
    testCase(2, Convert.FromHexString("DFAC3EDECC5F53EF1ACFEE07085A2A5C4327D1057BED2405D5E18EF12DE05C63"), iv, ciphertext);

    // 3. 
    testCase(3, Convert.FromHexString("000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F"), iv, ciphertext);
}

private static void testCase(int num, byte[] key, byte[] iv, byte[] ciphertext)
{
    Console.WriteLine("" + num + ".");
    try
    {
        byte[] decryptedNone = Decrypt(ciphertext, key, iv, PaddingMode.None);
        byte[] decryptedPkcs7 = Decrypt(ciphertext, key, iv, PaddingMode.PKCS7);
        Console.WriteLine("before depadding: " + Convert.ToHexString(decryptedNone));
        Console.WriteLine("after depadding:  " + Convert.ToHexString(decryptedPkcs7));
        Console.WriteLine("UTF-8 decoded:    " + Encoding.UTF8.GetString(decryptedPkcs7));
        Console.WriteLine();
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}

private static byte[] Decrypt(byte[] cipherText, byte[] Key, byte[] IV, PaddingMode pm)
{
    byte[] plaintext = null;
    using (Aes aesAlg = Aes.Create())
    {
        aesAlg.Key = Key;
        aesAlg.IV = IV;
        aesAlg.Padding = pm;
        ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
        using (MemoryStream msDecrypt = new MemoryStream(cipherText))
        {
            using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
            {
                using (MemoryStream plainTextStream = new MemoryStream())
                {
                    csDecrypt.CopyTo(plainTextStream);
                    plaintext = plainTextStream.ToArray();
                }
            }
            msDecrypt.Close();
        }
    }
    return plaintext;
}
  • 第一次解密使用正确的密钥。解密后的明文最后5个字节是0x05050505。这符合 PKCS#7,即填充有效并将自动删除。解密成功,明文与原明文相同。

  • 第二次解密使用了错误的密钥。巧合的是,解密后的明文的最后 2 个字节是 0x0202,这也符合 PKCS#7。因此,该数据被解释为有效填充并自动删除。没有抛出异常。当然,解密是不成功的,因为明文与原始明文不符。

  • 第三次解密也使用了错误的密钥。这里最后的字节不符合 PKCS#7。因此,这被解释为无效填充,并引发相应的异常(CryptographicException:填充无效且无法删除)。

因此,输出为:

1.
before depadding: 54686520717569636B2062726F776E20666F78206A756D7073206F76657220746865206C617A7920646F670505050505
after depadding:  54686520717569636B2062726F776E20666F78206A756D7073206F76657220746865206C617A7920646F67
UTF-8 decoded:    The quick brown fox jumps over the lazy dog

2.
before depadding: 4D5B5017ADF3B5EFB5716AB2CCFF8BA6129E35A123287CD564F12C2401AB985EEBC7E274B685595251059C56E2BC0202
after depadding:  4D5B5017ADF3B5EFB5716AB2CCFF8BA6129E35A123287CD564F12C2401AB985EEBC7E274B685595251059C56E2BC
UTF-8 decoded:    M[P↨???qj?????↕?5?#(|?d?,$☺??^???t??YRQ♣?V?

3.
Padding is invalid and cannot be removed.

2.,即错误密钥不会导致异常的概率是多少?对于 PKCS#7,填充字节的值对应于填充字节的数量,即,对于块大小为 16 字节的 AES,以下填充是可能的,请参阅 PKCS#7 了解详细信息:

0x01, 0x0202, 0x030303, ..., 0x10101010101010101010101010101010 (full block) 

末尾 0x01 的概率为265-1,对于 0x0202 265-2 等,整个块最多为 265-16。即,特别是小填充字节对概率的贡献是不可忽略的。

上述概率适用于检查所有填充字节的假设(这可能不适用于所有实现)。如果检查的填充字节较少,则概率会增加,请参阅其他答案


到目前为止,已经考虑了分组密码模式(例如CBC),这通常需要强制填充。还有一些不需要填充的流密码模式(例如 CTR)。对于这些,通常不会因为缺少填充而引发异常。


正如 评论中所述< /a> 并在 其他答案,经过身份验证的加密/解密(例如GCM)提供了一种可靠的方法来检测密文是否被篡改以及是否正在使用正确的密钥。

To answer the question straight away: Decrypting a ciphertext with an wrong key, i.e. a key that was not used in the encryption, does not necessarily result in an exception.

The following C# code shows the decryption of a ciphertext with three keys (the ciphertext was generated with AES/CBC and PKCS#7 padding):

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
...
static void Main(string[] args)
{
    byte[] iv = Convert.FromHexString("4B4D907F815EAB0EA3E8A2140968C395");
    byte[] ciphertext = Convert.FromHexString("4A9BE2E236868EC0D04F0D280A2876920F79C10969F32D751FF1976E6446F7BBF2469957130E4EE1CC56C386426E1C5C");

    // 1. 
    testCase(1, Convert.FromHexString("5DBF2259D534802EA0C8B24FD6CA876C345AE4C4AE2E000BCB05B33E7465FAEF"), iv, ciphertext);

    // 2. 
    testCase(2, Convert.FromHexString("DFAC3EDECC5F53EF1ACFEE07085A2A5C4327D1057BED2405D5E18EF12DE05C63"), iv, ciphertext);

    // 3. 
    testCase(3, Convert.FromHexString("000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F"), iv, ciphertext);
}

private static void testCase(int num, byte[] key, byte[] iv, byte[] ciphertext)
{
    Console.WriteLine("" + num + ".");
    try
    {
        byte[] decryptedNone = Decrypt(ciphertext, key, iv, PaddingMode.None);
        byte[] decryptedPkcs7 = Decrypt(ciphertext, key, iv, PaddingMode.PKCS7);
        Console.WriteLine("before depadding: " + Convert.ToHexString(decryptedNone));
        Console.WriteLine("after depadding:  " + Convert.ToHexString(decryptedPkcs7));
        Console.WriteLine("UTF-8 decoded:    " + Encoding.UTF8.GetString(decryptedPkcs7));
        Console.WriteLine();
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}

private static byte[] Decrypt(byte[] cipherText, byte[] Key, byte[] IV, PaddingMode pm)
{
    byte[] plaintext = null;
    using (Aes aesAlg = Aes.Create())
    {
        aesAlg.Key = Key;
        aesAlg.IV = IV;
        aesAlg.Padding = pm;
        ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
        using (MemoryStream msDecrypt = new MemoryStream(cipherText))
        {
            using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
            {
                using (MemoryStream plainTextStream = new MemoryStream())
                {
                    csDecrypt.CopyTo(plainTextStream);
                    plaintext = plainTextStream.ToArray();
                }
            }
            msDecrypt.Close();
        }
    }
    return plaintext;
}
  • The 1st decryption uses the correct key. The last 5 bytes of the decrypted plaintext are 0x05050505. This is compliant with PKCS#7, i.e. the padding is valid and will be removed automatically. The decryption is successful, the plaintext is the same as the original plaintext.

  • The 2nd decryption uses a wrong key. By chance the last 2 bytes of the decrypted plaintext are 0x0202, which is also compliant with PKCS#7. Therefore this data is interpreted as valid padding and removed automatically. No exception is thrown. Of course, the decryption is not successful because the plaintext does not match the original plaintext.

  • The 3rd decryption also uses a wrong key. Here the last bytes are not compliant with PKCS#7. Therefore this is interpreted as invalid padding and a corresponding exception is thrown (CryptographicException: Padding is invalid and cannot be removed).

Accordingly, the output is:

1.
before depadding: 54686520717569636B2062726F776E20666F78206A756D7073206F76657220746865206C617A7920646F670505050505
after depadding:  54686520717569636B2062726F776E20666F78206A756D7073206F76657220746865206C617A7920646F67
UTF-8 decoded:    The quick brown fox jumps over the lazy dog

2.
before depadding: 4D5B5017ADF3B5EFB5716AB2CCFF8BA6129E35A123287CD564F12C2401AB985EEBC7E274B685595251059C56E2BC0202
after depadding:  4D5B5017ADF3B5EFB5716AB2CCFF8BA6129E35A123287CD564F12C2401AB985EEBC7E274B685595251059C56E2BC
UTF-8 decoded:    M[P↨???qj?????↕?5?#(|?d?,$☺??^???t??YRQ♣?V?

3.
Padding is invalid and cannot be removed.

What are the probabilities for 2., i.e. for the case that a wrong key does not cause an exception? For PKCS#7 the value of the padding bytes corresponds to the number of padding bytes, i.e. the following paddings are possible for AES with a block size of 16 bytes, see PKCS#7 for details:

0x01, 0x0202, 0x030303, ..., 0x10101010101010101010101010101010 (full block) 

The probability, for a 0x01 at the end is 265-1, for 0x0202 265-2, etc. up to 265-16 for the full block. I.e. especially the contribution of the small padding bytes to the probability is not negligible.

The above probabilities apply under the assumption that all padding bytes are checked (which may not be true for all implementations). If fewer padding bytes are checked, the probability increases, see the other answer.


So far, block cipher modes (such as CBC) have been considered, which generally require mandatory padding. There are also stream cipher modes (such as CTR) that do not require padding. For these, no exception is generally thrown because of the missing padding.


As noted in the comment and elaborated in the other answer, authenticated encryption/decryption (e.g. GCM) provides a reliable way to detect whether the ciphertext has been tampered with and also whether the correct key is being used.

贩梦商人 2025-01-24 06:07:58

通常(对于 .NET 默认情况下,对于 ECB 和 CBC,默认情况下 CBC)使用 PKCS#7 兼容填充。在这种情况下,错误密钥的结果是完全随机的最终明文块。对于单个填充字节,正确填充的几率约为 2^256 分之一(接下来的可能性越来越小,65536 分之一等,因此它们相对无关紧要)。一些取消填充例程将(错误地)仅检查最后一个字节,在这种情况下,机会是 256 中的 16 或 16 中的 1。

对于 PKCS#7,总是添加填充。这是有道理的,因为解密例程不知道长度,并且(二进制)消息可能会偶然以填充字节结束 - 正如已经建立的那样。所以你会有 16 个字节的值 0x10。由于仍然使用取消填充,因此创建有效填充的机会当然仍然是 256 分之一,因为取消填充例程不知道所使用的填充。

要解决填充问题,您需要一个身份验证标签。该标签可以通过在密文上使用 HMAC 来生成,同时包含 IV(如果攻击者可以更改 IV)。您还可以使用 GCM 模式,它是不久前(即非常非常晚)添加到 .NET 中的,它既更高效,又不易出错(您不必显式包含 IV,并且验证更容易)或更少给定)。

请注意,取消填充的结果也可用于填充预言机攻击,这是一种通过仅具有解密例程的可用性而创建的明文预言机攻击。这些攻击可以通过平均每字节执行 128 次解密操作来完全检索明文。

Usually (and for .NET by default, for ECB and CBC, with CBC being the default) PKCS#7 compatible padding is used. In that case the result of a wrong key is a fully randomized final plaintext block. The chance of correct padding is about one in 2^256 for a single padding byte (the next ones are less and less likely, 1 in 65536 etc, so they are relatively inconsequential). Some unpadding routines will (incorrectly) only check the last byte, in which case the chance is 16 out of 256, or 1 out of 16.

For PKCS#7, the padding is always added. This makes sense because the decryption routine doesn't know the length, and the (binary) message may end with padding bytes by chance - as already established. So you'd have 16 bytes values 0x10. As unpadding is still used, the chance of creating a valid padding is of course still 1 in 256, as the unpadding routine doesn't know the padding used.

To get around issues with the padding you need an authentication tag. That tag can be generated by using a HMAC over the ciphertext while including the IV if that can be altered by an attacker. You could also use GCM mode, which has been added to .NET not too long ago (i.e. very, very late), which is both more efficient as less error prone (you don't have to explicitly include the IV and verification is more or less a given).

Note that the result of unpadding can also be used for padding oracle attacks, which are a type of plaintext oracle attack created by just having availability of a decrypt routine. These attacks can fully retrieve the plaintext by performing average 128 decryption ops per byte.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文