多次使用长分隔符会降低加密安全性吗?

信息安全 加密
2021-08-31 08:40:38

我需要将多个不同的数据压缩成一个加密字符串,以后可以解密和分离出来。

在加密之前,我需要用某种永远不会与数据混淆的分隔符来分隔数据。

我选择使用一个单一的、恒定的 20 个字符的十六进制分隔符。例如:

data_piece
data_item
data_obj

变成

data_piece214c1a16bb5236e7090cdata_item214c1a16bb5236e7090cdata_obj

然后变成

vjXC4Xd7LU6aZX4QClZkU330XT39hnoLoQYIFNov39tPX96OKsid7mOBHwoVb4KspyvMpVPrsfHCUd1zbzXyETtgW5yF4b0oaK8Q%2FZCZN2XBvbfL3vooD%2FDLOza3%2FSrSNNzIW8oALZhv08LBzeg3DvgUgC8fg0xv4%2BCAEIQLIhM%3D

在通过具有 256 位密钥和 url 编码器的标准 Rijndael 加密算法运行它之后。

如果在某些情况下可能会使用两打次,这种重复单个分隔符是否会降低加密的安全性?如果是这样,我应该使用一组唯一分隔符,以便它们永远不会在同一个字符串中重复,或者差异对于实际加密目的真的无关紧要吗?

更新:

我选择使用长分隔符而不是小分隔符的原因是针对以下情况:

delimiter = |
data1 = mydata\\\\
data2 = \|\|\|data

unescaped: mydata\\\\|\|\|\|data

escaped: mydata\\\\\\\\|\\\|\\\|\\\|data

编写代码来取消转义并将其分离出来并不是很简单。可以这样做,但必须有一个循环不断寻找分隔符之前的转义字符序列的结尾,然后仅当序列中的转义字符数为奇数时才取消转义。

由于这涉及到每个 unescape 函数之前的检查,我认为长分隔符更好,因为它实际上可以保证永远不会出现在数据中,并允许分离过程尽可能简单。

4个回答

您正在提供所谓的“婴儿床”。如果您使用长序列,尤其是在开始时,您失去一些(尽管很少)安全性。

只要您能够恢复它,您就可以通过使用随机字符串轻松缓解该问题。例如,您可以使用 20 字节的随机字符串。您知道前 20 个字节是分隔符,并在字符串的其余部分中查找它。这仍然为暴力攻击者提供了一个检查,以确定他是否找到了正确的解密,但是暴力本身应该足够笨重,以至于这个小小的帮助几乎没有用。在运行测试之前,他仍然需要解密相当大一部分密文。与仅解密婴儿床部分相比,这是一个改进。

您还可以使用的、转义的分隔符。与其“打赌”(有利于您的天文机会)分隔符永远不会“被动地”出现在文本中,不如通过替换或转义来积极努力使其不出现。例如,您可以转义所有“\”和“|”,然后确定单个“|” 将代表一个分隔符。未转义的“|”的机会 在错误解码的密文中是天文数字,这使得抄袭对攻击者来说毫无价值。同时,您转义的裸“|”保证您的明文中不会出现裸“|”。另一方面,这需要一个额外的阶段:

 plain|text\nand --> plain\|text\\nand|another text --> ....
 another text

如果您选择一个通常被转义的定界字符,例如 $,您可以使用多个标准库和各种语言的命令来执行此操作。

更新

至于安全性:开头的随机分隔符不会因为在开头而降低安全性:因为它是随机的,所以不能用作婴儿床。安全性降低的原因是分隔符将在解密文本中出现多次,从而确认它确实是正确解密的文本。(如果分隔符很短,它在不正确的文本中自然出现的机会是不可忽略的,因此它的出现不是确认。当然它的出现也不能是确认)。定界符必须出现在开头,因为这是您知道定界符是什么的唯一方法(定界符是随机的......)每个密文都有自己的。

空间考虑

TL;DR除非您有很多块和/或非常短的块,否则分隔符可能是节省空间的方法。

通常,您将对传入数据有足够的了解,可以选择一个不常见的字符作为分隔符和转义字符,或者至少,您将很少有“病态的”明文。为了最大限度地压缩数据,理论上您可以编写一个函数,该函数将 (a) 确定任何给定明文中不常用的两个字符,以及 (b) 将它们用作分隔符和转义符。这些字符最多每 256 个字符出现 3 次。它们都需要转义,会使 256 个字符增长到 259 个,即大小增加 1.2%。此外,您将需要存储这两个字符,例如在开头以便知道如何取消转义字符串。所以我们有两个字节的固定开销,大小开销为 1.2%,分隔符开销为 1:1;

使用定界符和长度为 L 字节的未转义明文,在位置 x 处出现长度为 D 的序列的概率为 (1/256)^D,并且 x 有 (L-D+1) 个可能值。所以 D 不在任何地方出现的概率是 1-(1-(1/(256^D)))^(L-D+1)。

(或者=(1-POWER((1-POWER(1/256,$D$1)),A2-$D$1+1))*1000000,如果您想将其放入 Google 电子表格并以百万分之一计算碰撞概率)。

对于大小为 16K 的块序列,为了确保 Pcoll < 1/百万,我至少需要一个定界符长度为 5(这是矫枉过正;但 4 太短了,给出大约 4/1000000 的 Pcoll)。

所以四个 4K 块每个需要 1.012*16384+4+2 = 16586 字节转义和 16384+4*5 没有,即分隔符允许节省大约 182 个字节(分隔符出现的可能性较小小于百万分之一),如果使用 6 个字符的分隔符,则为 178 个字节(可能性小于十亿分之一,或以前的千分之一)。

我们在 1.012*16384+N+2 = 16384+N*5 时达到奇偶校验,即当您有超过 50 个块时(如果使用六字符分隔符,则为 38 个块)。

PHP

在 PHP 中工作,我认为您可能会发现这样的序列是有利的:

  • 序列化(您获得一个包含所有块的字符串)
  • gzcompress(节省空间,并获得最大熵的数据块)
  • 加密

暴力破解第一个 AES 块将允许验证解密密钥是否正确(gzcompressed 序列化对象的开头可能充当婴儿床),但首先这样做(并清除误报)在计算上仍然不可行。把钥匙从你身上敲下来还是比较便宜的并且实现和可维护性的优势是值得冒险的。

您所描述的是已知明文攻击的变体,这是 Rijndael 密码家族高度抵抗的一种加密攻击。

使用如此长的定界符,整个加密块很可能只包含定界符字符串中的字符,这给攻击者一个小优势(他们不需要担心数据的非定界符部分),但执行攻击太难了,不值得担心。

假设“Rijndael”是指 AES-256(具有 256 位密钥和 128 位块的 Rijndael),您甚至可以通过使用 15 个字符的字符串进行分隔来消除这个微不足道的弱点。这样的字符串比块大小短一个字节,因此您确保始终将至少一个字节的变化数据编码在与分隔符相同的块中。

您需要考虑您正在使用的加密模式,还需要考虑大多数加密模式本身并不能验证数据的事实。

我建议使用 GCM 模式,因为它速度快,在 TLS 1.2 中使用,使用 IV,因此无论您的消息以婴儿床开头是什么都不可行。它还在同一次传递中对消息进行身份验证,因此您可以知道您解密的内容是有效/真实的。

但是关于模式的最终答案和对初学者的警告在这里: https ://stackoverflow.com/a/22958889/2238268

如果您真的关心安全性,您真的必须使用了解许多微妙方面的专家。安全性不仅通过技术控制来实现,还需要组织承诺和承诺。

我还将使用更强大的分隔符方法。单个分隔符和使用简单的正则表达式字符串替换进行适当的转义就足够了,并且可以避免对系统的更高阶攻击。

是的。但是加密(使用 AES-256)仍然足够强大,可以抵御现实世界的暴力攻击,因此增加的弱点可以忽略不计。

可以通过对每个压缩数据字符串使用随机分隔符来改进这种长分隔符技术。

但是,在任何情况下,使用长分隔符都不是最好的技术。对于安全性和内存使用而言,一个字符的分隔符就足够了,而且更好。要缓解我上面描述的问题,请使用以下过程:

delimiter = |
data1 = mydata\\\\
data2 = \|\|\|data

encode data separately to remove delimiter character altogether
(ex. using php rawurlencode)
mydata%5C%5C
%5C%7C%5C%7C%5C%7Cdata

serialize the data
mydata%5C%5C|%5C%7C%5C%7C%5C%7Cdata

encrypt the serialization
8XcaEW2st4qBZhGB1MbO200eLbhhoOV3V4MCpa9k6ODiN6dcJypTWabq2YsUwBC2tnkKdaUOU7jviilahNQ2B+DRvtLMYDrFNp3qHh0oWMUAuAnjCcKuHfE9tIcd/Jhv

如果您反转该过程,则分离数据并将其恢复为原始形式非常简单。

请注意,最终加密字符串比使用长分隔符的字符串短 30%。

有关更多详细信息和节省空间的方法,请参阅 lserni 的答案。