1

这是所有密码学家的求助。

场景:我有一个 Windows 应用程序(可能使用 VC++ 或 VB 构建,随后移至 .Net),它将一些密码保存在 XML 文件中。给定密码A0123456789abcDEFGH,生成的“加密”值为04077040940409304092040910409004089040880408704086040850404504044040430407404073040720407104070

查看字符串,我发现这只是字符转换:'04' 分隔实际的字符值,它们是十进制的;如果我从 142 中减去这些值,我会得到原始的 ASCII 码。在 Jython (2.2) 中,我的解密例程如下所示(编辑感谢评论中的建议):

blocks = [ pwd[i:i+5] for i in range(0, len(pwd), 5) ] 
# now a block looks like '04093'
decrypted = [ chr( 142 - int(block[3:].lstrip('0')) ) for block in blocks ]

这适用于 ASCII 值(总共 127 个)和少数重音字母,但 8 位字符集还有 128 个字符;从十进制的角度来看,将接受的值限制为 142 是没有意义的。

编辑:我翻遍了我们的系统,发现了三个非 ASCII 字符:

è 03910
Ø 03926
Õ 03929

从这些值中,看起来实际上从 4142 中减去 4 数字块(只留下“0”作为分隔符)给了我正确的字符。

所以我的问题是:

  • 有人熟悉 Windows 世界中的这种混淆方案吗?这可能是标准库函数的产物吗?老实说,我对 Win32 和 .Net 开发不是很熟悉,所以我可能会遗漏一些非常简单的东西。

  • 如果它不是一个库函数,你能想出一种更好的方法来对这些值进行去混淆而不使用神奇的 142 数字,即一种实际上可以应用于非 ASCII 字符而无需特殊大小写的方案?我在位移和所有这些方面都很糟糕,所以我可能会再次错过训练有素的眼睛明显的东西。

4

1 回答 1

2

有人熟悉 Windows 世界中的这种混淆方案吗?

一旦你正确理解它,它只是一个像ROT13这样的微不足道的旋转密码。

为什么会有人用这个?

嗯,一般来说,这很常见。假设您有一些需要混淆的数据。但是解密算法和密钥必须嵌入到观众拥有的软件中。使用像 AES 这样的花哨的东西是没有意义的,因为有人总是可以从你的代码中挖掘算法和密钥,而不是破解 AES。比找到隐藏密钥更难破解的加密方案与完美的加密方案一样好——也就是说,足以阻止普通观众,而对严重的攻击者毫无用处。(通常你甚至并不真正担心停止攻击,但要在事实之后证明您的攻击者出于合同/法律原因必须恶意行事。)因此,您使用简单的旋转密码或简单的异或密码 - 它很快,很难出错并且容易进行调试,如果最坏的情况发生,您甚至可以手动解密以恢复损坏的数据。

至于细节:

如果要处理非 ASCII 字符,则几乎必须使用 Unicode。如果您使用一些固定的 8 位字符集或本地系统的 OEM 字符集,您将无法处理来自其他机器的密码。

Python 脚本几乎肯定会处理 Unicode 字符,因为在 Python 中,您要么处理 a 中的字节,要么处理 a 中的strUnicode 字符unicode。但 Windows C 或 .NET 应用程序更可能使用 UTF-16,因为 Windows 原生 API 处理 UTF-16-LE 代码点WCHAR *(也称为 16 位字串)。

那么,为什么是 4142?好吧,关键是什么并不重要。我猜一些程序员建议42。他的经理然后说:“这听起来不太安全。” 他叹了口气说:“我已经解释过为什么没有钥匙会比……你知道吗,算了吧,4142呢?” 经理说:“哦,这听起来很安全!” 所以这就是4142的原因。


如果它不是一个库函数,你能想出一个更好的方法来对这些值进行去混淆,而无需求助于神奇的 142 数字。

你确实需要使用魔法 4142,但你可以让它变得更简单:

def decrypt(block):
    return struct.pack('>H', (4142 - int(block, 10)) % 65536)

因此,每个 5 个字符的块是 UTF-16 代码单元的十进制表示,使用 C 无符号短环绕规则从 4142 中减去。

这在本机 Windows C 中实现是微不足道的,但在 Python 中稍微难一些。我能想到的最好的转换函数是:

def decrypt_block(block):
    return struct.pack('>H', (4142 - int(block, 10)) % 65536)

def decrypt(pwd):
    blocks = [pwd[i:i+5] for i in range(0, len(pwd), 5)] 
    return ''.join(map(decrypt_block, blocks)).decode('utf-16-be')

这在 C 或 C# 中会更简单,这可能是他们实现的东西,所以让我解释一下我在做什么。

您已经知道如何将字符串转换为由 5 个字符组成的块序列。

Myint(block, 10)正在做与您相同的事情int(block.lstrip('0')),确保'0'前缀不会使 Python 将其视为八进制数字而不是十进制,但更明确。我认为这在 Jython 2.2 中实际上是不必要的(在更现代的 Python/Jython 中肯定不是),但我留下了它以防万一。

接下来,在 C 中,您只需执行unsigned short x = 4142U - y;,它会自动适当地下溢。Python 没有unsigned short值,只有 signed int,所以我们必须手动进行下溢。(因为 Python 使用底除法和余数,符号总是与除数相同——这在 C 语言中不成立,至少在 C99 和大多数平台的 C89 中不成立。)

然后,在 C 中,我们只需将 unsigned short 转换为 16 位“宽字符”;Python 没有办法做到这一点,所以我们必须使用struct.pack. (请注意,我将其转换为大端序,因为我认为这使调试更容易;在 C 中,您将转换为原生端序,并且由于这是 Windows,因此将是小端序。)

所以,现在我们得到了 2 个字符的 UTF-16-BE 代码点序列。我只是join将它们变成一个大字符串,然后decode将其作为 UTF-16-BE。


如果您真的想测试我是否正确,您需要找到不仅是非 ASCII 字符,而且是非西方字符。特别是,您需要:

  • > U+4142 但 < U+10000 的字符。大多数 CJK 表意文字,如 U+7000 (瀀),都符合要求。这应该显示为'41006',因为这是 4142-0x7000 翻转为无符号空头。
  • >= U+10000 的字符。这包括不常见的 CJK 字符、专门的数学字符、来自古代文字的字符等。例如,旧斜体字符 U+10300 () 编码为代理对 (0xd800, 0xdf00);4142-0xd800=14382 和 4142-0xdf00=12590,所以你会得到'1438212590'.

第一个很难找到——即使是我接触过的大多数以中文和日文为母语的程序员都使用 ASCII 密码。第二个,更是如此;除了历史语言学教授之外,没有人会想到在他们的密码中使用古文字。根据墨菲定律,如果您编写了正确的代码,它将永远不会被使用,但如果您不这样做,它保证会在您发布代码时立即出现。

于 2013-09-17T18:56:49.947 回答