{ "info": { "author": "Stephan Richter and the Zope Community", "author_email": "zope-dev@zope.org", "bugtrack_url": null, "classifiers": [ "Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Intended Audience :: Developers", "License :: OSI Approved :: Zope Public License", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Internet :: WWW/HTTP" ], "description": "This package provides a NIST SP 800-57 compliant Key Management Infrastructure\n(KMI).\n\nTo get started do::\n\n $ python bootstrap.py # Must be Python 2.7 or higher\n $ ./bin/buildout # Depends on successful compilation of M2Crypto\n $ ./bin/runserver # or ./bin/gunicorn --paste server.ini\n\nThe server will come up on port 8080. You can create a new key encrypting key\nusing::\n\n $ wget https://localhost:8080/new -O kek.dat --ca-certificate sample.crt \\\n --post-data=\"\"\n\nor, if you want a more convenient tool::\n\n $ ./bin/testclient https://localhost:8080 -n > kek.dat\n\nThe data encryption key can now be retrieved by posting the KEK to another\nURL::\n\n $ wget https://localhost:8080/key --header 'Content-Type: text/plain' \\\n --post-file kek.dat -O datakey.dat --ca-certificate sample.crt\n\nor ::\n\n $ ./bin/testclient https://localhost:8080 -g kek.dat > datakey.dat\n\nNote: To be compliant, the server must use an encrypted communication channel\nof course. The ``--ca-certificate`` tells wget to trust the sample self-signed\ncertificate included in the keas.kmi distribution; you'll want to generate a\nnew SSL certificate for production use.\n\n\n=============================\nKey Management Infrastructure\n=============================\n\nThis package provides a NIST SP 800-57 compliant key management\ninfrastructure. Part of this infrastructure is a key management facility that\nprovides several services related to keys. All keys are stored in a specified\nstorage directory.\n\n >>> from __future__ import print_function\n >>> import tempfile\n >>> storage_dir = tempfile.mkdtemp()\n\n >>> from keas.kmi import facility\n >>> keys = facility.KeyManagementFacility(storage_dir)\n >>> keys\n \n\n >>> from zope.interface import verify\n >>> from keas.kmi import interfaces\n >>> verify.verifyObject(interfaces.IKeyManagementFacility, keys)\n True\n\nOne of the services the facility provides in the generation of new keys.\n\n >>> verify.verifyObject(interfaces.IKeyGenerationService, keys)\n True\n\nThe algorithm to generate a new pair of keys is somewhat involved. The\nfollowing features are required:\n\n(A) The key local to the data cannot be directly used as the encrypting key.\n\n(B) The encrypting key must be stored using a cipher that is at least as\n strong as the key itself.\n\n(C) The computer storing the data cannot also store the key.\n\nThis suggests the following algorithm to generate and store a new encrypting\nkey:\n\n1. Create the key encrypting key (private and public).\n\n2. Create the encryption key.\n\n3. Use the public key encrypting key to encrypt both the encryption keys.\n\n4. Discard the public key encrypting key. It is important that this key is\n never stored anywhere.\n\n5. Store the encrypted encryption key in the key management facility.\n\n6. Return the private key encrypting key.\n\nLet's now use the key generation service's API to generate a key.\n\n >>> key = keys.generate()\n >>> print(key.decode())\n -----BEGIN RSA PRIVATE KEY-----\n ...\n -----END RSA PRIVATE KEY-----\n\nBy default the system uses the AES 256 cipher, because public commentary\nsuggests that the AES 196 or AES 256 cipher sufficiently fulfill the PCI,\nHIPAA and NIST key strength requirement.\n\nYou can now use this key encrypting key to extract the encryption keys:\n\n >>> from hashlib import md5\n >>> hash_key = md5(key).hexdigest()\n\n >>> len(keys.get(hash_key))\n 256\n\nOur key management facility also supports the encryption service, which allows\nyou to encrypt and decrypt a string given the key encrypting key.\n\n >>> verify.verifyObject(interfaces.IEncryptionService, keys)\n True\n\nLet's now encrypt some data:\n\n >>> encrypted = keys.encrypt(key, b'Stephan Richter')\n >>> len(encrypted)\n 16\n\nWe can also decrypt the data.\n\n >>> keys.decrypt(key, encrypted) == b'Stephan Richter'\n True\n\nWe can also encrypt data given by a file descriptor\n\n >>> import tempfile\n >>> tmp_file = tempfile.TemporaryFile()\n >>> data = b\"encryptioniscool\"*24*1024\n >>> pos = tmp_file.write(data)\n >>> pos = tmp_file.seek(0)\n >>> encrypted_file = tempfile.TemporaryFile()\n >>> keys.encrypt_file(key, tmp_file, encrypted_file)\n >>> tmp_file.close()\n\nAnd decrypt the file\n\n >>> decrypted_file = tempfile.TemporaryFile()\n >>> pos = encrypted_file.seek(0)\n >>> keys.decrypt_file(key, encrypted_file, decrypted_file)\n >>> encrypted_file.close()\n\n >>> pos = decrypted_file.seek(0)\n >>> decrypted_data = decrypted_file.read()\n >>> decrypted_file.close()\n >>> decrypted_data == data\n True\n\nAnd that's pretty much all there is to it. Most of the complicated\ncrypto-related work happens under the hood, transparent to the user.\n\nOne final note. Once the data encrypting key is looked up and decrypted, it is\ncached, since constantly decrypting the the DEK is expensive.\n\n >>> hash_key in keys._KeyManagementFacility__dek_cache\n True\n\nA timeout (in seconds) controls when a key must be looked up:\n\n >>> keys.timeout\n 3600\n\nLet's now force a reload by setting the timeout to zero:\n\n >>> keys.timeout = 0\n\nThe cache is a dictionary of key encrypting key to a 2-tuple that contains the\ndate/time the key has been fetched and the unencrypted DEK.\n\n >>> firstTime = keys._KeyManagementFacility__dek_cache[hash_key][0]\n\n >>> keys.decrypt(key, encrypted) == b'Stephan Richter'\n True\n\n >>> secondTime = keys._KeyManagementFacility__dek_cache[hash_key][0]\n\n >>> firstTime < secondTime\n True\n\n\nThe Local Key Management Facility\n---------------------------------\n\nHowever, using the master key management facility's encryption service is\nexpensive, since each encryption and decryption request would require a\nnetwork request. Fortunately, we can\n\n(A) communicate encryption keys across multiple devices, and\n\n(B) keep encryption keys in memory.\n\nIt is only required that the data transfer is completed via an encrypted\ncommunication channel. In this implementation the communication protocol is\nHTTP and thus a sufficiently strong SSL connection is appropriate.\n\nLet's now instantiate the local key management facility:\n\n >>> localKeys = facility.LocalKeyManagementFacility('http://localhost/keys')\n >>> localKeys\n \n\nThe argument to the constructor is the URL to the master key management\nfacility. The local facility will use a small REST API to communicate with the\nserver.\n\nFor the purpose of this test, we are going to install a network component that\nonly simulates the requests:\n\n >>> from keas.kmi import testing\n >>> testing.setupRestApi(localKeys, keys)\n\nAs with the master facility, the local facility provides the\n``IEncryptionService`` interface:\n\n >>> verify.verifyObject(interfaces.IEncryptionService, localKeys)\n True\n\nSo en- and decryption is very easy to do:\n\n >>> encrypted = localKeys.encrypt(key, b'Stephan Richter')\n >>> len(encrypted)\n 16\n\n >>> localKeys.decrypt(key, encrypted) == b'Stephan Richter'\n True\n\nInstead of forwarding the en- an decryption request to the master facility,\nthe local facility merely fetches the encryption key pair and executes the\noperation locally. This approach has the following advantages:\n\n(A) There is no general network latency for any en- and decryption call.\n\n(B) The expensive task of en- and decrypting a message is delegated to\n multiple servers, allowing better scaling.\n\n(C) Fetched keys can be cached locally decreasing the network calls to a once\n in a while process.\n\nIn this implementation, we do cache the keys in a private attribute:\n\n >>> key in localKeys._LocalKeyManagementFacility__cache\n True\n\nA timeout (in seconds) controls when a key must be refetched:\n\n >>> localKeys.timeout\n 3600\n\nLet's now force a reload by setting the timeout to zero:\n\n >>> localKeys.timeout = 0\n\nThe cache is a dictionary of key encrypting key to a 3-tuple that contains the\ndate/time the key has been fetched, the encryption (public) key, and the\ndecryption (private) key.\n\n >>> firstTime = localKeys._LocalKeyManagementFacility__cache[key][0]\n\n >>> localKeys.decrypt(key, encrypted) == b'Stephan Richter'\n True\n\n >>> secondTime = localKeys._LocalKeyManagementFacility__cache[key][0]\n\n >>> firstTime < secondTime\n True\n\nThe local facility also provides the ``IKeyGenerationService`` interface:\n\n >>> verify.verifyObject(interfaces.IKeyGenerationService, keys)\n True\n\nThe local method call is identical to the master one:\n\n >>> key2 = localKeys.generate()\n >>> print(key2.decode())\n -----BEGIN RSA PRIVATE KEY-----\n ...\n -----END RSA PRIVATE KEY-----\n\nThe operation is forwarded to the master server, so that the key is available\nthere as well:\n\n >>> hash = md5(key2)\n\n >>> hash.hexdigest() in keys\n True\n\n\nThe REST API\n------------\n\nThe REST API of the master key management facility defines the communication\nwith the local facility. When a new encryption key pair is created, we simply\nmake a `POST` call to the following URL::\n\n http://server:port/new\n\nThe request should have no body and the response is simply the key encrypting\nkey.\n\nSo let's have a look at the call:\n\n >>> from keas.kmi import rest\n >>> from webob import Request\n\n >>> request = Request({})\n >>> key3 = rest.create_key(keys, request).body\n >>> print(key3.decode())\n -----BEGIN RSA PRIVATE KEY-----\n ...\n -----END RSA PRIVATE KEY-----\n\nThe key is available in the facility of course:\n\n >>> hash = md5(key3)\n >>> hash.hexdigest() in keys\n True\n\nWe can now fetch the encryption key pair using a `POST` call to this URL::\n\n http://server:port/key\n\nThe request sends the key encrypting key in its body. The response is the\nencryption key string:\n\n >>> request = Request({})\n >>> request.body = key3\n\n >>> encKey = rest.get_key(keys, request)\n >>> len(encKey.body)\n 128\n\nIf you try to request a nonexistent key, you get a 404 error: encryption key\nstring:\n\n >>> request.body = b'xxyz'\n >>> print(rest.get_key(keys, request))\n Key not found\n\nA `GET` request to the root shows us a server status page\n\n >>> print(rest.get_status(keys, Request({})))\n 200 OK\n Content-Type: text/plain\n Content-Length: 25\n \n KMS server holding 3 keys\n\n\nThe Testing Key Management Facility\n-----------------------------------\n\nThe testing facility only manages a single key that is always constant. This\nallows you to install a testing facility globally, not storing the keys in the\ndatabase and still reuse a ZODB over multiple sessions.\n\n >>> storage_dir = tempfile.mkdtemp()\n >>> testingKeys = testing.TestingKeyManagementFacility(storage_dir)\n\nOf course, the key generation service is supported:\n\n >>> verify.verifyObject(interfaces.IKeyGenerationService, keys)\n True\n\nHowever, you will always receive the same key:\n\n >>> def getKeySegment(key):\n ... return str(key.decode().split('\\n')[1])\n\n >>> getKeySegment(testingKeys.generate())\n 'MIIBOAIBAAJBAL+VS9lDsS9XOaeJppfK9lhxKMRFdcg50MR3aJEQK9rvDEqNwBS9'\n >>> getKeySegment(testingKeys.generate())\n 'MIIBOAIBAAJBAL+VS9lDsS9XOaeJppfK9lhxKMRFdcg50MR3aJEQK9rvDEqNwBS9'\n\n >>> storage_dir = tempfile.mkdtemp()\n >>> testingKeys = testing.TestingKeyManagementFacility(storage_dir)\n >>> getKeySegment(testingKeys.generate())\n 'MIIBOAIBAAJBAL+VS9lDsS9XOaeJppfK9lhxKMRFdcg50MR3aJEQK9rvDEqNwBS9'\n\nAll other methods remain the same:\n\n >>> key = testingKeys.generate()\n >>> testingKeys.getEncryptionKey(key) == b'_\\xc4\\x04\\xbe5B\\x7f\\xaf\\xd6\\x92\\xbd\\xa0\\xcf\\x156\\x1d\\x88=p9{\\xaal\\xb4\\x84M\\x1d\\xfd\\xb2z\\xae\\x1a'\n True\n\nWe can also safely en- and decrypt:\n\n >>> encrypted = testingKeys.encrypt(key, b'Stephan Richter')\n >>> testingKeys.decrypt(key, encrypted) == b'Stephan Richter'\n True\n\n\nKey Holder\n----------\n\nThe key holder is a simple class designed to store a key in RAM:\n\n >>> from keas.kmi import keyholder\n >>> holder = keyholder.KeyHolder(__file__)\n\n >>> verify.verifyObject(interfaces.IKeyHolder, holder)\n True\n\n\n============================\nEncrypted Persistent Objects\n============================\n\nThis package provides an `EncryptedPersistent` class that takes care of data\nencryption in the storage. Usage is pretty simple: instead of subclassing\n`persistent.Persistent`, subclass `keas.kmi.persistent.EncryptedPersistent`:\n\n >>> from keas.kmi.persistent import EncryptedPersistent\n >>> class UserPrivateData(EncryptedPersistent):\n ... def __init__(self, name, ssn):\n ... self.name = name\n ... self.ssn = ssn\n ... def __repr__(self):\n ... return '' % (self.name, self.ssn)\n\n >>> userdata = UserPrivateData('Stephan Richter', '123456789')\n >>> userdata\n \n\nThe key used for encryption and decryption comes from an IKeyHolder utility\nthat you're supposed to provide in your application.\n\n >>> from keas.kmi.testing import TestingKeyHolder\n >>> from zope.component import provideUtility\n >>> provideUtility(TestingKeyHolder())\n\nNone of the raw data appears in the pickle\n\n >>> from zodbpickle import pickle\n >>> pickled_data = pickle.dumps(userdata)\n >>> b'Stephan' in pickled_data\n False\n >>> b'123456789' in pickled_data\n False\n\nWe can successfully load it\n\n >>> pickle.loads(pickled_data)\n \n\nEvery persistent object is stored separately. Only the objects that inherit\nfrom `EncryptedPersistent` will be encrypted.\n\n >>> import persistent.dict\n >>> users = persistent.dict.PersistentDict()\n >>> users['stephan'] = UserPrivateData('Stephan Richter', '123456789')\n >>> users['mgedmin'] = UserPrivateData('Marius Gedminas', '987654321')\n\n >>> pickled_data = pickle.dumps(users)\n >>> b'stephan' in pickled_data\n True\n >>> b'123456789' in pickled_data\n False\n\n\nPersistent References\n---------------------\n\nEnough pickling; we really should make sure our magic does not interfere\nwith ZODB keeping track of persistent object references.\n\nFirst let's make our `EncryptedPersistent` objects have some references to\nother (encrypted and unencrypted) persistent objects\n\n >>> users['stephan'].__parent__ = users\n >>> users['mgedmin'].__parent__ = users\n\n >>> users['stephan'].friend = users['mgedmin']\n >>> users['mgedmin'].friend = users['stephan']\n\nNow let's create a database:\n\n >>> import ZODB.DB\n >>> import ZODB.MappingStorage\n >>> db = ZODB.DB(ZODB.MappingStorage.MappingStorage())\n >>> conn = db.open()\n >>> conn.root()['users'] = users\n >>> import transaction\n >>> transaction.commit()\n\nAnd we can open a second connection (while carefully keeping the first one\nopen, to ensure it's not reused and we actually load the pickles rather than\nreceiving persistent objects from a cache) and load the whole object graph\n\n >>> conn2 = db.open()\n >>> users2 = conn2.root()['users']\n >>> users2['stephan']\n \n >>> users2['mgedmin']\n \n\nAll the object references between persistent and encrypted persistent objects\nare preserved correctly:\n\n >>> users2['stephan'].friend\n \n >>> users2['mgedmin'].friend\n \n\n >>> users2['stephan'].__parent__ is users2\n True\n >>> users2['mgedmin'].__parent__ is users2\n True\n >>> users2['stephan'].friend is users2['mgedmin']\n True\n >>> users2['mgedmin'].friend is users2['stephan']\n True\n\n\nData conversion\n---------------\n\nIf you used to have simple persistent objects, and now want to convert them\nto `EncryptedPersistent`, think again. This is not secure. You already have\nunencrypted bits on your disk platters, and the only way to get rid of them\nis to physically destroy the disk.\n\nBut if you have a testing-only database with fake data, and would like to\ncontinue using it with a small conversion step, you can use the\n``convert_object_to_encrypted()`` function.\n\n >>> from keas.kmi.persistent import convert_object_to_encrypted\n\nHere's the old class definition that we'll store:\n\n >>> from persistent import Persistent\n >>> class Password(Persistent):\n ... def __init__(self, password):\n ... self.password = password\n\n >>> db = ZODB.DB(ZODB.MappingStorage.MappingStorage())\n >>> conn = db.open()\n >>> conn.root()['pwd'] = Password('xyzzy')\n >>> transaction.commit()\n\nAnd now we redefine the class:\n\n >>> class Password(EncryptedPersistent):\n ... def __init__(self, password):\n ... self.password = password\n\nOnce again we have to use a different connection object (while keeping the\nfirst one alive) to avoid stepping on a ZODB cache:\n\n >>> conn2 = db.open()\n >>> pwd = conn2.root()['pwd']\n\nIf you try to use `Password` objects loaded from the database, you'll get an\nerror:\n\n >>> pwd.password\n Traceback (most recent call last):\n ...\n ValueError: not enough values to unpack (expected 2, got 1)\n\nBut we can apply the conversion step:\n\n >>> convert_object_to_encrypted(pwd)\n >>> pwd.password\n 'xyzzy'\n\nThe converted state is stored in the DB\n\n >>> transaction.commit()\n >>> conn3 = db.open()\n >>> pwd = conn3.root()['pwd']\n >>> pwd.password\n 'xyzzy'\n\n\n=======\nCHANGES\n=======\n\n3.2.1 (2018-10-15)\n------------------\n\n- The default initializeVector is now a byte string.\n\n\n3.2.0 (2017-05-16)\n------------------\n\n- Add support for Python 3.4, 3.5, 3.6.\n\n\n3.1.1 (2017-04-19)\n------------------\n\n- Removed all version constraints from buildout.cfg, since the app\n works fine with all the latest versions.\n\n\n3.1.0 (2016-04-22)\n------------------\n\n- Add an implementation of encrypt_file and decrypt_file.\n This allows chunked encoding and decoding of files.\n [pcdummy]\n\n\n3.0.1 (2016-04-05)\n------------------\n\n- Bumped the setuptools version in buildout.cfg.\n\n\n3.0.0 (2014-01-06)\n------------------\n\n- Switched from `M2Crypto` to `PyCrypto`, since `M2Crypto` is not maintained\n anymore.\n\n- Switched from deprecated `repoze.bfg` to `pyramid`.\n\nNOTE: While I found code online to make the switch from `PyCrypto` to\n`M2Crypto` backwards compatible, I have not tested that functionality. Please\ntry this on your data and let me know if you have issues.\n\nNOTE 2: `PyCrypto` does not allow 512-bit RSA keys, so I increased the key\nsize to 2048 bits. Old 512-bit keys should still work, but new ones will be\nalways larger now.\n\n\n2.1.0 (2010-10-07)\n------------------\n\n- Added a cache for unencrypted DEKs in the key management facility, like it\n was already done for the local key management facility. This increases\n encryption and decryption performance by an order of magnitude from roughly\n 2ms to 0.2ms.\n\n2.0.0 (2010-09-29)\n------------------\n\n- Refactored REST server to be a simple repoze.bfg application.\n\n- The encrypted data encrypting keys (DEKs) are now stored in a directory\n instead of the ZODB. This increases transparency in the data store and makes\n backups easier.\n\n- Added caching to directory-based facility, so we do not need to read files\n all the time.\n\n1.1.1 (2010-08-27)\n------------------\n\n- Fixed deprecation warnings about md5 and zope.testing.doctest.\n\n1.1.0 (2010-08-25)\n------------------\n\n- Feature: Updated code to work with Bluebream 1.0b3.\n\n1.0.0 (2009-07-24)\n------------------\n\n- Feature: Update to the latest package versions.\n\n\n0.3.1 (2008-09-11)\n------------------\n\n- Relax M2Crypto version requirements to 0.18 or newer.\n\n\n0.3.0 (2008-09-04)\n------------------\n\n- A simple KeyHolder utility is available in keas.kmi.keyholder.\n\n\n0.2.0 (2008-09-04)\n------------------\n\n- Sample server shows how to enable SSL\n\n- Front page now shows the number of stored keys instead of a\n ComponentLookupError message.\n\n- Command-line client for testing a remote Key Management Server\n\n- Bugfix: LocalKeyManagementFacility was broken (AttributeError: 'RESTClient'\n object has no attribute 'POST')\n\n\n0.1.0 (2008-09-03)\n------------------\n\n- Initial Release\n\n * Key Generation Service\n\n * Encryption Service (Master and Local)\n\n * REST API for key communication between encryption services\n\n * Encrypted Persistent Storage", "description_content_type": "", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "http://pypi.python.org/pypi/keas.kmi", "keywords": "security key management infrastructure nist 800-57", "license": "ZPL 2.1", "maintainer": "", "maintainer_email": "", "name": "keas.kmi", "package_url": "https://pypi.org/project/keas.kmi/", "platform": "", "project_url": "https://pypi.org/project/keas.kmi/", "project_urls": { "Homepage": "http://pypi.python.org/pypi/keas.kmi" }, "release_url": "https://pypi.org/project/keas.kmi/3.2.1/", "requires_dist": null, "requires_python": "", "summary": "A Key Management Infrastructure", "version": "3.2.1" }, "last_serial": 4376717, "releases": { "0.1.0": [ { "comment_text": "", "digests": { "md5": "c7b73ac49fd44ec236b9e7c727be432c", "sha256": "01448de9c8cff0710d3f105b7f42fafe13424ea3e2adf281f90df5e4c01edfc5" }, "downloads": -1, "filename": "keas.kmi-0.1.0.tar.gz", "has_sig": false, "md5_digest": "c7b73ac49fd44ec236b9e7c727be432c", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 24028, "upload_time": "2008-09-04T07:42:57", "url": "https://files.pythonhosted.org/packages/f2/ff/8d353c11c20cacce75fb4cb585e5e032d6a2c969db4b63bc2904e500cd0a/keas.kmi-0.1.0.tar.gz" } ], "0.2.0": [ { "comment_text": "", "digests": { "md5": "b83bf8237854d17c8f88c26f78c9afda", "sha256": "59219b730b84fcbf9d3ca85a4ab1ee4e9bf3cb6e4423063a89c7672b1f305bfe" }, "downloads": -1, "filename": "keas.kmi-0.2.0.tar.gz", "has_sig": false, "md5_digest": "b83bf8237854d17c8f88c26f78c9afda", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 23179, "upload_time": "2008-09-04T21:56:28", "url": "https://files.pythonhosted.org/packages/2c/25/d0f359c14ea76c9721f0a6ea4f56ceae25da6ee7de784db99911ae12ec78/keas.kmi-0.2.0.tar.gz" } ], "0.3.0": [ { "comment_text": "", "digests": { "md5": "037029eaf68c65069f052fe17aba78e2", "sha256": "30baf18f3d6690d10e3c535f4dbe997efac8fd8ad4c179ca9aa166ca733eacb1" }, "downloads": -1, "filename": "keas.kmi-0.3.0.tar.gz", "has_sig": false, "md5_digest": "037029eaf68c65069f052fe17aba78e2", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 23830, "upload_time": "2008-09-05T00:01:23", "url": "https://files.pythonhosted.org/packages/76/39/2ff9422b009ee3e9b00240525d413ea622d1415eb789da11dff68d7ca6f0/keas.kmi-0.3.0.tar.gz" } ], "0.3.1": [ { "comment_text": "", "digests": { "md5": "1136f0b9a45e43133afdbb09afddcc42", "sha256": "588d0fee3219567c1e18a205f2c2f2d903b226710955a146f3aca65f9186b0d1" }, "downloads": -1, "filename": "keas.kmi-0.3.1.tar.gz", "has_sig": false, "md5_digest": "1136f0b9a45e43133afdbb09afddcc42", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 23902, "upload_time": "2008-09-11T19:18:22", "url": "https://files.pythonhosted.org/packages/d6/25/1094af6d0320776e30f7cb1b806ab937218d64df916dd1c3c80690d89de1/keas.kmi-0.3.1.tar.gz" } ], "1.0.0": [ { "comment_text": "", "digests": { "md5": "90d188385b6cc50a6fff27d938321b81", "sha256": "371fdb5d3651a8b52ec1c0e8ba625fd874d017c4e7a474bb4ccde45143d2ba13" }, "downloads": -1, "filename": "keas.kmi-1.0.0.tar.gz", "has_sig": false, "md5_digest": "90d188385b6cc50a6fff27d938321b81", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 27772, "upload_time": "2009-07-27T04:00:45", "url": "https://files.pythonhosted.org/packages/db/29/d122d5a031549cc2b2830bd2bd83f3812cf770375fe4a388a441a4bac0ba/keas.kmi-1.0.0.tar.gz" } ], "1.1.0": [ { "comment_text": "", "digests": { "md5": "cfe6d6bb1f17b6f8c91afe3bc8f6ff96", "sha256": "34a7d80dce774186f1248fa897c9d3bbab19630666b9f5424c1f217ee9dc38ba" }, "downloads": -1, "filename": "keas.kmi-1.1.0.tar.gz", "has_sig": false, "md5_digest": "cfe6d6bb1f17b6f8c91afe3bc8f6ff96", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 27738, "upload_time": "2010-08-26T04:40:31", "url": "https://files.pythonhosted.org/packages/d4/26/2d515080301d7587a0af8b093293afe03247f786134e66d0b06db1b4edf3/keas.kmi-1.1.0.tar.gz" } ], "1.1.1": [ { "comment_text": "", "digests": { "md5": "90fcd1c9cf77e3841b368d6f729ac193", "sha256": "e6df0bbde049bd420862d144ffc781b09e8e850efb16e06dcdeb777c95dc07cb" }, "downloads": -1, "filename": "keas.kmi-1.1.1.tar.gz", "has_sig": false, "md5_digest": "90fcd1c9cf77e3841b368d6f729ac193", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 26507, "upload_time": "2010-08-27T20:11:07", "url": "https://files.pythonhosted.org/packages/00/6d/1e70ba45b6e7bab8def1d901dacd40a46ec40a5e3f901b6462b613f3fd9f/keas.kmi-1.1.1.tar.gz" } ], "2.0.0": [ { "comment_text": "", "digests": { "md5": "f3e7f11c7e82e01cc9c8a6485e6960df", "sha256": "0aa75839e9ecf438085cf9786da6f11a1623a49018d9718eeacf371df922ced4" }, "downloads": -1, "filename": "keas.kmi-2.0.0.tar.gz", "has_sig": false, "md5_digest": "f3e7f11c7e82e01cc9c8a6485e6960df", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 27872, "upload_time": "2010-09-30T02:53:14", "url": "https://files.pythonhosted.org/packages/2f/59/9c2446aedfb49be097e9442266fdcacdc42d79b31aea6cf974d0e82a62a1/keas.kmi-2.0.0.tar.gz" } ], "2.1.0": [ { "comment_text": "", "digests": { "md5": "6a81dbfb790f44c472145e54d499976b", "sha256": "039653efbaeb855cc9029f1164ae4f9a6bf953a8b4d79aeb108ffff6955c9a3e" }, "downloads": -1, "filename": "keas.kmi-2.1.0.tar.gz", "has_sig": false, "md5_digest": "6a81dbfb790f44c472145e54d499976b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 28876, "upload_time": "2010-10-07T22:11:58", "url": "https://files.pythonhosted.org/packages/f7/d7/25c480af10bdda841d27be6bd19954712357545865408109537be3e1d4d9/keas.kmi-2.1.0.tar.gz" } ], "3.0.0": [ { "comment_text": "", "digests": { "md5": "f5058499d6d2cf5f7c51a33d68257702", "sha256": "e94aa7a4cfded5b62120096310278e050fdebd9600b3e7d9f4458405fb3ff010" }, "downloads": -1, "filename": "keas.kmi-3.0.0.zip", "has_sig": false, "md5_digest": "f5058499d6d2cf5f7c51a33d68257702", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 44502, "upload_time": "2014-01-06T16:36:15", "url": "https://files.pythonhosted.org/packages/56/7a/d280a124a7912b0e33ec8d54d0b1642392b5ffffcbc53a5d96af253290e4/keas.kmi-3.0.0.zip" } ], "3.1.0": [ { "comment_text": "", "digests": { "md5": "8eaacc64f8025009e7f7fcfe2ba53506", "sha256": "7dc7884bcfb4dffd92b36a4080df8759dc25c4d3e12daf214ad5ec3a3d4da217" }, "downloads": -1, "filename": "keas.kmi-3.1.0.tar.gz", "has_sig": false, "md5_digest": "8eaacc64f8025009e7f7fcfe2ba53506", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 32282, "upload_time": "2016-04-22T10:24:05", "url": "https://files.pythonhosted.org/packages/5d/0f/c00da1544b28548347b9062bb29314db676f0c2ffecc21bf76b6f3941b73/keas.kmi-3.1.0.tar.gz" } ], "3.1.1": [ { "comment_text": "", "digests": { "md5": "cb85830ce51071ca62c8522f94ca43b3", "sha256": "f03ad0eefd08db39624fbb8468934316cf8888c179237863136e0dbe6434423a" }, "downloads": -1, "filename": "keas.kmi-3.1.1.tar.gz", "has_sig": false, "md5_digest": "cb85830ce51071ca62c8522f94ca43b3", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 31716, "upload_time": "2017-04-19T18:38:43", "url": "https://files.pythonhosted.org/packages/15/5d/9e14d663e20810c01a9ab0d2400785e3737f6a0104b46762ab7de8e5daca/keas.kmi-3.1.1.tar.gz" } ], "3.2.0": [ { "comment_text": "", "digests": { "md5": "d07c97a0846ef46608ad99166f62d604", "sha256": "1c8bd04864d572769686c13d1fd1b795d90d32b9c586cd9b13846c636b222a66" }, "downloads": -1, "filename": "keas.kmi-3.2.0.tar.gz", "has_sig": false, "md5_digest": "d07c97a0846ef46608ad99166f62d604", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 32836, "upload_time": "2017-05-16T11:08:35", "url": "https://files.pythonhosted.org/packages/90/48/75e6c3a2424691e6e7e4cf4321b159d6e2e71c683edaad551ada07437643/keas.kmi-3.2.0.tar.gz" } ], "3.2.1": [ { "comment_text": "", "digests": { "md5": "ba246b9dc03f69c84d48d1cdd99e68c3", "sha256": "b879e9b431f8272116ae62c63f12a0844e77f427435b873dc81cca1303b28c6a" }, "downloads": -1, "filename": "keas.kmi-3.2.1.tar.gz", "has_sig": false, "md5_digest": "ba246b9dc03f69c84d48d1cdd99e68c3", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 30186, "upload_time": "2018-10-15T08:32:15", "url": "https://files.pythonhosted.org/packages/d8/e2/a5710dc20f22470224305325d01dc072dee8937415a54102d919ae6f18c6/keas.kmi-3.2.1.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "ba246b9dc03f69c84d48d1cdd99e68c3", "sha256": "b879e9b431f8272116ae62c63f12a0844e77f427435b873dc81cca1303b28c6a" }, "downloads": -1, "filename": "keas.kmi-3.2.1.tar.gz", "has_sig": false, "md5_digest": "ba246b9dc03f69c84d48d1cdd99e68c3", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 30186, "upload_time": "2018-10-15T08:32:15", "url": "https://files.pythonhosted.org/packages/d8/e2/a5710dc20f22470224305325d01dc072dee8937415a54102d919ae6f18c6/keas.kmi-3.2.1.tar.gz" } ] }