1. ホーム
  2. スクリプト・コラム
  3. パイソン

PythonでECDSAを実装する方法 知っていますか?

2022-01-08 12:59:09

ResultSet.RTYPE_FORWORD_ONLY: This is the default value, only forward scrolling is possible  
ResultSet.TYPE_SCROLL_INSENSITIVE: two-way scrolling, but not updated in time, that is, if the data in the database has been modified, and does not react in the ResultSet.  
TYPE_SCROLL_SENSITIVE: scrolls in both directions and keeps track of database updates in order to change the data in the ResultSet.


NIST for Digital Test Suite NISTの詳細について

GF§ (素数体) 曲線、鍵長 192, 224, 256, 384, 521 ビット

OpenSSL ツール ( ResultSet.CONCUR_READ_ONLY: This is the default value, specifying that the ResultSet cannot be updated. CONCUR_UPDATABLE: Specifies that the ResultSet can be updated. これらの曲線の短縮形として知られているのが prime192v1 , secp224r1 ,

prime256v1
, secp384r1
and
secp521r1
. It includes the 256-bit curve secp256k1 used by Bitcoin. It also supports regular (non-twisted) variants of the Brainpool curve from 160 to 512 bits. The "acronyms" for these curves are: brainpoolP160r1, brainpoolP192r1, brainpoolP224r1, brainpoolP256r1, brainpoolP320r1, brainpoolP384r1, brainpoolP512r1. a few from SEC A few standard minor curves are also included (mainly to speed up library testing), they are: secp112r1, secp112r2, secp128r1, and secp160r1. No other curves are included, but it is not difficult to add support for curves with more prime domains.
#Not quite sep=": ",unit="s",form=".5f",form_inv=".2f",
prnt_form = (
    "{name:>16}{sep:1} {siglen:>6} {keygen:>9{form}}{unit:1} "
    "{keygen_inv:>9{form_inv}} {sign:>9{form}}{unit:1} "
    "{sign_inv:>9{form_inv}} {verify:>9{form}}{unit:1} "
    "{verify_inv:>9{form_inv}} {verify_single:>13{form}}{unit:1} "
    "{verify_single_inv:>14{form_inv}}"
)
print(
    prnt_form.format(
        siglen="siglen",
        keygen="keygen",
        keygen_inv="keygen/s",
        sign="sign",
        sign_inv="sign/s",
        verify="verify",
        verify_inv="verify/s",
        verify_single="no PC verify",
        verify_single_inv="no PC verify/s",
        name="",
        sep="",
        unit="",
        form="",
        form_inv="",
    )
)
for curve in [i.name for i in curves]:
    S1 = "import six; from ecdsa import SigningKey, %s" % curve
    S2 = "sk = SigningKey.generate(%s)" % curve # Generate private key
    S3 = "msg = six.b('msg')" # message
    S4 = "sig = sk.sign(msg)" # sign
    S5 = "vk = sk.get_verifying_key()"# public key derived from private key get_verifying_key() function
    S6 = "vk.precompute()"# don't understand
    S7 = "vk.verify(sig, msg)"# verify the signature with the public key
    # We happen to know that .generate() is also calculating the verification key, which is the most time-consuming part. If the code is changed to lazily compute vk, we would need to change this benchmark to loop over S5 instead of S2.
    keygen = do([S1], S2)
    sign = do([S1, S2, S3], S4)
    verf = do([S1, S2, S3, S4, S5, S6], S7)
    verf_single = do([S1, S2, S3, S4, S5], S7)
    import ecdsa
    c = getattr(ecdsa, curve)# get the attribute value from the name
    sig = ecdsa.SigningKey.generate(c).sign(six.b("msg"))
    # key pairs (keygen), signing data (sign), verifying these signatures (verify), sharing secrets (ecdh) and verifying signatures without a specific key precomputation (no PC verify), and the size of the original signature (usually the smallest way the signature can be encoded) are also provided in the siglen column
    print(
        prnt_form.format(
            name=curve,# all curves
            sep=":",
            siglen=len(sig),
            unit="s",
            keygen=keygen,
            keygen_inv=1.0 / keygen,
            sign=sign,
            sign_inv=1.0 / sign,
            verify=verf,
            verify_inv=1.0 / verf,
            verify_single=verf_single,
            verify_single_inv=1.0 / verf_single,
            form=".5f",# 5 decimal places after the decimal point
            form_inv=".2f",# 2 decimal places after the decimal point
        )
    )


print("")


ED25519 and Cureve5519
ecdh_form = "{name:>16}{sep:1} {ecdh:>9{form}}{unit:1} {ecdh_inv:>9{form_inv}}"
print(
    ecdh_form.format(
        ecdh="ecdh",
        ecdh_inv="ecdh/s",
        name="",
        sep="",
        unit="",
        form="",
        form_inv="",
    )
)
for curve in [i.name for i in curves]:
    if curve == "Ed25519" or curve == "Ed448":
        continue
    S1 = "from ecdsa import SigningKey, ECDH, {0}".format(curve)
    S2 = "our = SigningKey.generate({0})".format(curve)# private key
    S3 = "remote = SigningKey.generate({0}).verifying_key".format(curve)#public key
    S4 = "ecdh = ECDH(private_key=our, public_key=remote)"
    S5 = "ecdh.generate_sharedsecret_bytes()"#generate shared key
    ecdh = do([S1, S2, S3, S4], S5)
    print(
        ecdh_form.format(
            name=curve,
            sep=":",
            unit="s",
            form=".5f",
            form_inv=".2f",
            ecdh=ecdh,
            ecdh_inv=1.0 / ecdh,
        )
    )



from ecdsa import SigningKey sk = SigningKey.generate() # uses NIST192p to generate the private key vk = sk.verifying_key # generates the public key based on the private key signature = sk.sign(b"message")# sign the message with the private key assert vk.verify(signature, b"message")# verify with the public key. assert is an assertion function: if the condition is not met, the exception is triggered directly and the next code is not executed, with the condition in parentheses from ecdsa import SigningKey, NIST384p#384-bit NIST prime domain elliptic curve, where private/public keys are associated with a specific curve, longer curves are more secure, but longer time and longer keys and signatures sk = SigningKey.generate(curve=NIST384p) vk = sk.verifying_key signature = sk.sign(b"message") assert vk.verify(signature, b"message") # Serialize the signing key (private key) into a different format. from ecdsa import SigningKey, NIST384p sk = SigningKey.generate(curve=NIST384p) sk_string = sk.to_string()# shortest call, then recreate the private key. to_string(): converts the number inside the brackets to a string, the actual returned type bytes sk2 = SigningKey.from_string(sk_string, curve=NIST384p)# recreate the private key, the first parameter is the character we want to process, if the point code is invalid or not on the specified curve, from_string() will raise MalformedPointError print(sk_string.hex()) print(sk2.to_string().hex()) from ecdsa import SigningKey, NIST384p sk = SigningKey.generate(curve=NIST384p) sk_pem = sk.to_pem() # sk.to_pem() and sk.to_der() will serialize the signing key to the same format used by OpenSSL sk2 = SigningKey.from_pem(sk_pem)#SigningKey.from_pem()/.from_der() will undo this serialization. These formats include the curve name, so you don't need to pass the curve identifier to the deserializer. If the file is malformed, from_der() and from_pem() will raise UnexpectedDER or MalformedPointError. # sk and sk2 are the same key from ecdsa import SigningKey, VerifyingKey, NIST384p sk = SigningKey.generate(curve=NIST384p) vk = sk.verifying_key vk_string = vk.to_string()# public key can be serialized in the same way vk2 = VerifyingKey.from_string(vk_string, curve=NIST384p) # vk and vk2 are the same key from ecdsa import SigningKey, VerifyingKey, NIST384p sk = SigningKey.generate(curve=NIST384p) vk = sk.verifying_key vk_pem = vk.to_pem() vk2 = VerifyingKey.from_pem(vk_pem) # vk and vk2 are the same key import os from ecdsa import NIST384p, SigningKey from ecdsa.util import randrange_from_seed__trytryagain#generate random numbers def make_key(seed): secexp = randrange_from_seed__trytryagain(seed, NIST384p.order) return SigningKey.from_secret_exponent(secexp, curve=NIST384p) seed = os.urandom(NIST384p.baselen) # or other starting point, return a bit string suitable for encryption sk1a = make_key(seed) sk1b = make_key(seed) # note: sk1a and sk1b are the same key assert sk1a.to_string() == sk1b.to_string() sk2 = make_key(b"2-"+seed) # different key b for bit assert sk1a.to_string() ! = sk2.to_string() from ecdsa import SigningKey, NIST384p sk = SigningKey.generate(curve=NIST384p) vk = sk.verifying_key vk.precompute() signature = sk.sign(b"message") assert vk.verify(signature, b"message") # openssl ecparam -name prime256v1 -genkey -out sk.pem # openssl ec -in sk.pem -pubout -out vk.pem # echo "data for signing" > data # openssl dgst -sha256 -sign sk.pem -out data.sig data # openssl dgst -sha256 -verify vk.pem -signature data.sig data # openssl dgst -sha256 -prverify sk.pem -signature data.sig data # OpenSSL uses the PEM file format to store certificates and keys, which is essentially Base64 encoded binary content import hashlib# from ecdsa import SigningKey, VerifyingKey from ecdsa.util import sigencode_der, sigdecode_der# write and read signatures from ecdsa.util with open("vk.pem") as f:# public key file vk = VerifyingKey.from_pem(f.read()) with open("data", "rb") as f:#open() for read mode, with statement call close method directly, r for read mode, w/wb for write mode, rb mode open binary file, message data data = f.read() with open("data.sig", "rb") as f:# message signature readable mode signature = f.read() assert vk.verify(signature, data, hashlib.sha256, sigdecode=sigdecode_der)# public key verify signature with open("sk.pem") as f:# private key file sk = SigningKey.from_pem(f.read(), hashlib.sha256) new_signature = sk.sign_deterministic(data, sigencode=sigencode_der)# generate a new signature with the private key signature with open("data.sig2", "wb") as f:# write mode f.write(new_signature) # openssl dgst -sha256 -verify vk.pem -signature data.sig2 data # If you need compatibility with OpenSSL 1.0.0 or earlier, you can use sigencode_string and sigdecode_string in ecdsa.util to write and read signatures respectively. from ecdsa import SigningKey, VerifyingKey with open("sk.pem") as f: sk = SigningKey.from_pem(f.read()) with open("sk.pem", "wb") as f: f.write(sk.to_pem()) with open("vk.pem") as f: vk = VerifyingKey.from_pem(f.read()) with open("vk.pem", "wb") as f: f.write(vk.to_pem()) The #ecdsa.util.PRNG tool comes in handy here: it takes a seed and generates a strong pseudo-random stream from it. The #os.urandom function as entropy= argument does something different #ECDSA signature generation also requires a random number, and each signature must use a different random number (using the same number twice will immediately expose the private signature key). The # sk.sign() method takes an entropy= parameter and behaves the same as SigningKey.generate(entropy=). from ecdsa.util import PRNG from ecdsa import SigningKey rng1 = PRNG(b"seed") sk1 = SigningKey.generate(entropy=rng1) rng2 = PRNG(b"seed") sk2 = SigningKey.generate(entropy=rng2) # sk1 and sk2 are the same key # If you call SigningKey.sign_deterministic(data) instead of .sign(data), the code will generate a deterministic signature instead of a random one. # This uses the algorithm in RFC6979 to securely generate a unique K value that comes from the private key and the message being signed. Each time you sign the same message with the same key, you will get the same signature (using the same k). # Create a NIST521p key pair from ecdsa import SigningKey, NIST521p sk = SigningKey.generate(curve=NIST521p) vk = sk.verifying_key # Create three separate signing keys from a master seed from ecdsa import NIST192p, SigningKey from ecdsa.util import randrange_from_seed__trytryagain def make_key_from_seed(seed, curve=NIST192p): secexp = randrange_from_seed__trytryagain(seed, curve.order) return SigningKey.from_secret_exponent(secexp, curve) sk1 = make_key_from_seed("1:%s" % seed) sk2 = make_key_from_seed("2:%s" % seed) sk3 = make_key_from_seed("3:%s" % seed) # load a verifying key from disk and print it in uncompressed and compressed format using hexadecimal encoding (defined in the X9.62 and SEC1 standards) from ecdsa import VerifyingKey with open("public.pem") as f:# load the verification key vk = VerifyingKey.from_pem(f.read()) print("uncompressed: {0}".fo

#Not quite sep=": ",unit="s",form=".5f",form_inv=".2f",
prnt_form = (
    "{name:>16}{sep:1} {siglen:>6} {keygen:>9{form}}{unit:1} "
    "{keygen_inv:>9{form_inv}} {sign:>9{form}}{unit:1} "
    "{sign_inv:>9{form_inv}} {verify:>9{form}}{unit:1} "
    "{verify_inv:>9{form_inv}} {verify_single:>13{form}}{unit:1} "
    "{verify_single_inv:>14{form_inv}}"
)
print(
    prnt_form.format(
        siglen="siglen",
        keygen="keygen",
        keygen_inv="keygen/s",
        sign="sign",
        sign_inv="sign/s",
        verify="verify",
        verify_inv="verify/s",
        verify_single="no PC verify",
        verify_single_inv="no PC verify/s",
        name="",
        sep="",
        unit="",
        form="",
        form_inv="",
    )
)
for curve in [i.name for i in curves]:
    S1 = "import six; from ecdsa import SigningKey, %s" % curve
    S2 = "sk = SigningKey.generate(%s)" % curve # Generate private key
    S3 = "msg = six.b('msg')" # message
    S4 = "sig = sk.sign(msg)" # sign
    S5 = "vk = sk.get_verifying_key()"# public key derived from private key get_verifying_key() function
    S6 = "vk.precompute()"# don't understand
    S7 = "vk.verify(sig, msg)"# verify the signature with the public key
    # We happen to know that .generate() is also calculating the verification key, which is the most time-consuming part. If the code is changed to lazily compute vk, we would need to change this benchmark to loop over S5 instead of S2.
    keygen = do([S1], S2)
    sign = do([S1, S2, S3], S4)
    verf = do([S1, S2, S3, S4, S5, S6], S7)
    verf_single = do([S1, S2, S3, S4, S5], S7)
    import ecdsa
    c = getattr(ecdsa, curve)# get the attribute value from the name
    sig = ecdsa.SigningKey.generate(c).sign(six.b("msg"))
    # key pairs (keygen), signing data (sign), verifying these signatures (verify), sharing secrets (ecdh) and verifying signatures without a specific key precomputation (no PC verify), and the size of the original signature (usually the smallest way the signature can be encoded) are also provided in the siglen column
    print(
        prnt_form.format(
            name=curve,# all curves
            sep=":",
            siglen=len(sig),
            unit="s",
            keygen=keygen,
            keygen_inv=1.0 / keygen,
            sign=sign,
            sign_inv=1.0 / sign,
            verify=verf,
            verify_inv=1.0 / verf,
            verify_single=verf_single,
            verify_single_inv=1.0 / verf_single,
            form=".5f",# 5 decimal places after the decimal point
            form_inv=".2f",# 2 decimal places after the decimal point
        )
    )



print("")



from ecdsa import SigningKey, NIST384p#384-bit NIST prime domain elliptic curve, where private/public keys are associated with a specific curve, longer curves are more secure, but longer time and longer keys and signatures
sk = SigningKey.generate(curve=NIST384p)
vk = sk.verifying_key
signature = sk.sign(b"message")
assert vk.verify(signature, b"message")


from ecdsa import SigningKey, NIST384p
sk = SigningKey.generate(curve=NIST384p)
sk_pem = sk.to_pem() # sk.to_pem() and sk.to_der() will serialize the signing key to the same format used by OpenSSL
sk2 = SigningKey.from_pem(sk_pem)#SigningKey.from_pem()/.from_der() will undo this serialization. These formats include the curve name, so you don't need to pass the curve identifier to the deserializer. If the file is malformed, from_der() and from_pem() will raise UnexpectedDER or MalformedPointError.
# sk and sk2 are the same key



from ecdsa import SigningKey, VerifyingKey, NIST384p
sk = SigningKey.generate(curve=NIST384p)
vk = sk.verifying_key
vk_string = vk.to_string()# public key can be serialized in the same way
vk2 = VerifyingKey.from_string(vk_string, curve=NIST384p)
# vk and vk2 are the same key


import os
from ecdsa import NIST384p, SigningKey
from ecdsa.util import randrange_from_seed__trytryagain#generate random numbers
def make_key(seed):
  secexp = randrange_from_seed__trytryagain(seed, NIST384p.order)
  return SigningKey.from_secret_exponent(secexp, curve=NIST384p)
seed = os.urandom(NIST384p.baselen) # or other starting point, return a bit string suitable for encryption
sk1a = make_key(seed)
sk1b = make_key(seed)
# note: sk1a and sk1b are the same key
assert sk1a.to_string() == sk1b.to_string()
sk2 = make_key(b"2-"+seed) # different key b for bit
assert sk1a.to_string() ! = sk2.to_string()
from ecdsa import SigningKey, NIST384p
sk = SigningKey.generate(curve=NIST384p)
vk = sk.verifying_key
vk.precompute()
signature = sk.sign(b"message")
assert vk.verify(signature, b"message")



# openssl ecparam -name prime256v1 -genkey -out sk.pem
# openssl ec -in sk.pem -pubout -out vk.pem
# echo "data for signing" > data
# openssl dgst -sha256 -sign sk.pem -out data.sig data
# openssl dgst -sha256 -verify vk.pem -signature data.sig data
# openssl dgst -sha256 -prverify sk.pem -signature data.sig data
# OpenSSL uses the PEM file format to store certificates and keys, which is essentially Base64 encoded binary content
import hashlib#
from ecdsa import SigningKey, VerifyingKey
from ecdsa.util import sigencode_der, sigdecode_der# write and read signatures from ecdsa.util
with open("vk.pem") as f:# public key file
   vk = VerifyingKey.from_pem(f.read())
with open("data", "rb") as f:#open() for read mode, with statement call close method directly, r for read mode, w/wb for write mode, rb mode open binary file, message data
   data = f.read()
with open("data.sig", "rb") as f:# message signature readable mode
   signature = f.read()
assert vk.verify(signature, data, hashlib.sha256, sigdecode=sigdecode_der)# public key verify signature
with open("sk.pem") as f:# private key file
   sk = SigningKey.from_pem(f.read(), hashlib.sha256)
new_signature = sk.sign_deterministic(data, sigencode=sigencode_der)# generate a new signature with the private key signature
with open("data.sig2", "wb") as f:# write mode
   f.write(new_signature)


The #ecdsa.util.PRNG tool comes in handy here: it takes a seed and generates a strong pseudo-random stream from it.
The #os.urandom function as entropy= argument does something different
#ECDSA signature generation also requires a random number, and each signature must use a different random number (using the same number twice will immediately expose the private signature key).
The # sk.sign() method takes an entropy= parameter and behaves the same as SigningKey.generate(entropy=).
from ecdsa.util import PRNG
from ecdsa import SigningKey
rng1 = PRNG(b"seed")
sk1 = SigningKey.generate(entropy=rng1)
rng2 = PRNG(b"seed")
sk2 = SigningKey.generate(entropy=rng2)
# sk1 and sk2 are the same key



# If you call SigningKey.sign_deterministic(data) instead of .sign(data), the code will generate a deterministic signature instead of a random one.
# This uses the algorithm in RFC6979 to securely generate a unique K value that comes from the private key and the message being signed. Each time you sign the same message with the same key, you will get the same signature (using the same k).
# Create a NIST521p key pair
from ecdsa import SigningKey, NIST521p
sk = SigningKey.generate(curve=NIST521p)
vk = sk.verifying_key
# Create three separate signing keys from a master seed
from ecdsa import NIST192p, SigningKey
from ecdsa.util import randrange_from_seed__trytryagain
def make_key_from_seed(seed, curve=NIST192p):
    secexp = randrange_from_seed__trytryagain(seed, curve.order)
    return SigningKey.from_secret_exponent(secexp, curve)
sk1 = make_key_from_seed("1:%s" % seed)
sk2 = make_key_from_seed("2:%s" % seed)
sk3 = make_key_from_seed("3:%s" % seed)
# load a verifying key from disk and print it in uncompressed and compressed format using hexadecimal encoding (defined in the X9.62 and SEC1 standards)
from ecdsa import VerifyingKey
with open("public.pem") as f:# load the verification key
    vk = VerifyingKey.from_pem(f.read())
print("uncompressed: {0}".fo
# openssl ecparam -name prime256v1 -genkey -out sk.pem
# openssl ec -in sk.pem -pubout -out vk.pem
# echo "data for signing" > data
# openssl dgst -sha256 -sign sk.pem -out data.sig data
# openssl dgst -sha256 -verify vk.pem -signature data.sig data
# openssl dgst -sha256 -prverify sk.pem -signature data.sig data
# OpenSSL uses the PEM file format to store certificates and keys, which is essentially Base64 encoded binary content
import hashlib#
from ecdsa import SigningKey, VerifyingKey
from ecdsa.util import sigencode_der, sigdecode_der# write and read signatures from ecdsa.util
with open("vk.pem") as f:# public key file
   vk = VerifyingKey.from_pem(f.read())
with open("data", "rb") as f:#open() for read mode, with statement call close method directly, r for read mode, w/wb for write mode, rb mode open binary file, message data
   data = f.read()
with open("data.sig", "rb") as f:# message signature readable mode
   signature = f.read()
assert vk.verify(signature, data, hashlib.sha256, sigdecode=sigdecode_der)# public key verify signature
with open("sk.pem") as f:# private key file
   sk = SigningKey.from_pem(f.read(), hashlib.sha256)
new_signature = sk.sign_deterministic(data, sigencode=sigencode_der)# generate a new signature with the private key signature
with open("data.sig2", "wb") as f:# write mode
   f.write(new_signature)



# openssl dgst -sha256 -verify vk.pem -signature data.sig2 data
# If you need compatibility with OpenSSL 1.0.0 or earlier, you can use sigencode_string and sigdecode_string in ecdsa.util to write and read signatures respectively.
from ecdsa import SigningKey, VerifyingKey
with open("sk.pem") as f:
    sk = SigningKey.from_pem(f.read())
with open("sk.pem", "wb") as f:
    f.write(sk.to_pem())
with open("vk.pem") as f:
    vk = VerifyingKey.from_pem(f.read())
with open("vk.pem", "wb") as f:
    f.write(vk.to_pem())


The #ecdsa.util.PRNG tool comes in handy here: it takes a seed and generates a strong pseudo-random stream from it.
The #os.urandom function as entropy= argument does something different
#ECDSA signature generation also requires a random number, and each signature must use a different random number (using the same number twice will immediately expose the private signature key).
The # sk.sign() method takes an entropy= parameter and behaves the same as SigningKey.generate(entropy=).
from ecdsa.util import PRNG
from ecdsa import SigningKey
rng1 = PRNG(b"seed")
sk1 = SigningKey.generate(entropy=rng1)
rng2 = PRNG(b"seed")
sk2 = SigningKey.generate(entropy=rng2)
# sk1 and sk2 are the same key


# If you call SigningKey.sign_deterministic(data) instead of .sign(data), the code will generate a deterministic signature instead of a random one.
# This uses the algorithm in RFC6979 to securely generate a unique K value that comes from the private key and the message being signed. Each time you sign the same message with the same key, you will get the same signature (using the same k).
# Create a NIST521p key pair
from ecdsa import SigningKey, NIST521p
sk = SigningKey.generate(curve=NIST521p)
vk = sk.verifying_key
# Create three separate signing keys from a master seed
from ecdsa import NIST192p, SigningKey
from ecdsa.util import randrange_from_seed__trytryagain
def make_key_from_seed(seed, curve=NIST192p):
    secexp = randrange_from_seed__trytryagain(seed, curve.order)
    return SigningKey.from_secret_exponent(secexp, curve)
sk1 = make_key_from_seed("1:%s" % seed)
sk2 = make_key_from_seed("2:%s" % seed)
sk3 = make_key_from_seed("3:%s" % seed)
# load a verifying key from disk and print it in uncompressed and compressed format using hexadecimal encoding (defined in the X9.62 and SEC1 standards)
from ecdsa import VerifyingKey
with open("public.pem") as f:# load the verification key
    vk = VerifyingKey.from_pem(f.read())
print("uncompressed: {0}".fo