从MD5构造海绵功能

2021-03-26 21:42:49

在进行一些研究时,我遇到了“海绵函数”一词。在与他们一起玩并在我的内核内实施一个,我决定写这个博客文章有关如何编写简化版本。要将低级加密代码保持为最小,我们将依靠哈希函数MD5。扣上,这将是很长的一个。

本文将从MD5哈希的简单概念开始,并逐步构建它,直到我们实施大量常用功能,似乎是黑匣子。每一步都应该足够小,以便单独消化,同时仍然有助于对主题的整体理解。如果有任何清楚,请随时在评论中讨论。该帖子被设计成使您可以随时随地暂停和播放概念,如果这是您喜欢的话,才能通过它速度。

由于我们将在MD5哈希函数上基于项目,因此让我们留出一小部分来经历它是什么。我们将把MD5视为一个黑匣子,为简洁起见,忽略任何复杂的细节。

MD5是加密散列函数,可将任意数量的数据映射到16个字节(或128位)。在鼎盛时期,MD5是散列密码,检查损坏文件的可选择,并将数据标记为篡改。那些日子已经一去不回。它被认为已被破坏了一段时间了,并不建议使用它的任何安全相关的东西。但它是众所周知的,并且已经为几乎所有创建的计算设备实施了。幸运的是,Python捆绑了Hashlib模块中的哈希函数集合。让我们快点看它是如何工作的。

正如我们所看到的,不同长度映射的输入到固定尺寸输出,并且输入的小变化导致完全不同的输出值。基本上我们期望哈希函数。在本文中,我们将使用MD5哈希函数来创建海绵函数。但在我们能做到之前,我们需要知道海绵函数。

海绵函数是一个加密功能,可以“吸收”任何数量的位和“挤压”任何数量的位,就像海绵。这与我们观察到的大约MD5不同;虽然MD5只产生16个字节的固定尺寸输出,但海绵函数可以输出1字节,26个字节,5000个字节或您喜欢的任何号码。这听起来有趣,可能对许多不同的任务有用,因此让我们做一些不明的编程并将MD5变成海绵函数。

海绵功能令人着迷。您可以使用海绵功能作为哈希函数,随机数生成器,消息认证码或数据加密。将一个是一个加密瑞士军刀。

为了创建海绵函数,我们需要一个内部状态(只是缓冲区),并函数伪装成将一个状态转换为另一个状态。我们将利用MD5哈希的两个属性,我们的状态缓冲区将是MD5输出的16个字节,我们的变换功能将是MD5本身。

海绵函数隐藏的大部分内部状态。吸收的位和位挤压只有触摸它的一小部分,所以输出永远不会透露功能的完整状态。

第一步是初始化状态,无论是零还是明智的默认值。

此过程将所有输入数据吸收到状态。我们吸收了我们的输入后,我们可以在遵循非常相似的过程时挤出尽可能多的输出字节。对于我们想要生产的每个字节:

警告!您可能不想为任何太敏感使用此功能。这是一个使用破损的MD5函数作为基础的概念验证实现。至少选择更好的东西,如Chacha20或SHA-512。一般来说,我们想要一个大状态和变换功能,可以很好地混合状态。

现在我们已经简要走过了这个理论,让我们来实现。我们将逐步逐步并实现我们上面提到的每个操作。我们的第一步是转换功能。

根据我们上面概述的理论,我们需要一个转变函数,将我们的州和伪安地多组将其映射到另一个州。在我们的案例中,MD5哈希函数将为我们做繁重的举重。通过沉重的举重,我的意思是MD5将做几乎整个工作。

我们可以通过通过MD5功能转换当前状态。这是一个小型示范。

看起来一切都在工作。让我们在海绵类的方法中封装这一点。每次吸收或挤压字节时,我们都会使用此方法将状态混合。

如前所述,在开始吸收和挤压任何比特之前,需要初始化状态。由于我们使用MD5,我们希望我们的状态为16个字节。幸运的是,MD5确保无论我们提供的价值,州最终将成为16个字节。所以我们可以选择任何值,包括空字符串。让我们选择这个选项。

让我们看看一切是否正常工作。创建海绵实例后,我们应该与空字符串的MD5打招呼,D41D8CD98F00B204E9800998ECF8427E。

使用理论部分的逻辑,我们可以轻松地编写用于吸收单个字节的代码。我们将用输入XOR第一个字节替换状态的第一个字节,然后转换状态。

我们可以在我们吸收不同数据后快速测试这导致不同状态。让我们试图吸收[1,2]和[2,1]并观察各州的差异。

将其概括为自动尺寸的缓冲是微不足道的。只是迭代缓冲区并一对一吸收字节。这是一个有用的抽象,因为在实际代码中,我们通常会与缓冲区而不是单个字节一起工作。

这是一个快速的理智检查:我们的州应在吸收字节后与空状态不同。让我们在继续之前快速验证这一点。

由于我们不需要做任何输入混合,因此我们的挤压逻辑比我们的吸收逻辑更简单。在理论部分之后,我们将输出第一个字节并再次转换状态以产生一个字节。

从提取单个字节到exctracting缓冲区并不太难。我们可以使用列表理解以简明的方式写入这一点。

它似乎是一个非常少量的代码,但这就是我们所需要的。在以后添加一些便利功能可能很有用,但对于99%的用例,这些方法就足够了。现在我们可以开始使用我们的海绵功能。

在开始,我们提到海绵函数具有广泛的加密用例。在本节中,我将以简单的方式实现它们,以提供一些示例,就如何有用的海绵函数。

哈希是用海绵函数实现的最简单的事情。实际上,当测试上面的挤压功能时,我们已经看到了这一点。澄清;我们可以通过吸收所有输入并挤压固定数量的字节来生成哈希。

这似乎符合我们的哈希功能标准;将不同大小的映射输入到固定尺寸输出,以及输出的小变化导致完全不同的哈希值。您可以用任何其他长度替换10,以更改哈希的输出大小。一般来说,较长的哈希不太可能具有碰撞,但占用更多空间。您可以播放并为您的用例选择甜蜜点。

随机数生成也是可以使用海绵功能完成的。基本思想是吸收rng种子,然后按照您的需要挤出与多个随机数的字节。在以下示例中,我使用固定的种子来生成10个无符号16位整数。

在[19]中:导入struct s = sponge()s。吸收(B"播种RNG")def rng():buf = s。挤压(2)返回结构。打开包装(' h',buf)[0] [rng()在范围内(10)]

如果我们使用相同的种子,我们将始终获得相同的输出。这可能会对生成“随机”数字的目标来说,但通常需要能够复制随机结果。如果这不是您需要的,您可以从实际随机源或定期更改的内容中种子。这取决于您对随机数所期望的质量。以下是如何从/ dev / urandom读取随机种子的演示。

主意!您也可以在生成它们的同时吸收值,这允许您使用外部来源定期重新定位您的RNG。

我们可以使用海绵函数来生成可以使用秘密密钥生成和验证签名的机制。这是一种非常常见的技术,尤其是在移动和Web应用程序中,它用于将会话存储在客户端上而不让他们篡改它。如果您想了解有关此用例的更多信息,请查看JSON Web令牌。

为了产生签名;我们将吸收数据和秘密密钥。在此之后,我们可以挤压任何可以用作签名的位数。

在[21]中:DEF标志(数据,键):s = sponge()s。吸收(数据)s。吸收(键)返回s。挤压(5)数据= B"你好世界!"键= B" Password123"签名=符号(数据,键)签名。十六进制()

可以通过生成自己生成签名并进行比较您生成的签名来进行签名来完成签名的验证。如果它们匹配,则数据和签名尚未被篡改。

正如预期的那样,可以成功验证签名。让我们尝试修改数据的数据,并在周围切换两个字符。

同样,我们可以使用签名的正确数据和篡改。验证失败,显示签名和数据都受到保护免受损坏和篡改。

流密码允许我们使用单个密钥加密和解密字节流。这可用于仅确保您,或者您委托密钥的任何人都可以解密数据。

在[25]中:def stream_cipher(数据,键):s = sponge()s。吸收(键)输出= I在范围内的(数据)(LEN(数据)):key = s。 squeeze_byte()输出[i] ^ =键返回输出数据= B"您好,世界!"加密= Stream_Cipher(数据,B" password123")加密。十六进制()

解码流密码非常简单,实际上它根本没有代码。只需使用正确的键加密已有加密的值将结束解密您的数据。让我们尝试用正确且不正确的密码解码我们的数据。

主意!您可以将消息认证码和流密码组合顺序,使得加密的数据块和攻击篡改。这被称为经过验证的加密,通常以实际协议完成。尝试实现AE和AEAD变体。

警告!建议还包含一个带有钥匙的IV / nonce,以确保相同的明文加密到不同的密文。

你可能已经注意到这几天有很多服务在尝试验证时要求您进行一次性令牌。这些令牌通常显示为6位数,并在〜30秒内过期。使用海绵函数,我们可以轻松实现自己的版本。这是这些一次性令牌的工作原理。

客户端吸收当前时间和密钥以生成令牌,并将其发送到服务器。

服务器独立地使用相同的键和遵循相同规则生成令牌。

[27]:导入时间键= B"秘密密钥123" def get_otp(key,su段= 10):t =时间。时间()值= int(t /si段)time_left =句点 - (t%时段)s = sponge()s。吸收(键)s。吸收(str(值)。编码(' ascii'))OTP = [s。挤压(1)。 _在范围(3)] OTP =&#39中的十六进制(); ' 。加入(OTP)返回OTP,int(time_left)otp,time_left = get_otp(key)f" OTP是' {OTP}'。" F"适用于{time_left}更多秒。"

如果代码仍然有效,这意味着time_left仍然不为零,则OTP将被视为有效。

主意!建议也接受当前时间之前或之后的代码,以便考虑时钟偏斜。毕竟,当前时间是输入确定代码是什么,因此如果时钟不匹配,则无法进行身份验证。

使用加密散列的流密码有跑进循环的风险。这是在调用州上的变换功能时,最终将返回前一个。为了缓解此操作,我们可以使用块密码。

块密码的差异是;而不是从它挤压海绵到整个流,而是将反击与钥匙和随机块一起吸收到海绵中,以便产生固定的字节块。这是我们获得名称“块密码”的地方。

在[30]中:blocksize = 10 def get_block(key,counter):s = sponge()s。吸收(键)s。吸收(柜台)。编码(" ascii"))返回bytearray(s。挤压(blocksize))def block_encrypt(数据,键):size = len(数据)结果= b" "计数器= 0虽然数据:#echech关闭off offata data_block = data [:blocksize] data = data [blocksize:]#生成块密码块= i,emumerate中的byte(ket_block(key,count)(data_block ):块[i] ^ =字节结果+ =块计数器+ = 1返回结果[:size] data = b"您好,世界!不要忘记保持水分。"加密= block_encrypt(数据,B"测试")加密。十六进制()

正如我们对流密码所做的那样,让我们​​尝试用正确且不正确的键解密我们的数据。

如果你这么做,请感谢您阅读我的文章。我很感激任何电子邮件或评论。您现在有一个了解如何从头开始实施一些常用的加密技术,请让我知道你最终有什么项目用海绵函数。