數字簽名及驗證例子
阿新 • • 發佈:2019-02-18
1. 數字簽名和驗證
數字簽名的生成:將簽名者的私鑰對資訊的雜湊值進行加密,獲得簽名者的數字簽名。
數字簽名的驗證:將簽名者的公鑰解密簽名者的數字簽名,然後和資訊的雜湊值作對比。如果兩者相同則數字簽名驗證成功,否則失敗。
2. 數字簽名和驗證的 Python 程式碼實現 “signmessage.py”
#!/usr/bin/env python
# the code below is 'borrowed' almost verbatim from electrum,
# https://gitorious.org/electrum/electrum
# and is under the GPLv3.
import ecdsa
import base64
import hashlib
from ecdsa.util import string_to_number
import sys
VERBOSE = False
#VERBOSE = True
# secp256k1, http://www.oid-info.com/get/1.3.132.0.10
_p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2FL
_r = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141L
_b = 0x0000000000000000000000000000000000000000000000000000000000000007L
_a = 0x0000000000000000000000000000000000000000000000000000000000000000L
_Gx = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798L
_Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8L
curve_secp256k1 = ecdsa.ellipticcurve.CurveFp( _p, _a, _b )
generator_secp256k1 = ecdsa.ellipticcurve.Point( curve_secp256k1, _Gx, _Gy, _r )
oid_secp256k1 = (1 ,3,132,0,10)
SECP256k1 = ecdsa.curves.Curve("SECP256k1", curve_secp256k1, generator_secp256k1, oid_secp256k1 )
addrtype = 0
# from http://eli.thegreenplace.net/2009/03/07/computing-modular-square-roots-in-python/
def modular_sqrt(a, p):
""" Find a quadratic residue (mod p) of 'a'. p
must be an odd prime.
Solve the congruence of the form:
x^2 = a (mod p)
And returns x. Note that p - x is also a root.
0 is returned is no square root exists for
these a and p.
The Tonelli-Shanks algorithm is used (except
for some simple cases in which the solution
is known from an identity). This algorithm
runs in polynomial time (unless the
generalized Riemann hypothesis is false).
"""
# Simple cases
#
if legendre_symbol(a, p) != 1:
return 0
elif a == 0:
return 0
elif p == 2:
return p
elif p % 4 == 3:
return pow(a, (p + 1) / 4, p)
# Partition p-1 to s * 2^e for an odd s (i.e.
# reduce all the powers of 2 from p-1)
#
s = p - 1
e = 0
while s % 2 == 0:
s /= 2
e += 1
# Find some 'n' with a legendre symbol n|p = -1.
# Shouldn't take long.
#
n = 2
while legendre_symbol(n, p) != -1:
n += 1
# Here be dragons!
# Read the paper "Square roots from 1; 24, 51,
# 10 to Dan Shanks" by Ezra Brown for more
# information
#
# x is a guess of the square root that gets better
# with each iteration.
# b is the "fudge factor" - by how much we're off
# with the guess. The invariant x^2 = ab (mod p)
# is maintained throughout the loop.
# g is used for successive powers of n to update
# both a and b
# r is the exponent - decreases with each update
#
x = pow(a, (s + 1) / 2, p)
b = pow(a, s, p)
g = pow(n, s, p)
r = e
while True:
t = b
m = 0
for m in xrange(r):
if t == 1:
break
t = pow(t, 2, p)
if m == 0:
return x
gs = pow(g, 2 ** (r - m - 1), p)
g = (gs * gs) % p
x = (x * gs) % p
b = (b * g) % p
r = m
def legendre_symbol(a, p):
""" Compute the Legendre symbol a|p using
Euler's criterion. p is a prime, a is
relatively prime to p (if p divides
a, then a|p = 0)
Returns 1 if a has a square root modulo
p, -1 otherwise.
"""
ls = pow(a, (p - 1) / 2, p)
return -1 if ls == p - 1 else ls
__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
__b58base = len(__b58chars)
def b58encode(v):
""" encode v, which is a string of bytes, to base58.
"""
long_value = 0L
for (i, c) in enumerate(v[::-1]):
long_value += (256**i) * ord(c)
result = ''
while long_value >= __b58base:
div, mod = divmod(long_value, __b58base)
result = __b58chars[mod] + result
long_value = div
result = __b58chars[long_value] + result
# Bitcoin does a little leading-zero-compression:
# leading 0-bytes in the input become leading-1s
nPad = 0
for c in v:
if c == '\0': nPad += 1
else: break
return (__b58chars[0]*nPad) + result
def b58decode(v, length):
""" decode v into a string of len bytes."""
long_value = 0L
for (i, c) in enumerate(v[::-1]):
long_value += __b58chars.find(c) * (__b58base**i)
result = ''
while long_value >= 256:
div, mod = divmod(long_value, 256)
result = chr(mod) + result
long_value = div
result = chr(long_value) + result
nPad = 0
for c in v:
if c == __b58chars[0]: nPad += 1
else: break
result = chr(0)*nPad + result
if length is not None and len(result) != length:
return None
return result
def msg_magic(message):
return "\x18Bitcoin Signed Message:\n" + chr( len(message) ) + message
def Hash(data):
return hashlib.sha256(hashlib.sha256(data).digest()).digest()
def hash_160(public_key):
md = hashlib.new('ripemd160')
md.update(hashlib.sha256(public_key).digest())
return md.digest()
def hash_160_to_bc_address(h160):
vh160 = chr(addrtype) + h160
h = Hash(vh160)
addr = vh160 + h[0:4]
return b58encode(addr)
def public_key_to_bc_address(public_key):
h160 = hash_160(public_key)
return hash_160_to_bc_address(h160)
def encode_point(pubkey, compressed=False):
order = generator_secp256k1.order()
p = pubkey.pubkey.point
x_str = ecdsa.util.number_to_string(p.x(), order)
y_str = ecdsa.util.number_to_string(p.y(), order)
if compressed:
return chr(2 + (p.y() & 1)) + x_str
else:
return chr(4) + x_str + y_str
def sign_message(private_key, message, compressed=False):
public_key = private_key.get_verifying_key()
signature = private_key.sign_digest( Hash( msg_magic( message ) ), sigencode = ecdsa.util.sigencode_string )
address = public_key_to_bc_address(encode_point(public_key, compressed))
assert public_key.verify_digest( signature, Hash( msg_magic( message ) ), sigdecode = ecdsa.util.sigdecode_string)
for i in range(4):
nV = 27 + i
if compressed:
nV += 4
sig = base64.b64encode( chr(nV) + signature )
try:
if verify_message( address, sig, message):
return sig
except:
continue
else:
raise BaseException("error: cannot sign message")
def verify_message(address, signature, message):
""" See http://www.secg.org/download/aid-780/sec1-v2.pdf for the math """
from ecdsa import numbertheory, ellipticcurve, util
curve = curve_secp256k1
G = generator_secp256k1
order = G.order()
# extract r,s from signature
sig = base64.b64decode(signature)
if len(sig) != 65: raise BaseException("Wrong encoding")
r,s = util.sigdecode_string(sig[1:], order)
nV = ord(sig[0])
if nV < 27 or nV >= 35:
return False
if nV >= 31:
compressed = True
nV -= 4
else:
compressed = False
recid = nV - 27
# 1.1
x = r + (recid/2) * order
# 1.3
alpha = ( x * x * x + curve.a() * x + curve.b() ) % curve.p()
beta = modular_sqrt(alpha, curve.p())
y = beta if (beta - recid) % 2 == 0 else curve.p() - beta
# 1.4 the constructor checks that nR is at infinity
R = ellipticcurve.Point(curve, x, y, order)
# 1.5 compute e from message:
h = Hash( msg_magic( message ) )
e = string_to_number(h)
minus_e = -e % order
# 1.6 compute Q = r^-1 (sR - eG)
inv_r = numbertheory.inverse_mod(r,order)
Q = inv_r * ( s * R + minus_e * G )
public_key = ecdsa.VerifyingKey.from_public_point( Q, curve = SECP256k1 )
# check that Q is the public key
public_key.verify_digest( sig[1:], h, sigdecode = ecdsa.util.sigdecode_string)
# check that we get the original signing address
addr = public_key_to_bc_address(encode_point(public_key, compressed))
if address == addr:
return True
else:
#print addr
return False
def sign_message_with_secret(secret, message, compressed=False):
private_key = ecdsa.SigningKey.from_secret_exponent( secret, curve = SECP256k1 )
public_key = private_key.get_verifying_key()
signature = private_key.sign_digest( Hash( msg_magic( message ) ), sigencode = ecdsa.util.sigencode_string )
address = public_key_to_bc_address(encode_point(public_key, compressed))
if VERBOSE: print 'address:\n', address
assert public_key.verify_digest( signature, Hash( msg_magic( message ) ), sigdecode = ecdsa.util.sigdecode_string)
for i in range(4):
nV = 27 + i
if compressed:
nV += 4
sig = base64.b64encode( chr(nV) + signature )
try:
if verify_message( address, sig, message):
return sig
except:
continue
else:
raise BaseException("error: cannot sign message")
def sign_message_with_private_key(base58_priv_key, message, compressed=True):
encoded_priv_key_bytes = b58decode(base58_priv_key, None)
encoded_priv_key_hex_string = encoded_priv_key_bytes.encode('hex')
secret_hex_string = ''
if base58_priv_key[0] == 'L' or base58_priv_key[0] == 'K':
assert len(encoded_priv_key_hex_string) == 76
# strip leading 0x08, 0x01 compressed flag, checksum
secret_hex_string = encoded_priv_key_hex_string[2:-10]
elif base58_priv_key[0] == '5':
assert len(encoded_priv_key_hex_string) == 74
# strip leading 0x08 and checksum
secret_hex_string = encoded_priv_key_hex_string[2:-8]
else:
raise BaseException("error: private must start with 5 if uncompressed or L/K for compressed")
if VERBOSE: print 'secret_hex_string:\n', secret_hex_string
secret = int(secret_hex_string, 16)
checksum = Hash(encoded_priv_key_bytes[:-4])[:4].encode('hex')
if VERBOSE: print 'checksum:\n', checksum
assert checksum == encoded_priv_key_hex_string[-8:] #make sure private key is valid
if VERBOSE: print 'secret:\n', secret
return sign_message_with_secret(secret, message, compressed)
def sign_and_verify(wifPrivateKey, message, bitcoinaddress, compressed=True):
sig = sign_message_with_private_key(wifPrivateKey, message, compressed)
assert verify_message(bitcoinaddress, sig, message)
if VERBOSE: print 'verify_message:', verify_message(bitcoinaddress, sig, message)
return sig
def test_sign_messages():
wif1 = '5KMWWy2d3Mjc8LojNoj8Lcz9B1aWu8bRofUgGwQk959Dw5h2iyw'
compressedPrivKey1 = 'L41XHGJA5QX43QRG3FEwPbqD5BYvy6WxUxqAMM9oQdHJ5FcRHcGk'
addressUncompressesed1 = '1HUBHMij46Hae75JPdWjeZ5Q7KaL7EFRSD'
addressCompressesed1 = '14dD6ygPi5WXdwwBTt1FBZK3aD8uDem1FY'
msg1 = 'test message'
print 'sig:\n', sign_and_verify(wif1, msg1, addressUncompressesed1, False) # good
print 'sig:\n', sign_and_verify(wif1, msg1, addressCompressesed1) # good
#print 'sig:\n', sign_and_verify(wif1, msg1, addressUncompressesed1) # bad
#print 'sig:\n', sign_and_verify(wif1, msg1, addressCompressesed1, False) # bad
print 'sig:\n', sign_and_verify(compressedPrivKey1, msg1, addressCompressesed1) # good
print 'sig:\n', sign_and_verify(compressedPrivKey1, msg1, addressUncompressesed1, False) # good
#print 'sig:\n', sign_and_verify(compressedPrivKey1, msg1, addressUncompressesed1) # bad
#print 'sig:\n', sign_and_verify(compressedPrivKey1, msg1, addressCompressesed1, False) # bad
def sign_input_message():
print 'Sign message\n'
address = raw_input("Enter address:\n")
message = raw_input("Enter message:\n")
base58_priv_key = raw_input("Enter private key:\n")
"""
address = '14dD6ygPi5WXdwwBTt1FBZK3aD8uDem1FY'
message = 'test message'
base58_priv_key = 'L41XHGJA5QX43QRG3FEwPbqD5BYvy6WxUxqAMM9oQdHJ5FcRHcGk'
#"""
compressed = True
if base58_priv_key[0] == 'L' or base58_priv_key[0] == 'K':
compressed = True
elif base58_priv_key[0] == '5':
compressed = False
else:
raise BaseException("error: private must start with 5 if uncompressed or L/K for compressed")
print '\n\n\n'
print address
print message
print base58_priv_key
print 'Signature:\n\n', sign_and_verify(base58_priv_key, message, address, compressed)
def verify_input_message():
print 'Verify message\n'
address = raw_input("Enter address:\n")
message = raw_input("Enter message:\n")
signature = raw_input("Enter signature:\n")
"""
address = '14dD6ygPi5WXdwwBTt1FBZK3aD8uDem1FY'
message = 'test message'
signature = 'IPn9bbEdNUp6+bneZqE2YJbq9Hv5aNILq9E5eZoMSF3/fBX4zjeIN6fpXfGSGPrZyKfHQ/c/kTSP+NIwmyTzMfk='
#"""
print '\n\n\n'
print address
print message
print signature
print 'Message verified:', verify_message(address, signature, message)
def main():
argv = sys.argv
if len(argv) > 1 and argv[1] == '-sign':
sign_input_message()
else:
verify_input_message()
if __name__ == '__main__':
#test_sign_messages()
main()
3. 程式碼測試
假設,給定橢圓曲線的一個公鑰私鑰對如下,
Alice公鑰:14dD6ygPi5WXdwwBTt1FBZK3aD8uDem1FY
Alice私鑰:L41XHGJA5QX43QRG3FEwPbqD5BYvy6WxUxqAMM9oQdHJ5FcRHcGk
原始資訊:Alice pay Bob $3
接著,Alice輸入私鑰對資訊 “Alice pay Bob $3” 進行數字簽名,如下,
如圖所示,1表示Alice的公鑰,2表示Alice要簽發的資訊,3表示Alice的私鑰,4表示Alice的簽名。
對資訊簽名完成之後,Alice將 <公鑰1,資訊2,數字簽名4>作為一條資訊傳送到網上,然後,網上任何人都可以驗證這一條資訊,驗證過程如下,
如圖所示,1表示Alice的公鑰,2表示Alice要簽發的資訊,3表示Alice的私鑰,4表示Alice的簽名。只有在資訊2的完整性沒有被破壞,且數字簽名沒有被偽造的情況下,數字簽名才能被驗證成功,
一旦資訊被偽造,比如說,這裡資訊2被偽造,整個數字簽名的驗證就會失敗。
總結,通過數字簽名,我們可以保證原始資訊的完整性,同時簽名者本人也無法抵賴。
最後,歡迎關注作者的微信公眾號 “BlkchainPlus”,