首页 > 解决方案 > AES 128 解密,密文比密钥短

问题描述

我们正在开发一个应用程序,该应用程序必须处理由 LoraWan ( https://www.lora-alliance.org )加密的数据

我们已经找到了他们如何加密数据的文档,并且在过去的几天里一直在阅读它(https://www.lora-alliance.org/sites/default/files/2018-04/lorawatm_specification_-v1 .1.pdf ) 但目前仍然无法解决我们的问题。

我们必须使用带有零填充的 AES 128 位 ECB 解密来解密消息,但问题是它不起作用,因为我们收到的加密消息对于 AES 128 来说不够长,因此算法返回“数据不是完成块”的最后一行异常。

我们收到的一个示例密钥是这样的:D6740C0B8417FF1295D878B130784BC5(不是真正的密钥)。它是 32 个字符长,因此是 32 个字节,但如果将其视为十六进制,则它变为 16 个字节长,这是 AES 128 位所需要的。这是我们用来从字符串转换十六进制的代码:

public static string HextoString(string InputText)
{byte[] hex= Enumerable.Range(0, InputText.Length)
                 .Where(x => x % 2 == 0)
                 .Select(x => Convert.ToByte(InputText.Substring(x, 2), 16))
                 .ToArray();
return System.Text.Encoding.ASCII.GetString(hex);}

(上面代码需要注意的一点是,我们不确定要使用什么编码,因为我们在 Lora 文档中找不到它并且他们没有告诉我们,但是根据这个小设置,我们可能会搞砸我们的解密(尽管我们已经尝试了所有可能的组合,ascii、utf8、utf7 等))

我们收到的示例消息是:d3 73 4c,我们假设它也是十六进制的。这只有 6 个字节,如果我们将其从十六进制转换为普通字节,则需要 3 个字节,而我们需要最少的 16 个字节来匹配密钥长度。

这是我们正在使用的 Aes 128 解密代码:

private static string Aes128Decrypt(string cipherText, string key){

string decrypted = null;

var cipherPlainTextBytes = HexStringToByteArray(cipherText);
//var cipherPlainTextBytes = ForcedZeroPadding(HexStringToByteArray(cipherText));
var keyBytes = HexStringToByteArray(key);

using (var aes = new AesCryptoServiceProvider())
{
    aes.KeySize = 128;
    aes.Key = keyBytes;
    aes.Mode = CipherMode.ECB; 
    aes.Padding = PaddingMode.Zeros; 

    ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
    using (MemoryStream ms = new MemoryStream(cipherPlainTextBytes, 0, cipherPlainTextBytes.Length))
    {
        using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
        {
            using (StreamReader sr = new StreamReader(cs))
            {
                decrypted = sr.ReadToEnd();
            }
        }
    }
}
return decrypted;}

所以很明显这将在 sr.ReadToEnd() 处返回“数据是一个不完整的块”。

正如您从示例中看到的那样,在注释掉的行中,我们还尝试使用正确长度的完整零字节数组(16 - cipherText)将文本“填充”到正确的大小,在这种情况下,算法运行很好,但它返回完整的乱码,而不是原始文本。

我们已经尝试了所有的操作模式,并且也使用了填充模式。除了密文和该文本的密钥,他们没有为我们提供任何东西。也没有初始化向量,所以我们假设我们应该每次都生成它(但对于 ECB,它甚至不需要 iirc)

更重要的是,他们能够很好地加密-解密他们的消息。最令人费解的是,我已经用谷歌搜索了好几天了,我在 google 上找不到一个 SINGLE 示例,其中 CIPHERTEXT 在解密期间比密钥短。

显然,我发现了他们正在加密的消息比所需消息短的示例,但这就是加密端的填充(对吗?)。这样当您收到填充消息时,您可以告诉算法使用什么填充模式使其长度正确,这样它就可以将填充与消息分开。但在所有这些情况下,解密期间收到的消息的长度都是正确的。

所以问题是——我们做错了什么?有没有办法用比密钥短的密文解密?还是他们通过生成太短的密码而在某个地方搞砸了?

谢谢你的帮助。

标签: encryptionaesblock-cipherkey

解决方案


在 AES-ECB 中,短于 16 字节的唯一有效密文是 empty。这个 16 字节的限制是 AES 的块(不是密钥)大小,恰好与 AES-128 的密钥大小相匹配。

因此,问题的

我们收到的示例消息是:d3 73 4c

不显示 ECB 加密消息(因为评论告诉它来自 JSON,所以不能是恰好显示为十六进制的字节)。这太短了,不能成为 Join-Accept 的 FRMPayload(根据此评论),因为规范说的是后者:

1625     消息长度为 16 或 32 个字节。

难道 JSON 消息包含的不是完整的 FRMPayload,而是一个数据包的片段,编码为带有空格分隔符的十六进制对?只要不知道如何构建 FRMPayload,解密它就没有意义。

更新:如果该神秘消息始终为 3 个字节,并且对于给定的密钥(或每个密钥一次可用)始终相同,则根据 Maarten Bodewes 的评论,它可能是密钥检查值。KCV 通常是全零值加密的前 3 个字节,每个原始分组密码(相当于:每个 ECB)的密钥。Herbert Hanewinkel 的 javascript AES可以完全离线工作(这是不暴露密钥所必需的),并用于手动验证假设。它表明对于问题中给出的 16 字节密钥,KCV 将是cd15e1(或c076fc根据下一节中的变体)。


此外:它用于CreateDecryptor制作负责 ECB 解密的小玩意儿。在 LoraWan 有效负载的解密上下文中,这可能是不正确的,这需要 ECB加密来解密某些字段:

1626     注意:ECB 模式下的 AES 解密操作用于对 join-accept 消息进行加密,以便终端设备可以使用 AES 加密操作对消息进行解密。这样,终端设备只需执行 AES 加密而不是 AES 解密。


在解密 LoraWan 数据包的上下文中,您希望使用字节数组而不是字符串与 AES 引擎进行通信。字符串有编码,而 LoraWan 密文和相应的明文没有。其他人似乎已经设法强制使用漂亮的 .NET do-it-all 加密 API 来完成低级工作。


HextoString代码中,我隐约明白,其意图和结果可能是hex成为最初的十六进制输入作为字节数组(完全摆脱了十六进制和其他编码罪恶;在这种情况下,变量hex应该重命名为pure_bytes. 但后来我不知所措System.Text.Encoding.ASCII.GetString(hex)。如果它只是从字节数组创建了一个字节字符串,或者将密钥转回十六进制以便稍后输入HexStringToByteArrayin ,我会感到惊讶Aes128Decrypt。另外,让我担心 [0x80..0xFF] 中的任何字节都可能变成 0x3F,这对于密钥、密文和相应的 LoraWan 有效负载都不好。这些在去十六进制时没有字符编码。

我的结论是,如果HexStringToByteArray它的名字所暗示的那样,并且给定 的当前接口Aes128DecryptHextoString应该简单地删除空格(或者如果动态HexStringToByteArray删除空格则不需要)。但我的建议是将接口更改为使用字节数组,而不是字符串(参见上一节)。


顺便说一句:从其密钥创建一个 ICryptoTransform 对象应该执行一次以用于对象的多次使用。


推荐阅读