随机数k复用
题目
from Crypto.Util.number import *
import json
import hashlib
KEY_LENGTH = 2048
FLAG = "grey{fakeflagfornow}"
class Uwu:
def __init__(self, keylen):
self.p = getPrime(keylen)
self.g = getRandomRange(1, self.p)
self.x = getRandomRange(2, self.p) # x is private key
self.y = pow(self.g, self.x, self.p) # y is public key
self.k = getRandomRange(1, self.p)
while GCD(self.k, self.p - 1) != 1:
self.k = getRandomRange(1, self.p)
print(f"{self.p :} {self.g :} {self.y :}")
print(f"k: {self.k}")
def hash_m(self, m):
sha = hashlib.sha256()
sha.update(long_to_bytes(m))
return bytes_to_long(sha.digest())
def sign(self, m):
assert m > 0
assert m < self.p
h = self.hash_m(m)
r = pow(self.g, self.k, self.p)
s = ((h - self.x * r) * pow(self.k, -1, self.p - 1)) % (self.p - 1)
return (r, s)
def verify(self, m, signature):
r, s = signature
assert r >= 1
assert r < self.p
h = self.hash_m(m)
lhs = pow(self.g, h, self.p)
rhs = (pow(self.y, r, self.p) * pow(r, s, self.p)) % self.p
return lhs == rhs
def main():
print("Welcome to my super uwu secure digital signature scheme!")
uwu = Uwu(KEY_LENGTH)
sign_count = 0
while True:
print("1. Show me some of your cutesy patootie signatures!")
print("2. Get some of my uwu signatures (max 2)")
choice = int(input("> "))
if choice == 1:
data = json.loads(input("Send me a message and a signature: "))
m, r, s = data["m"], data["r"], data["s"]
if m == bytes_to_long(b"gib flag pls uwu"):
if uwu.verify(m, (r, s)):
print("Very cutesy, very mindful, very demure!")
print(FLAG)
exit()
else:
print("Very cutesy, but not very mindful")
exit()
else:
print("Not very cutesy")
exit()
elif choice == 2:
if sign_count >= 2:
print("Y-Y-You'd steal from poor me? U_U")
exit()
data = json.loads(input("Send me a message: "))
m = data["m"]
if type(m) is not int or m == bytes_to_long(b"gib flag pls uwu"):
print("Y-Y-You'd trick poor me? U_U")
exit()
r, s = uwu.sign(m)
print(f"Here's your uwu signature! {s :}")
sign_count += 1
else:
print("Not very smart of you OmO")
exit()
if __name__ == "__main__":
main()
解题思路
这段代码实现了一个基于ElGamal数字签名的系统,存在一个关键漏洞:每次签名使用相同的随机数k,这是ElGamal签名算法的致命错误
我们连接服务器得到p,g,y
的值,一般随后我们提交两个明文(格式要求为{"m":114514}
)签名得到s
的值,因为随机数k
是一样的,所以可以反推k
(这里求出的k
和题目给的不一样主要还是因为使用扩展欧几里得算法求解关于k
的同余方程k(s1-s2) ≡ m1-m2 mod (p-1)
时,当gcd(s1-s2,p-1)=d>1
且(m1-m2)%d==0
,该同余方程会有d
个模p-1
不同余的解),最后即可求私钥x
from Crypto.Util.number import *
import hashlib
import json
from math import gcd
def hash_m(m):
sha = hashlib.sha256()
sha.update(long_to_bytes(m))
return bytes_to_long(sha.digest())
def extended_gcd(a, b):
"""
非递归扩展欧几里得算法:返回 gcd(a, b), x, y 使得 ax + by = gcd(a, b)
"""
x0, x1 = 1, 0
y0, y1 = 0, 1
while b != 0:
q, a, b = a // b, b, a % b
x0, x1 = x1, x0 - q * x1
y0, y1 = y1, y0 - q * y1
return a, x0, y0
def solve_congruence(a, b, n):
"""
Solve a * x ≡ b mod n using extended Euclidean algorithm.
Returns a list of all solutions modulo n.
"""
g, x, _ = extended_gcd(a, n)
if b % g != 0:
raise ValueError("No solution exists")
x0 = (x * (b // g)) % n
solutions = [(x0 + i * (n // g)) % n for i in range(g)]
return solutions
def recover_k_and_x(p, g, y, m1, s1, m2, s2):
h1 = hash_m(m1) % (p - 1)
h2 = hash_m(m2) % (p - 1)
delta_s = (s1 - s2) % (p - 1)
delta_h = (h1 - h2) % (p - 1)
# solve delta_s * k ≡ delta_h mod (p - 1)
try:
possible_ks = solve_congruence(delta_s, delta_h, p - 1)
except ValueError as e:
print("[-] 解不出来:", e)
return None
for k in possible_ks:
r = pow(g, k, p)
r_inv = inverse(r, p - 1)
x = ((h1 - s1 * k) * r_inv) % (p - 1)
# 验证第二组签名
lhs = pow(g, hash_m(m2), p)
rhs = (pow(y, r, p) * pow(r, s2, p)) % p
if lhs == rhs:
return k, x, r
return None
p = 28503489812773709296438275497916960466059552932439132188303685622180086495096706728667991583097582688361385305926204896520392124247226449034910838420713455934482267158434772926899422702712614586790299943025980753709711409780744947301307269939417283064804832486428649339529581148810156571572563207388559263952860507704735190923168223515951235442785055731081971784930384639490706176813184031262692624713250361392192167579324010328916696577469719558522429392964798414784158930727194224115321107911706560265944808666511247513437030415950455905586120261185711488087530897031309974278119663845438376403417575148852959601959
g = 16238347619563885835580433152967429572605948438094962086745567771081968010053267967426302727826405561694643010991874849404108116026124560092830492520202298763218392697752506491562747830210063881198463959527589211138857155302665474672595787842585590413511795509027338901884805576240266531214551195192897645999501468412326703053316001414015876269870478757869371983245715069415743263548029860054100648904292369261864776697726454339437656133352537483384554833154599996308757933214832738667493860696416304333337945438706435478910702601128819420492086753045913300843645824662351137770607957869896734451616168071655563605725
y = 11685132043835666411775470724026799881096312459316039935290141767640553983093749430878594159320454451684458272799522972419746761092949559010072321952231861653587697707254048501704410326867184798447618069634079425026667270237910649097514022547181314682063609945167930803952371494777051536079071236194874454170737531281871669583175585359140256255211278077648774884432553963306433541798889346696154417150363286225453526228422316609218327798988827529053302858195559255559308120244607034512312495089728182461793188726043993229421753538694164898470934224306175133115195874968399267944849810029691791534963049464699145061977
m1 = 114514
s1 = 4647171060100052424910713592526127524949438440379187235011071061295013969275223846062646744081757556406416202708749279673860619918206103474751323484124025135373980788736175830110063767235070842357682283349274108738467260204466114525077231522907199413943029071545265832746996945373636291924475676326423940345475408969506901115414875424947944007383643224071842122394634778654564988497303029029637467455748421960259353367997855846064301793419218452101810781250326039714921984083567544614133163848740331596245786204812090611416181228738949568071463128646210209425053158566129348507136920067117201717517272838380577106734
m2 = 1919810
s2 = 22055449648452619177306343232396675180338384507907411113685089156917952164825781173809159826202575188911255991244473170719065617149515001561210077006000319123918955363209680357175624407720596006526723870375550729191756699642424185409807121017145579858814698705948023170549921097183996952138252387303487928404827833678949245682333772056336165019552276346298531983576225878137819355060415336746263911335934327258165012138374923593367572825292761650052747307326580820862071261970119144274200198234121373483615403657071147106373179344274210793004555159373670520269595389775542170407455189877640135096317547632793110339032
result = recover_k_and_x(p, g, y, m1, s1, m2, s2)
if result:
k, x, r = result
print(f"[+] 成功恢复:k = {k}")
print(f"[+] 恢复的私钥 x = {x}")
print(f"[+] 验证用 r = {r}")
else:
print("[-] 无法恢复 k 和 x")
'''
[+] 成功恢复:k = 14135499818268506568572354181952124790561442166899416714280960795678181499130341792204420043434635724903125295508918368556384902202643087792650773282799082934012548683706157716789026481794357578810816753515966822634494195428254648262567220302608177855265860845172056387577009941274038983037398337835799159613993399982558504415236492697385087503459893019019710674018942071231204197970963574106794460469086980204884724411552977456939372972149771980750361252809270945552804514285485388833041434547304804571465416852799808682970264977159666761072697049225243243098072743573741457282007362459320049878492265376517835326722
[+] 恢复的私钥 x = 6508500991026256249164473697707477169445100035267226133298818834673789888743991270344370292199335234655078780740338136461925240554701706599542403520969988680278342770891734756327554420627083731421612176778347008903678316018269396337932145645995590915103700640043945429224908208249494491946143301534494795094853165081546779456475925204573085552221785617533114259302759013615795511928358147663908403931620534751459151123218034389032631460031783944543665290502729656904435856233501681662927090060429720625740038792180793137391619287461954731916448920140031110676823796583607443195162209852717849240048605135686261455874
[+] 验证用 r = 878982900469029789059057111782649363091511558370140125190373397532925224657698015517997001624641006860735772522899540163368549877354376391283912806145777594316130764969667890716535345172749559265012498781021103264542249601468722619928312208377057379394438330044421510529950173576710332495713447282374504231609160667076654995953542344388142013705640846787791566022125631396828333559676360874422317934885254075629342902118582598522220305688156850384327875143020634752065280654493550086025083099001410447026359030158748689595643797662643404602110048523359178300696986609388296467029755071721102121847758001415783284113
'''
不过由于这个题目直接给了k
,所以直接用公式求解x
即可,如果r_inv = inverse(r, p - 1)
不互素就求不出来,可以重新请求服务器换个数据,也可以用扩展欧几里得算法求解(上面的代码就是使用扩展欧几里得的方法求的r,k,x
)
r = pow(g, k, p)
r_inv = inverse(r, p - 1)
x = ((h1 - s1 * k) * r_inv) % (p - 1)
在这里我额外加了一个验证用本地解出的x
签名1919810
这个消息返回的s
值是否和服务器返回一致以此来判断求解的正确性
提交回服务器的格式为<font style="color:rgba(0, 0, 0, 0.85);">{"m":gib flag pls uwu, "r":r, "s":s_target}</font>
解答
from Crypto.Util.number import *
import hashlib
import json
def hash_m(m):
sha = hashlib.sha256()
sha.update(long_to_bytes(m))
return bytes_to_long(sha.digest())
def recover_private_key(p, g, y, k, m1, s1, m2, s2):
r = pow(g, k, p)
h1 = hash_m(m1)
h2 = hash_m(m2)
r_inv = inverse(r, p - 1)
x = ((h1 - s1 * k) * r_inv) % (p - 1)
# 验证第二组签名
lhs = pow(g, h2, p)
rhs = (pow(y, r, p) * pow(r, s2, p)) % p
valid = (lhs == rhs)
return x, valid
p = 28503489812773709296438275497916960466059552932439132188303685622180086495096706728667991583097582688361385305926204896520392124247226449034910838420713455934482267158434772926899422702712614586790299943025980753709711409780744947301307269939417283064804832486428649339529581148810156571572563207388559263952860507704735190923168223515951235442785055731081971784930384639490706176813184031262692624713250361392192167579324010328916696577469719558522429392964798414784158930727194224115321107911706560265944808666511247513437030415950455905586120261185711488087530897031309974278119663845438376403417575148852959601959
g = 16238347619563885835580433152967429572605948438094962086745567771081968010053267967426302727826405561694643010991874849404108116026124560092830492520202298763218392697752506491562747830210063881198463959527589211138857155302665474672595787842585590413511795509027338901884805576240266531214551195192897645999501468412326703053316001414015876269870478757869371983245715069415743263548029860054100648904292369261864776697726454339437656133352537483384554833154599996308757933214832738667493860696416304333337945438706435478910702601128819420492086753045913300843645824662351137770607957869896734451616168071655563605725
y = 11685132043835666411775470724026799881096312459316039935290141767640553983093749430878594159320454451684458272799522972419746761092949559010072321952231861653587697707254048501704410326867184798447618069634079425026667270237910649097514022547181314682063609945167930803952371494777051536079071236194874454170737531281871669583175585359140256255211278077648774884432553963306433541798889346696154417150363286225453526228422316609218327798988827529053302858195559255559308120244607034512312495089728182461793188726043993229421753538694164898470934224306175133115195874968399267944849810029691791534963049464699145061977
m1 = 114514
s1 = 4647171060100052424910713592526127524949438440379187235011071061295013969275223846062646744081757556406416202708749279673860619918206103474751323484124025135373980788736175830110063767235070842357682283349274108738467260204466114525077231522907199413943029071545265832746996945373636291924475676326423940345475408969506901115414875424947944007383643224071842122394634778654564988497303029029637467455748421960259353367997855846064301793419218452101810781250326039714921984083567544614133163848740331596245786204812090611416181228738949568071463128646210209425053158566129348507136920067117201717517272838380577106734
k = 28387244724655361216791491930910605023591218633118982808432803606768224746678695156538415834983427069083817948472020816816580964326256312310106192493155810901253682262923544180238737833150664872205966725028957199489349900318627121913220855272316819387668277088386381057341800515679117268823679941530078791590423653834926099876820604455360705224852420884560696566484134390976557286377555589738140772825712160900980808201214982621397721260884631760011575949291670152944883979649082500890701988503158084704437821186055432439688780185134894713865757179818098987141838192089396444421067194382039238080201052950944315127701
m2 = 1919810
s2 = 22055449648452619177306343232396675180338384507907411113685089156917952164825781173809159826202575188911255991244473170719065617149515001561210077006000319123918955363209680357175624407720596006526723870375550729191756699642424185409807121017145579858814698705948023170549921097183996952138252387303487928404827833678949245682333772056336165019552276346298531983576225878137819355060415336746263911335934327258165012138374923593367572825292761650052747307326580820862071261970119144274200198234121373483615403657071147106373179344274210793004555159373670520269595389775542170407455189877640135096317547632793110339032
x, valid = recover_private_key(p, g, y, k, m1, s1, m2, s2)
print(f"恢复的私钥 x = {x}")
print(f"用第二组签名验证私钥是否正确: {'通过' if valid else '失败'}")
'''
恢复的私钥 x = 6508500991026256249164473697707477169445100035267226133298818834673789888743991270344370292199335234655078780740338136461925240554701706599542403520969988680278342770891734756327554420627083731421612176778347008903678316018269396337932145645995590915103700640043945429224908208249494491946143301534494795094853165081546779456475925204573085552221785617533114259302759013615795511928358147663908403931620534751459151123218034389032631460031783944543665290502729656904435856233501681662927090060429720625740038792180793137391619287461954731916448920140031110676823796583607443195162209852717849240048605135686261455874
用第二组签名验证私钥是否正确: 通过
'''
# 对消息签名
message_str = "gib flag pls uwu"
m_target = bytes_to_long(message_str.encode())
# 计算哈希
h = hash_m(m_target)
# 计算 r
r = pow(g, k, p)
# 计算 k 的逆元 mod p-1
k_inv = inverse(k, p - 1)
# 计算签名 s
s_target = ((h - x * r) * k_inv) % (p - 1)
# 生成签名json
signature = {
"m": m_target,
"r": r,
"s": s_target
}
print(json.dumps(signature, separators=(',', ':')))
#{"m":137457664979133345092444721504268416885,"r":878982900469029789059057111782649363091511558370140125190373397532925224657698015517997001624641006860735772522899540163368549877354376391283912806145777594316130764969667890716535345172749559265012498781021103264542249601468722619928312208377057379394438330044421510529950173576710332495713447282374504231609160667076654995953542344388142013705640846787791566022125631396828333559676360874422317934885254075629342902118582598522220305688156850384327875143020634752065280654493550086025083099001410447026359030158748689595643797662643404602110048523359178300696986609388296467029755071721102121847758001415783284113,"s":2803321573861023772997091619147539527123780077136979949465056052618744835498754487324367196077381493150301320057632689826653014239432259418168747847731152979117160540113417233374172668251297406289681947752339523047331550859864976065248046473613785466146480058554937331272093586545464756164188419200847799644209721708835303281603822779138438865454055923950675339022094519837560017221537412016192947283991129539297712311705398270856827578828056232223354297777120528020635674811437369872026450564390085232345452607325406261640535764941756831283108659437165852124176416969191106072253690392958806356871818635630243663749}
#grey{h_h_H_h0wd_y0u_Do_tH4T_OMO}