在最近的很多比赛都遇到了这个Hidden Number Problem(HNP),所以抽个时间来仔细学习一下,然后马上要HGAME2023了,正好准备一下题目给新生写。
Introduce
HNP问题第一次被提出是在这篇论文中 “Hardness of computing the most significant bits of secret keys in Diffie-Hellman and related schemes” by Dan Boneh and Ramarathnam Venkatesan。HNP问题本来是用来研究Diffie-Hellman共享密钥中的最高有效位是否与整个秘密一样难以计算?并且D. Boneh和R. Venkatesan还展示了一种有效的算法,用于在有足够大的位泄漏的情况下恢复密钥。
这个方法用到了格和格基规约的算法,一开始学习格密码时把重点放在了基于格的密钥系统的学习上,但格终究是数学上的东西,数学说白了就是一个工具,那么格自然也是一个工具,只不过我们把格这个有力的工具用在了密码分析上而已。
How is the HNP defined
论文中首先提出了most significant bits(MSB)的定义。首先令\(p\)是一个素数,\(n\)是\(p\)的二进制位数,即\(n=log_2(p)\),用\(x\ mod\ p\)来表示一个定义在有限域的数\(a \in GF(p)\),即\(x \equiv a \pmod{p}\)。定义\(MSB_k(x)\)的值为\(t\)并且 \((t-1)\cdot p/2^k\leq x <t \cdot p/2^k\),或者更简单的定义
\[MSB_k(x)=z,\quad |x-z|< p/2^k \]About the \(MSB_k(x)\)
一看到most signficant bits可能很多人都会想当然的认为是\(x\)的最高\(k\)位。其实不然,根据定义,可以写一个小demo
from random import randint
from sage.all import *
from Crypto.Util.number import getPrime
# Some parameters of the game, chosen for simplicity.
# p - A prime number for our field.
p = getPrime(128)
# n - The number of bits in `p`.
n = ceil(log(p, 2))
# k - The number of significant bits revealed by the oracle.
# Using parameters from Thereom 1.
k = ceil(sqrt(n)) + ceil(log(n, 2))
print(f"The number of significant bits = {k}")
def msb(query):
"""Returns the MSB of query based on the global paramters p, k.
"""
while True:
z = randint(1, p-1)
answer = abs(query - z)
if answer < p / 2**(k+1):
break
return z
x = randint(1, p-1)
print(f"x = {x}")
print(f"MSB's output = {msb(x)}")
print(f"most k bit of x = {x >> (n - k)}")
'''
The number of significant bits = 19
x = 54914319491231438995398843041850226262
MSB's output = 54914493779494223381640832877194242316
|MSB(x) - x| = 124182961411716441204715130169529
most k bit of x = 84608
'''
如何去理解这个\(MSB_k(x)\)呢,不难得出以下几点:
-
根据\(MSB_k(x)\)的定义,有\(u=x\)是一定满足\(|x-u|\leq\frac{p}{2^{k+1}}\)的,那么\(u=x\)一定是\(MSB\)的一个输出,为什么是一个呢,因为这个不等式肯定是多解的。
-
当\(k\)增大时,不等式的右边\(\frac{p}{2^{k+1}}\)和会快速的缩减,意味着不等式左边的\(z\)也就是\(MSB\)的输出要接近\(x\)。\(k\)越大,\(MSB_k(x)\)越接近\(x\)。
由于\(k\)越大,\(MSB_k(x)\)越接近\(x\),也就是说,对于越大的\(k\),我们得到的数就泄漏了越多的\(x\)的有效位。
然后定义Hidden Number Problem(HNP):确定\(p\)和\(k\),已知\(MSB_k(\alpha g^x\ mod \ p)\)求\(\alpha\)。
论文中提到如果可以请求到正确的\(MSB_k(\alpha\cdot t\ mod\ p)\),这里\(t=g^x\),那么恢复\(\alpha\)是一件简单的事情。
Main Results
这一节主要讲的是多大的k可以来解决HNP问题。直接上定理。
Theorem 1
令\(\alpha\)是定义在有限域\(GF(p)\)的一个数, \(k=\lceil\sqrt{n}\rceil+\lceil\log n\rceil\),\(\mathcal{O}(t)=\operatorname{MSB}_{k}(\alpha t \bmod p)\)。存在一种算法A可以在多项式时间内解决HNP。
\[\underset{t_{1}, \ldots, t_{d}}{\operatorname{Pr}}\left[A\left(t_{1}, \ldots, t_{d}, \mathcal{O}\left(t_{1}\right), \ldots, \mathcal{O}\left(t_{d}\right)\right)=\alpha\right] \geq \frac{1}{2} \]其中\(d=2\sqrt{n}\),\(t_1,\cdots,t_d\)是均匀的,独立的随机从\(\mathbb{Z}_{p}^{*}\)中选择。
这里均匀的,独立的随机选择应该是为了保证数据的不相关性。
Theorem 2
设\(k=\lceil\sqrt{n}\rceil+\lceil\log{n}\rceil\) ,给出一个有效的算法从\(g^a, g^b\)计算\(MSB_k(g^{ab})\),存在一个算法可以在多项式时间内计算\(g^{ab}\)。也就是已知\(MSB_k(g^{ab})\)求\(g^{ab}\)。
Proof
首先回顾一下格这个东西。格可以这个样子定义
\[L=\left\{y: y=\sum_{i=1}^{d} t_{i} b_{i}, t_{i} \in \mathbb{Z}\right\} \]\(d\)是格\(L\)的阶,\(\{b_i\}={b_1,\cdots,c_d}\)是\(L\)的一组线性无关向量,被称为\(L\)的基,\(\sum^{d}_{i=1}t_ib_i\)是这个基的一个线性组合。利用LLL格基规约算法可以从一个给定的格\(L\)和一个点\(v\)(不必要是格上的点),找到一个最靠近\(v\)的格点。
接下来证明Theorem 1。
\(d=2\lceil\sqrt{n}\rceil\),\(k=k=\lceil\sqrt{n}\rceil+\lceil\log n\rceil\),然后假设我们已经获得了\(d\)个正确的MSB oracle \(a_1, \cdots, a_d\),对应的输入为\(t_1,\cdots,t_d\)。根据MSB的定义,我们有对于所有的a和t
\[|\alpha t_i\ mod\ p-a_i|\leq p/2^k \]构造格\(L\)
\[\left(\begin{array}{cccccc} p & 0 & 0 & \ldots & 0 & 0 \\ 0 & p & 0 & \ldots & 0 & 0 \\ & \vdots & & & & \vdots \\ 0 & 0 & 0 & \ldots & p & 0 \\ t_{1} & t_{2} & t_{3} & \ldots & t_{d} & 1 / p \end{array}\right) \]注意对于最后一行乘\(\alpha\)后模\(p\)可以得到这样一个向量
\[v_{\alpha}=(r_1,\cdots,r_d,\alpha/p) \]说明\(v_{\alpha}\)确实存在与格\(L\)中,其中 \(r_i=\alpha t_i\ mod\ p\),于是
\[|r_i - a_i|<p/2^k\quad i\in[1,d] \]定义向量\(u=(a_1,\cdots,a_d,0)\),那么向量\(u\)和\(v_{\alpha}\)的距离,即\(||u-v_{\alpha}||\)
\[||u-v_{\alpha}||<\sqrt{\sum^{d-1}_{i=1}(a_i-r_i)^2+(\frac{\alpha}{p})^2} \le \sqrt{d+1}p/2^k \]那么我们就可以用LLL和Babai算法来寻找到\(v_{\alpha}\),对\(v_{\alpha}\)的最后一个元素乘\(p\)就求出了\(\alpha\)。
Example & Demo
demo from kel.
from random import randint
from sage.all import *
from Crypto.Util.number import getPrime
# Some parameters of the game, chosen for simplicity.
# p - A prime number for our field.
p = getPrime(128)
# n - The number of bits in `p`.
n = ceil(log(p, 2))
# k - The number of significant bits revealed by the oracle.
# Using parameters from Thereom 1.
k = ceil(sqrt(n)) + ceil(log(n, 2))
print(f"The number of significant bits = {k}")
d = 2*ceil(sqrt(n))
def msb(query):
"""Returns the MSB of query based on the global paramters p, k.
"""
while True:
z = randint(1, p-1)
answer = abs(query - z)
if answer < p / 2**(k+1):
break
return z
def create_oracle(alpha):
"""Returns a randomized MSB oracle using the specified alpha value.
"""
alpha = alpha
def oracle():
random_t = randint(1, p-1)
return random_t, msb((alpha * random_t) % p)
return oracle
def build_basis(oracle_inputs):
"""Returns a basis using the HNP game parameters and inputs to our oracle
"""
basis_vectors = []
for i in range(d):
p_vector = [0] * (d+1)
p_vector[i] = p
basis_vectors.append(p_vector)
basis_vectors.append(list(oracle_inputs) + [QQ(1)/QQ(p)])
return Matrix(QQ, basis_vectors)
def approximate_closest_vector(basis, v):
"""Returns an approximate CVP solution using Babai's nearest plane algorithm.
"""
BL = basis.LLL()
G, _ = BL.gram_schmidt()
_, n = BL.dimensions()
small = vector(ZZ, v)
for i in reversed(range(n)):
c = QQ(small * G[i]) / QQ(G[i] * G[i])
c = c.round()
small -= BL[i] * c
return (v - small).coefficients()
# Hidden alpha scalar
alpha = randint(1, p-1)
# Create a MSB oracle using the secret alpha scalar
oracle = create_oracle(alpha)
# Using terminology from the paper: inputs = `t` values, answers = `a` values
inputs, answers = zip(*[ oracle() for _ in range(d) ])
# Build a basis using our oracle inputs
lattice = build_basis(inputs)
print("Solving CVP using lattice with basis:\n%s\n" % str(lattice))
# The non-lattice vector based on the oracle's answers
u = vector(ZZ, list(answers) + [0])
print("Vector of MSB oracle answers:\n%s\n" % str(u))
# Solve an approximate CVP to find a vector v which is likely to reveal alpha.
v = approximate_closest_vector(lattice, u)
print("Closest lattice vector:\n%s\n" % str(v))
# Confirm the recovered value of alpha matches the expected value of alpha.
recovered_alpha = (v[-1] * p) % p
assert recovered_alpha == alpha
print("Recovered alpha! Alpha is %d" % recovered_alpha)
Refer to
- The Hidden Number Problem
- Hardness of computing the most significant bits of secret keys in Diffie-Hellman and related schemes” by Dan Boneh and Ramarathnam Venkatesan