我有一个64个字符的SHA256哈希。
我希望训练一个模型,该模型可以预测用于生成哈希的纯文本是否以1开头。
不管这是“可能的”,哪种算法是最佳方法?
我最初的想法:
- 生成以1开头的大量散列样本和不以1开头的大量散列样本
- 将哈希的64个字符中的每个字符设置为某种无监督逻辑回归模型的参数。
- 通过告诉模型正确/错误的时间来训练模型。
- 希望能够创建一个模型,该模型能够以足够高的精度(以及适当的kappa值)预测明文是否以1开头。
我有一个64个字符的SHA256哈希。
我希望训练一个模型,该模型可以预测用于生成哈希的纯文本是否以1开头。
不管这是“可能的”,哪种算法是最佳方法?
我最初的想法:
Answers:
这实际上不是一个统计答案,但是:
不,您无法从哈希值中确定纯文本的第一个字符,因为对于给定的哈希值,没有“纯文本”之类的东西。
SHA-256是一种哈希算法。无论您使用什么明文,都可以得到一个32字节的签名,通常以64个字符的十六进制字符串表示。明文比64个字符的十六进制字符串要多得多-可以从任意数量的不同明文中生成相同的哈希。没有理由相信在产生给定哈希值的所有纯文本中,第一个字符为“ 1”或不为“ 1”是统一的。
SHA256被设计为尽可能随机,因此您不太可能将以1-前缀的明文和非以-前缀的明文分开。散列字符串应该完全没有任何功能可以将这些信息泄露出去。
不管这是“可能的”,哪种算法是最佳方法?
对不起,但这是一个荒谬的问题。如果无法解决问题,那么您将无法找到解决问题的最佳方法。
在这种情况下,这绝对是不可能的,因为哈希是一种单向函数:多个输入(实际上是无限个)可以产生相同的输出。如果输入的第一位本身会以某种方式影响特定哈希值的可能性,则这意味着哈希算法完全有缺陷。
您当然可以训练神经网络,线性分类器,SVM和其他工具以尝试进行预测。而且,如果您能够从某种哈希算法的输出可靠地预测输入,那么这将证明该算法毫无价值。我想说,对于像SHA256这样广泛使用的算法,这种可能性几乎没有了。但是,这是一种快速排除新的,未经验证和未经测试的哈希算法的合理方法。
sign(x)
从这个意义上讲,它不是单向功能,因为找到原像是微不足道的。
虽然不能用一个例子证明负面。我仍然觉得举个例子是有启发性的。也许有用。它确实显示了人们将如何(试图)解决类似的问题。
在我想使用二进制矢量特征进行二进制预测的情况下 ,随机森林是一个不错的选择。我想这种回答是您问题的第二部分:什么是好的算法。
我们非常希望将SHA256字符串预处理为二进制(布尔)向量,因为每个位在统计上都是独立的,因此每个位都是一个很好的功能。这样我们的输入将成为256个元素的布尔向量。
这是使用Julia DecisionTree.jl库如何完成整个过程的演示。
您可以将以下内容复制粘贴到julia提示符中。
using SHA
using DecisionTree
using Statistics: mean
using Random: randstring
const maxlen=10_000 # longest string (document) to be hashed.
gen_plaintext(x) = gen_plaintext(Val{x}())
gen_plaintext(::Val{true}) = "1" * randstring(rand(0:maxlen-1))
gen_plaintext(::Val{false}) = randstring(rand(1:maxlen))
bitvector(x) = BitVector(digits(x, base=2, pad=8sizeof(x)))
bitvector(x::AbstractVector) = reduce(vcat, bitvector.(x))
function gen_observation(class)
plaintext = gen_plaintext(class)
obs = bitvector(sha256(plaintext))
obs
end
function feature_mat(obs)
convert(Array, reduce(hcat, obs)')
end
########################################
const train_labels = rand(Bool, 100_000)
const train_obs = gen_observation.(train_labels)
const train_feature_mat = feature_mat(train_obs)
const test_labels = rand(Bool, 100_000)
const test_obs = gen_observation.(test_labels)
const test_feature_mat = feature_mat(test_obs)
# Train the model
const model = build_forest(train_labels, train_feature_mat)
@show model
#Training Set accuracy:
@show mean(apply_forest(model, train_feature_mat) .== train_labels)
#Test Set accuracy:
@show mean(apply_forest(model, test_feature_mat) .== test_labels)
当我这样做时,训练100,000个随机ASCII字符串,其长度最大为10,000。这是我看到的结果:
julia> const model = build_forest(train_labels, train_feature_mat)
Ensemble of Decision Trees
Trees: 10
Avg Leaves: 16124.7
Avg Depth: 17.9
julia> mean(apply_forest(model, train_feature_mat) .== train_labels)
0.95162
julia> mean(apply_forest(model, test_feature_mat) .== test_labels)
0.5016
因此,基本上什么都没有。我们从训练组的95%上升到测试组的50%以上。有人可以运用适当的假设检验,看看我们是否可以拒绝原
假设,但我敢肯定我们不能。与猜测率相比,这是很小的改进。
这表明它是无法学习的。如果是“随机森林”,则可以从拟合良好到仅达到猜测率。随机森林非常有能力学习困难的输入。如果有什么要学习的,我希望至少有百分之几。
您可以通过更改代码来使用不同的哈希函数。当在构建hash
函数中使用julia时,我得到的结果基本相同(这不是加密安全的hsah,但仍然是一个很好的哈希,因此确实应该发送类似的字符串),这可能会很有趣。对于,我也得到了基本相同的结果 CRC32c
。
哈希函数(通过设计)非常不适合使用它们进行任何机器学习。
ML本质上是建模/估计局部连续函数的方法系列。即,您正在尝试描述一些物理系统,尽管它可能具有某些不连续性,但在某种意义上,它在大多数参数空间中都足够平滑,因此只能使用分散的测试数据样本来预测其他结果输入。为此,AI算法需要以某种方式将数据分解为一个聪明的基础表示形式,为此训练表明,例如,如果您看到这样或那样的形状(看起来与这种或这种卷积的结果相关),那么输出在相应区域中具有这样和这样的结构的好机会(可以再次用卷积或某种形式描述)。
(我知道,许多ML方法根本都不像卷积,但总的思路始终是相同的:您的输入空间是如此之高,无法进行详尽的采样,因此您发现了一个聪明的分解方法,可以进行推断比较稀疏的样本得出的结果。)
但是,密码散列函数背后的想法是,对纯文本的任何更改都应导致完全不同的摘要。因此,无论您如何分解函数,局部估计器都不允许您推断该部分周围的微小波动如何影响结果。除非您当然要实际处理有限集合中的所有信息,否则这不会被称为机器学习:您只会构建一个Rainbow table。
这是一个有趣的问题,因为它引发了有关什么被视为“机器学习”的问题。如果可以解决,肯定会有一种算法最终可以解决此问题。它是这样的:
选择您喜欢的编程语言,然后确定将每个字符串映射到(可能非常大)整数的编码。
选择一个随机数并将其转换为字符串。检查它是否是您所用语言的有效程序。如果不是,请选择另一个号码,然后重试。如果是这样,请启动它,立即暂停它,并将其添加到已暂停程序的列表中。
运行所有暂停的程序一会儿。如果它们中的任何一个都停止了而没有产生适当的解决方案,请从列表中删除它们。如果有足够的解决方案,那么您就完成了!否则,让它们全部运行一段时间后返回2。
毫无疑问,如果您有无限的存储空间和无限的时间,上述算法最终将找到一个好的解决方案。但这可能不是您所说的“机器学习”的意思。
问题在于:如果考虑所有可能的问题,那么平均而言,没有机器学习算法可以做得更好!这就是所谓的免费午餐定理。事实证明,在任何给定的机器学习算法中,您可能会遇到的所有问题中,它可以迅速解决的数量几乎没有。
它可以快速解决这些问题,只是因为它们受算法可以预期的模式支配。例如,许多成功的算法都假定以下条件:
解决方案可以通过一系列复杂的矩阵乘法和非线性失真来描述,并由一组参数控制。
好的解决方案将在参数空间中聚集在一起,因此您要做的就是选择一个搜索邻域,在其中找到最佳解决方案,移动您的搜索邻域,以使最佳解决方案位于中心,然后重复。
显然,这两个假设都不成立。第二点尤其令人怀疑。没有免费的午餐告诉我们,这些假设在大多数情况下都不成立。实际上,它们几乎永远不会保持住!它们确实可以解决某些实际问题,这只是我们的好运。
您选择的问题从一开始就是为了违反假设2而设计的。哈希函数经过专门设计,以便类似的输入提供完全不同的输出。
因此,您的问题-什么是解决该问题的最佳机器学习算法?-可能有一个非常简单的答案:随机搜索。
这几乎是不可能的。但是,人们观察到SHA256中的一些模式,这可能表明它是非随机的。他们的tldr:
“要区分理想的随机排列散列和SHA256,将大量(〜2 ^ 80)个候选1024位块进行散列两次,就像在比特币中所做的那样。确保候选块的位被稀疏设置(比根据比特币协议,期望的平均值为512),丢弃不符合比特币“难度”标准的候选区块(在该区块中,生成的散列以大量的0开头),剩下的有效输入候选集(467369,当完成此分析),观察输入块中特定的一组32位(位于比特币具有随机数的位置,输入比特607-639)。请注意,随机数字段中设置的平均位数向左偏斜,即少于设定的16位的期望值(估计平均值15.428)。”
请参阅lobste.rs上的讨论。一种可能的解释是矿工造成的偏见。
我会回答一个程序。为了减少计算要求,我将使用sha256的变体,我称之为sha16,它只是sha256的前16位。
#!/usr/bin/python3
import hashlib
from itertools import count
def sha16(plaintext):
h = hashlib.sha256()
h.update(plaintext)
return h.hexdigest()[:4]
def has_plaintext_start_with_1(digest):
"""Return True if and only if the given digest can be generated from a
plaintext starting with "1" first bit."""
return True
def plaintext_starting_with_1(digest):
"""Return a plaintext starting with '1' matching the given digest."""
for c in count():
plaintext = (b'\x80' + str(c).encode('ascii'))
d = sha16(plaintext)
if d == digest:
return plaintext
for digest in range(0x10000):
digest = "%04x" % (digest,)
plain = plaintext_starting_with_1(digest)
print("%s hashes to %s" % (plain, digest))
产生输出:
b'\x8094207' hashes to 0000
b'\x8047770' hashes to 0001
b'\x8078597' hashes to 0002
b'\x8025129' hashes to 0003
b'\x8055307' hashes to 0004
b'\x80120019' hashes to 0005
b'\x8062700' hashes to 0006
b'\x8036411' hashes to 0007
b'\x80135953' hashes to 0008
b'\x8044091' hashes to 0009
b'\x808968' hashes to 000a
b'\x8039318' hashes to 000b
[...]
我会将完整的证明留给读者练习,但请注意:对于每个可能的摘要(从0000到ffff),都有一个以“ 1”开头的输入。
还有一个输入不以“ 1”开头。还有一部也是从莎士比亚的完整著作开始的。
尽管我的强力证明可能在计算上变得不可行,但这适用于任何相当好的哈希函数。
您所描述的基本上是预映像攻击。您正在尝试找到一个输入,以便在对其进行哈希处理时,输出具有一些属性,例如“前导1”。*
密码散列的一个明确目标是防止此类前映像攻击。如果您可以进行此类攻击,我们倾向于认为该算法不安全并停止使用它。
因此,尽管这并非不可能,但这意味着您的机器学习算法必须同时胜过世界上很大一部分数学家及其超级计算机。您不太可能会这样做。
但是,如果您这样做了,您将成为打破主要加密哈希算法的人。那个名望值得!
*从技术上讲,“第一次原像攻击”试图找到特定哈希值的匹配项。但是,为了表明哈希算法具有第一个像前攻击的持久性,它们通常表明您找不到与哈希输入有关的任何有意义的信息。
这里的大多数答案都在告诉您为什么您不能执行此操作,但这是直接的答案:
不管这是“可能的”,哪种算法是最佳方法?
假设输入足够大:
这就是输入字符串以“ 1”开头的可能性。您甚至不需要查看输入。如果您能做得更好,则意味着哈希值非常混乱。与尝试训练算法以选择随机数相比,您可以节省大量CPU周期。
您可以训练算法,由于过度拟合,可能会得出不同的答案。除非哈希算法确实有什么问题。与仅选择一个随机值相比,使用该算法出错的可能性更高。
哈希函数是有意设计的,很难建模,因此(正如已经指出的那样),这可能非常困难。尽管如此,哈希函数的任何弱点都会降低其熵,从而使其更可预测。
不管这是“可能的”,哪种算法是最佳方法?
一个有用的示例是“ 物理上不可克隆的函数 ”或PUF-与硬件哈希函数类似。通常,有意使用制造差异为每个PUF提供稍微不同的响应,以使它们的“散列”输出对于给定的输入而言是不同的。设计弱点限制了熵,但是,如果有足够的质询-响应对,通常可以构建PUF的黑匣子模型,以便可以预测新的,以前看不见的质询的响应。
逻辑回归是这些建模攻击最常用的方法,例如Rührmair的论文。
遗传算法(或更笼统地说是进化策略)可能是一种替代方法,因为它们适用于不可微分和/或线性可分离的问题。上面的文章中也对它们进行了讨论。
问题在于“机器学习”不是智能的。它只是试图找到模式。在SHA-256中,没有模式。没什么可找的。机器学习没有比蛮力更好的机会了。
如果您想通过计算机破解SHA-256,唯一的可能性就是创建真实的智能,并且由于许多聪明的人还没有找到创建SHA-256的方法,因此您需要创建比许多聪明的人。到那时,我们还不知道这样的超人类智能是否会破解SHA-256,证明它不会被破解,或者是否会决定它不够聪明(就像人类一样)。第四个可能性当然是,这样的超人类人工智能甚至不会打扰,而是会考虑更重要的问题。