summaryrefslogtreecommitdiffstats
path: root/python/PyECC/ecc/Key.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/PyECC/ecc/Key.py')
-rw-r--r--python/PyECC/ecc/Key.py320
1 files changed, 320 insertions, 0 deletions
diff --git a/python/PyECC/ecc/Key.py b/python/PyECC/ecc/Key.py
new file mode 100644
index 000000000..8ba268576
--- /dev/null
+++ b/python/PyECC/ecc/Key.py
@@ -0,0 +1,320 @@
+# ====================================================================
+#
+# ELLIPTIC CURVE KEY ENCAPSULATION
+# Version 2011-01-26
+#
+# Copyright (c) 2010 - 2011 | Toni Mattis
+#
+# ====================================================================
+
+"""
+== Elliptic Curve Key Encapsulation ==
+
+Keypairs
+--------
+Keypairs are generated using: Key.generate(bits)
+
+The number of bits is tied to the NIST-proposed elliptic curves
+and has to be 192, 224, 256, 384 or 521 (not 512!).
+The result is a Key object containing public and private key.
+
+private() is a method for checking whether the Key object is a
+pure public key or also includes the private part.
+
+
+Exchange
+--------
+Public keys have to be exported using the export()-Method without
+passing an argument. The result is a string which can be safely
+transmitted.
+
+Using Key.decode(<encoded key>) the receiver obtains a new
+public Key object of the sender.
+
+
+Storage
+-------
+For storing a key, export(True) exports both private and public
+key as a string. Make sure this information is properly encrypted
+when stored.
+
+Key.decode(<encoded key>) obtains the full Key object from the
+encoded keypair.
+
+
+Public Keys
+-----------
+A public Key object can perform the following cryptographic
+operations:
+
+* validate() Checks key integrity, i.e. after loading the
+ key from a file. Returns True if the key is
+ valid. Invalid keys should be discarded.
+
+* fingerprint() Returns the public key fingerprint used to
+ identify the key. Optional arguments:
+ 1. as_hex - True, if output should be formatted
+ as hexadecimal number (default: True).
+ 2. hashfunc - The official name of the hash
+ function being used (default: 'sha1')
+ For supported hash functions see below.
+
+* keyid() Returns a (mostly) unique Key ID, which is
+ shorter than the fingerprint. The result
+ is an integer of max. 64 bits.
+
+* verify() Verifies whether the given data (argument 1)
+ matches the signature (argument 2) issued
+ by the owner of this key. A falsification
+ can have multiple causes:
+
+ - Data, public key or signature were altered
+ during transmission/storage.
+ - The siganture was not issued by the owner
+ of this key but may be valid with another
+ key.
+ - The signature was issued for different data.
+ - The signature was issued using a different
+ hash function. Another hash function may work.
+
+ Optionally, the name of a hash algorithm
+ can be provided. For hash names see below.
+
+* encrypt() Encrypts a packet of data destined for the owner
+ of this key*. After encryption only the holder
+ of this Key's private part is able to decrypt
+ the message.
+
+Private Keys / Keypairs
+-----------------------
+
+If the key object is private, then it is a keypair consisting of
+a public and a private key. Therefore all Public key operations
+are supported.
+
+Additional functions:
+
+* sign() Signs given data using this private key. The
+ result is a signature which can be passed as
+ argument to the verify() function in addition
+ to the data being verified.
+
+ As additional argument the name of the hash
+ function can be provided (defaults to 'sha256').
+ For hash names see below.
+
+* auth_encrypt() Performs authenticated encryption of data
+ (argument 1) for the holder of the key provided
+ as second argument. Only the receiver whose
+ public key is given is able to derypt and verify
+ the message. The message will be implicitly
+ signed using the own private key. *
+
+* decrypt() Decrypts a message which has been encrypted
+ using the public key of this keypair*. If
+ decryption yields random data, this can have
+ multiple causes:
+ - You were not the intended receiver, a different
+ private key may be able to decrypt it.
+ - The message was altered.
+ - Your private key is damaged.
+
+* auth_decrypt() Decrypts a message while verifying whether
+ it has been authentically issued by the holder
+ of the given key (argument 2). When
+ authentication failed, a
+ SecurityViolationException is thrown. Reasons
+ for this to happen are those mentioned with
+ decrypt() and verify(). *
+
+*) The encryption used here depends on the "eccrypt" module imported
+by this module. Default implementation should use RABBIT as cipher
+and do the asymmetric part using an optimized El-Gamal scheme.
+
+
+
+Hash functions
+--------------
+The following hash functions can be passed at the moment:
+
+name | hash size | security level
+ | (bits, bytes, hex digits)
+---------+------------------------+----------------
+'sha1' 160 / 20 / 40 medium
+'sha224' 224 / 28 / 56 medium-strong
+'sha256' 256 / 32 / 64 strong
+'sha384' 384 / 48 / 96 very strong
+'sha512' 512 / 64 / 128 very strong
+
+'md5' 128 / 16 / 32 weak (not recommended!)
+
+
+Curves
+------
+According to FIPS 186-3, Appendix D.1.2 there are 5 elliptic
+curves recommended. All of those are strong, but those with
+a higher bit number even stronger.
+
+192 and 224 bits are sufficient for most purposes.
+256 bits offer an additional magnitude of security.
+ (i.e. for classified / strongly confidential data)
+384 and 521 bits provide exceptionally strong security. According
+ to current research they most probably keep this level for
+ decades in the future.
+
+FIPS also recommends curves over polynomial fields but actually
+only prime fields are implemented here. (Because 2^521-1 is a mersenne
+prime having great security characteristics, 521 bits are preferred
+over a constructed 512 bit field.)
+"""
+
+from encoding import *
+from eccrypt import *
+import ecdsa
+import hashlib
+from SecurityViolationException import *
+
+class Key:
+
+ # --- KEY SETUP ------------------------------------------------------------
+
+ def __init__(self, public_key, private_key = None):
+ '''Create a Key(pair) from numeric keys.'''
+ self._pub = public_key
+ self._priv = private_key
+ self._fingerprint = {}
+ self._id = None
+
+ @staticmethod
+ def generate(bits):
+ '''Generate a new ECDSA keypair'''
+ return Key(*ecdsa.keypair(bits))
+
+ # --- BINARY REPRESENTATION ------------------------------------------------
+
+ def encode(self, include_private = False):
+ '''Returns a strict binary representation of this Key'''
+ e = Encoder().int(self.keyid(), 8)
+ e.int(self._pub[0], 2).point(self._pub[1], 2)
+ if include_private and self._priv:
+ e.long(self._priv[1], 2)
+ else:
+ e.long(0, 2)
+ return e.out()
+
+ def compress(self):
+ '''Returns a compact public key representation'''
+
+
+ @staticmethod
+ def decode(s):
+ '''Constructs a new Key object from its binary representation'''
+ kid, ksize, pub, priv = Decoder(s).int(8).int(2).point(2).long(2).out()
+ k = Key((ksize, pub), (ksize, priv) if priv else None)
+ if kid == k.keyid():
+ return k
+ else:
+ raise ValueError, "Invalid Key ID"
+
+ # --- IDENTIFICATION AND VALIDATION ----------------------------------------
+
+ def private(self):
+ '''Checks whether Key object contains private key'''
+ return bool(self._priv)
+
+ def validate(self):
+ '''Checks key validity'''
+ if ecdsa.validate_public_key(self._pub):
+ if self._priv: # ? validate and match private key
+ return ecdsa.validate_private_key(self._priv) and \
+ ecdsa.match_keys(self._pub, self._priv)
+ else:
+ return True # : everything valid
+ else:
+ return False
+
+ def fingerprint(self, as_hex = True, hashfunc = 'sha1'):
+ '''Get the public key fingerprint'''
+ if hashfunc in self._fingerprint:
+ return self._fingerprint[hashfunc] if not as_hex else \
+ self._fingerprint[hashfunc].encode("hex")
+ else:
+ h = hashlib.new(hashfunc, enc_point(self._pub[1]))
+ d = h.digest()
+ self._fingerprint[hashfunc] = d
+ return d.encode("hex") if as_hex else d
+
+ def keyid(self):
+ '''Get a short, unique identifier'''
+ if not self._id:
+ self._id = dec_long(self.fingerprint(False, 'sha1')[:8])
+ return self._id
+
+ # --- DIGITAL SIGNATURES ---------------------------------------------------
+
+ def sign(self, data, hashfunc = 'sha256'):
+ '''Sign data using the specified hash function'''
+ if self._priv:
+ h = dec_long(hashlib.new(hashfunc, data).digest())
+ s = ecdsa.sign(h, self._priv)
+ return enc_point(s)
+ else:
+ raise AttributeError, "Private key needed for signing."
+
+ def verify(self, data, sig, hashfunc = 'sha256'):
+ '''Verify the signature of data using the specified hash function'''
+ h = dec_long(hashlib.new(hashfunc, data).digest())
+ s = dec_point(sig)
+ return ecdsa.verify(h, s, self._pub)
+
+ # --- HYBRID ENCRYPTION ----------------------------------------------------
+
+ def encrypt(self, data):
+ '''Encrypt a message using this public key'''
+ ctext, mkey = encrypt(data, self._pub)
+ return Encoder().point(mkey).str(ctext, 4).out()
+
+ def decrypt(self, data):
+ '''Decrypt an encrypted message using this private key'''
+ mkey, ctext = Decoder(data).point().str(4).out()
+ return decrypt(ctext, mkey, self._priv)
+
+ # --- AUTHENTICATED ENCRYPTION ---------------------------------------------
+
+ def auth_encrypt(self, data, receiver):
+ '''Sign and encrypt a message'''
+ sgn = self.sign(data)
+ ctext, mkey = encrypt(data, receiver._pub)
+ return Encoder().point(mkey).str(ctext, 4).str(sgn, 2).out()
+
+ def auth_decrypt(self, data, source):
+ '''Decrypt and verify a message'''
+ mkey, ctext, sgn = Decoder(data).point().str(4).str(2).out()
+ text = decrypt(ctext, mkey, self._priv)
+ if source.verify(text, sgn):
+ return text
+ else:
+ raise SecurityViolationException, "Invalid Signature"
+
+
+if __name__ == "__main__":
+
+ import time
+
+ def test_overhead():
+ print "sender", "receiver", "+bytes", "+enctime", "+dectime"
+ for s in [192, 224, 256, 384, 521]:
+ sender = Key.generate(s)
+ for r in [192, 224, 256, 384, 521]:
+ receiver = Key.generate(r)
+ t = time.time()
+ e = sender.auth_encrypt("", receiver)
+ t1 = time.time() - t
+ t = time.time()
+ receiver.auth_decrypt(e, sender)
+ t2 = time.time() - t
+ print s, r, len(e), t1, t2
+
+
+
+