ECDSA learning

本文介绍了ECDSA加密算法的工作原理,包括私钥、公钥的生成,加密和解密过程。重点讲解了数字签名的创建与验证,并通过一个具体的ECDSA解题实例展示了如何利用签名的r、s和hash值恢复私钥。文章最后提供了实现ECDSA加密解密的Python代码片段,帮助读者深入理解该算法。
摘要由CSDN通过智能技术生成

ECDSA learning

前言

pwnhub六月内部赛出了一道ecdsa,由于当时还没学到签名的内容,再加上还要复习宪法学,下了个附件看了看题就扔一边了,现在有时间学习一下。

加密

首先,我们给出ECDSA处理过程的简要概述。本质上,包括四个基本要素:

1、参与数字签名的所有通信方都使用相同的全局域参数,用定义椭圆曲线以及曲线上的基点。
2、签名者首先需要生成一对公钥、私钥。对于私钥,签名者选择一个随机数或者伪随机数作为签名者的私钥。使用随机数和基点,签名者计算出椭圆曲线上的另一解点,作为签名者的公钥
3、对于待签名的消息计算其hash值。使用私钥、全局域参数、hash值来产生签名、签名包括两个整数,r和s
4、如果要对签名进行验证,验证者使用签名者的公钥、全局域参数、整数s作为输入,并将计算得到的输出值v与收到的r进行比较。如果v=r,则签名通过

私钥

随机选取一个在(1,n−1)区间上的整数dA作为私钥

其中n是选取椭圆曲线上的秩,也就是椭圆曲线加密方程的模数

公钥

Q=dA*G,其中Q是公钥,是由私钥生成的

G是椭圆曲线上的基点(有问题的话可以去看我之前那篇ecc加密原理的文章)

加密过程

若对消息m进行签名,所采用过的椭圆曲线参数为D=(p,a,b,G,n,h),对应的密钥对为(k,Q),其中Q为公钥,k为私钥。

步骤如下:

1、生成一个临时密钥k

2、计算P=k*G,其中P是椭圆曲线上的一个点

3、取P点的横坐标x,使r≡x(mod n)

4、使用sha1函数计算message的hash值,使用e表示,e=H(m),注意这个哈希值需要转换为数值型

5、s≡k-1 * (e+ dA * r)(mod n)

输出值(r,s)即为数字签名

代码如下:

import ecdsa
gen = ecdsa.NIST256p.generator
order = gen.order()
# 生成私钥d_A
d_A = random.randrange(1,order-1)
# 生成公私钥对象
public_key = ecdsa.ecdsa.Public_key(gen,gen * d_A)
private_key = ecdsa.ecdsa.Private_key(public_key,d_A)
message = "Mu.Chen_want_girlfriends"
m = int(hashlib.sha1(message.encode("utf8")).hexdigest(),16)
# 临时密钥
k = random.randrange(1,order-1)
# 签名
signature = private_key.sign(m,k)
r = signature.r 
s = signature.s 

解密过程

因为是dsa,所以解密又叫验证

步骤如下:

1、检查r和s是否为1到n-1之间的整数

2、使用sha,计算160位的hash值e

3、令w=inverse(s,n)

4、计算u1=ew u2=rw

5、计算解点X=(x1,y1)= u1 * G + u2 * Q

6、进行判断当v = = r vrvr时,校验成功

题目

回到这道ecdsa里,我们来具体看一下是如何解码校验

from ecdsa.ecdsa import *
import hashlib
import gmpy2
from secret import secrpt


def inverse_mod(a, m):
    if a == 0:
        return 0
    return gmpy2.powmod(a, -1, m)


def bit_length(x):
    return x.bit_length()


class RSZeroError(RuntimeError):
    pass


class InvalidPointError(RuntimeError):
    pass


class Signature(object):
    """ECDSA signature."""

    def __init__(self, r, s):
        self.r = r
        self.s = s


class Public_key(object):
    """Public key for ECDSA."""

    def __init__(self, generator, point, verify=True):
        """Low level ECDSA public key object.

        :param generator: the Point that generates the group (the base point)
        :param point: the Point that defines the public key
        :param bool verify: if True check if point is valid point on curve

        :raises InvalidPointError: if the point parameters are invalid or
            point does not lay on the curve
        """

        self.curve = generator.curve()
        self.generator = generator
        self.point = point
        n = generator.order()
        p = self.curve.p()
        if not (0 <= point.x() < p) or not (0 <= point.y() < p):
            raise InvalidPointError(
                "The public point has x or y out of range."
            )
        if verify and not self.curve.contains_point(point.x(), point.y()):
            raise InvalidPointError("Point does not lay on the curve")
        if not n:
            raise InvalidPointError("Generator point must have order.")
        # for curve parameters with base point with cofactor 1, all points
        # that are on the curve are scalar multiples of the base point, so
        # verifying that is not necessary. See Section 3.2.2.1 of SEC 1 v2
        if (
            verify
            and self.curve.cofactor() != 1
            and not n * point == ellipticcurve.INFINITY
        ):
            raise InvalidPointError("Generator point order is bad.")


class Private_key(object):
    """Private key for ECDSA."""

    def __init__(self, public_key, secret_multiplier):
        """public_key is of class Public_key;
        secret_multiplier is a large integer.
        """

        self.public_key = public_key
        self.secret_multiplier = secret_multiplier

    def sign(self, hash, random_k):
        """Return a signature for the provided hash, using the provided
        random nonce.  It is absolutely vital that random_k be an unpredictable
        number in the range [1, self.public_key.point.order()-1].  If
        an attacker can guess random_k, he can compute our private key from a
        single signature.  Also, if an attacker knows a few high-order
        bits (or a few low-order bits) of random_k, he can compute our private
        key from many signatures.  The generation of nonces with adequate
        cryptographic strength is very difficult and far beyond the scope
        of this comment.

        May raise RuntimeError, in which case retrying with a new
        random value k is in order.
        """

        G = self.public_key.generator
        n = G.order()
        k = random_k % n
        # Fix the bit-length of the random nonce,
        # so that it doesn't leak via timing.
        # This does not change that ks = k mod n
        ks = k + n
        kt = ks + n
        if bit_length(ks) == bit_length(n):
            p1 = kt * G
        else:
            p1 = ks * G
        r = p1.x() % n
        if r == 0:
            raise RSZeroError("amazingly unlucky random number r")
        s = (
            inverse_mod(k, n)
            * (hash + (self.secret_multiplier * r) % n)
        ) % n
        if s == 0:
            raise RSZeroError("amazingly unlucky random number s")
        return Signature(r, s)


message = b'get the flag'
hash_message = int(hashlib.sha1(message).hexdigest(), 16)
public_key = Public_key(generator_192, generator_192 * secret)
private_key = Private_key(public_key, secret)
sig = private_key.sign(hash_message, secret)
flag = 'flag{' + hashlib.md5(str(secret).encode()).hexdigest() + '}'
print(sig.r, sig.s)
# 827738947342412163466256986352463260575568151152429823167 827738947342412163577310637036310636847677110838127448036

代码很长,但上面的都是关于ecdsa加密的实现代码,真正有用的只有下面的部分

message = b'get the flag'
hash_message = int(hashlib.sha1(message).hexdigest(), 16)
public_key = Public_key(generator_192, generator_192 * secret)
private_key = Private_key(public_key, secret)
sig = private_key.sign(hash_message, secret)
flag = 'flag{' + hashlib.md5(str(secret).encode()).hexdigest() + '}'
print(sig.r, sig.s)
# 827738947342412163466256986352463260575568151152429823167 827738947342412163577310637036310636847677110838127448036

已知message,s,r,公钥曲线,还知道公钥私钥所选用的是相同的临时密钥secret,并且此secret的md5值就是flag

那么思路就很明确了,就是利用hash_message,s,r在有限域n的环境下得到k(dA)。

n作为椭圆曲线的秩,直接在ecdsa库里用order声明一下就行。

在这里插入图片描述

用最后一条等式来写脚本就ok了

from ecdsa.ecdsa import *
import hashlib
import gmpy2
message = b'get the flag'
n=generator_192.order()
r=827738947342412163466256986352463260575568151152429823167
s=827738947342412163577310637036310636847677110838127448036
e = int(hashlib.sha1(message).hexdigest(), 16)
tmp=gmpy2.invert(s-r,n)
k=(e*tmp)%n
flag = 'flag{' + hashlib.md5(str(k).encode()).hexdigest() + '}'
print(flag)

官方wp的思路我没看懂,主要还是数学基础不牢,好多数学符号都还不认识,呜呜呜

总结

写完快5点了,准备去吃早饭了

通过这个题目,主要还是学到了dsa数字签名方面的内容,通过网上找到的加密脚本,更深入了解了代码构造,对题目中自己写解题脚本帮助还是巨大的。

 

  • 16
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值