1. 程式人生 > >數字簽名及驗證例子

數字簽名及驗證例子

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”,