# utinyec
A tiny library to perform potentially unsafe cryptography with arithmetic operations on elliptic curves in pure micropython. No dependencies.
**This is not a library suitable for production.** It is useful for security professionals to understand the inner workings of EC, and be able to play with pre-defined curves.
No really, this module has not been tested for vulnerabilities. It should never be used in production. I am not accountable for what you choose to do with this module.
utinyec shows the mathematics behind eliptical curve cryptography (ECC) in pure python which is useful for educational purposes. C based solutions with python API can be compiled from these two libraries: [ucryptography](https://github.com/dmazzella/ucryptography) or [ucrypto](https://github.com/dmazzella/ucrypto), they use C for the cryptography which is MUCH faster than pure python.
If you want to convert this from micropython to regular python then change "from uos import urandom" to "from os import urandom" in both ec.py AND cryptography.py, then change 'from ucryptolib import aes' to 'from cryptolib import aes' in cryptography.py.
This package is very slow, and possibly unsafe as it has not been audited; if you need a cryptography solution in regular python please use pip install cryptography, or if you are using android you need to use 'apt install python-cryptography' as the rust dependency is dropped for functionality in Termux.
## installation in Micropython
`pip install utinyec`
In Thonny you can find it in Tools -> Manage Packages, then search for utinyec.
## usage
```python
#Must be micropython, not regular python.
print('please be patient, this may take a while...')
#init:
from utinyec.cryptography import uECcrypto
specified_private_key_int = None #use a previous alices_session.get_private_key_int() in here to use the same key pair
curve_name = 'secp256r1' #see registry.py for a list of curve names
alices_session = uECcrypto(curve_name, specified_private_key_int) # derive_shared_secret AES256 keys are
print('alices_session is ready...')
#now we will generate a public_key position (x and y coordinates) which will represent the "other" public key being given to us
bobs_session = uECcrypto(curve_name)
print('bobs_session is ready...')
#exchange keys over network, see examples below
alices_public_key = alices_session.get_public_key()
bobs_public_key = bobs_session.get_public_key()
print("bob and alice exchange public keys...")
#temporary keypair example:
#from utinyec import registry as reg
#from utinyec import ec as tinyec
#curve = reg.get_curve(curve_name)
#keypair, bobs_public_key = tinyec.make_keypair(curve)
#
#this is one-sided, since we didn't really make an actual class. Not really that useful, but shows uECcrypto can be used as a component of a larger project from exposed api calls.
#encrypted_cbc = alices_session.encrypt(plaintext, bobs_public_key, "CBC")
#decrypted_cbc = alices_session.decrypt(encrypted_cbc, bobs_public_key, "CBC")
#
#example other_public_key formats:
#other_public_key = tinyec.make_public_key()
#
#other_public_key = (24186427586325584408744247601395677827137854066598587263728946626505599356143, 54693647365151360530247677535261819666420276295191767359408775310662222563936)
#
#other_public_key = {"x":24186427586325584408744247601395677827137854066598587263728946626505599356143,
# "y":54693647365151360530247677535261819666420276295191767359408775310662222563936,
# "curve":curve_name} #specifying "curve" is optional, the class will use it's own if none is provided. Curve can be a name or the Curve class object
#
#the dictionary format is particularly useful as it is JSON serializable for network transmission, you can create it with {"x":alices_session.keypair.x,"y":alices_session.keypair.y}
#then just import json (or ujson in micropython) and run ujson.dumps(public_key_dict) then use urequests with networking to send to another microcontroller for key exchange
#remember, this has not been validated and should not be used in production, only as an example to demonstrate how an ECC key exchange could work in a network
#cryptography demonstration
plaintext = 'This is AES cryptographic'.strip() #secret message that alice will send to bob, note that blank spaces will be trimmed due to block padding
print('alice is encrypting her message...')
encrypted_message = alices_session.encrypt(plaintext, bobs_public_key, "CBC") #alices_session will encrypt using the derived shared secret
print('alice has encrypted her message and sends it to bob...')
decrypted_message = bobs_session.decrypt(encrypted_message, alices_public_key, "CBC") #the second alices_session will derive the same shared secret with the other key combination, then decrypt the data
print('bob has decrypted alices message...')
#analysis of results:
print("")
print("RESULT:")
#what alice knows:
print("")
print("alices_session private",alices_session.keypair.priv) #alice shouldn't share this with anyone!
print('bobs_session public:',bobs_public_key)
alices_shared_secret = alices_session.derive_shared_secret(bobs_public_key)
print('alices shared secret:', alices_shared_secret) #this is faster with caching enabled, also alice shouldn't share this with anyone!
#what bob knows:
print("")
print("bobs_session private",bobs_session.keypair.priv) #bob shouldn't share this with anyone!
print('alices_session public:',alices_public_key)
bobs_shared_secret = bobs_session.derive_shared_secret(alices_public_key)
print('bobs shared secret:',bobs_session.derive_shared_secret(alices_public_key) ) #this is faster with caching enabled, also bob shouldn't share this with anyone!
#remember the man in the middle (MITM) knows both public keys, but should never know any private keys...
#in theory with Elliptical Curve Cryptography (ECC), deriving a shared secret from two public keys is not possible. Remember this is an example and should not be used in production.
print("")
print('Alices original message:',plaintext)
print('Encrypted message:', encrypted_message)
print('Alices message decrypted by Bob:', decrypted_message.strip())
assert bobs_shared_secret == alices_shared_secret, "Error: alice and bob derived different shared secrets!"
print("bob and alice have correctly derived the same shared secret")
assert plaintext == decrypted_message.decode('utf-8').strip(), "Error: bob's decrypted message is not the same as what alice sent!"
print("bob has correctly read alice's message")
print("Demonstration complete.")
#caching example
#try toggling off by using enable_public_key_caching=False as an argument in a uECcrypto init or.set_public_key_caching(False)
#if you do use caches (on by default) you can clear the cache by running .clear_public_key_cache()
print("")
print("Demonstrating caching...")
import utime
print("3")
utime.sleep(1)
print("2")
utime.sleep(1)
print("1")
utime.sleep(1)
print("GO")
plaintext2 = 'Alice likes eating apples.'.strip() #secret message that alice will send to bob, note that blank spaces will be trimmed due to block padding
print('alice is encrypting her second message...')
encrypted_message2 = alices_session.encrypt(plaintext2, bobs_public_key, "CBC") #if caching is enabled this will be FAST
print('alice has encrypted her second message and sends it to bob...')
decrypted_message2 = bobs_session.decrypt(encrypted_message2, alices_public_key, "CBC") #if caching is enabled this will be FAST
print('bob has decrypted alices second message...')
assert plaintext2 == decrypted_message2.decode('utf-8').strip(), "Error: bob's decrypted message is not the same as what alice sent!"
print("bob has correctly read alice's second message")
print("Caching demonstration complete.")
```
## PEM formatting
Using regular python (not micropython) you can use the cryptography module to convert from coordinate to standard PEM, or reverse.
Conversion may be useful when communicating with non-microcontroller devices, as devices using regular python can convert public keys to coordinates before sending them.
I do not know of a method to convert formats within micropython, and such a method is outside my personal use case for this project. Send the github repository a pull request if you write one!
### converting to and from PEM format
```python
#REGULAR python, NOT micropython
#I provide some public key coordinates for this example, but you should derive your own from the uECCrypto class
public_key_coordinates = (27080695663519936575286139140947921079432612852248858477930157300769994068404, 89650813448058425836500999002714743992773189021923677962194438343503940101997)
#the previous line is an EXAMPLE set of coordinates, you should use your own, see the next couple of lines which shows you where to get them
#public_key_coordinates = (ecc_session.keypair.pub.x, ecc_session.keypair.pub.y)
#public_key_coordinates = ecc_session.get_public_key()
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
def get_pem_from_ecc_coordinates(public_key_coordinates):
#define curve SECP256R1 (for example)
curve = ec.SECP256R1()
x_coordinate, y_coordinate = public_key_coordinates
#create public key object
ec_public_key = ec.EllipticCurvePublicNumbers(x_coordinate, y_coordinate, curve).public_key()
#serialize public key to PEM format
return ec_public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
def get_ecc_coordinates_from_pem(pem_data):
# deserialize PEM data to public key object
ec_public_key = serialization.load_pem_public_key(pem_data)
# extract public numbers from the public key
public_numbers = ec_public_key.public_numbers()
# return x and y coordinates
return (public_numbers.x, public_numbers.y)
#USAGE:
print("original coordinates:", public_key_coordinates)
#convert to PEM
pem = get_pem_from_ecc_coordinates(public_key_coordinates)
print("derived PEM:", pem.decode('utf-8')) #decode bytes to string format
#and convert back to coordinates
processed_public_key_coordinates = get_ecc_coordinates_from_pem(pem) #pem is given as bytes by the way, not a string!
print("derived coordinates:", public_key_coordinates)
```
Raw data
{
"_id": null,
"home_page": "https://github.com/JackLawrenceCRISPR/utinyec",
"name": "utinyec",
"maintainer": null,
"docs_url": null,
"requires_python": null,
"maintainer_email": null,
"keywords": "elliptic, curves, crypto, tls, ssl, ecdhe, diffie-hellman",
"author": "Jack Lawrence",
"author_email": "JackLawrenceCRISPR@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/a4/08/737fc0858851b3a8396199029b5256df651bde72ebe48f4d71911a46c4c4/utinyec-0.4.5.tar.gz",
"platform": null,
"description": "# utinyec\r\nA tiny library to perform potentially unsafe cryptography with arithmetic operations on elliptic curves in pure micropython. No dependencies.\r\n\r\n**This is not a library suitable for production.** It is useful for security professionals to understand the inner workings of EC, and be able to play with pre-defined curves.\r\nNo really, this module has not been tested for vulnerabilities. It should never be used in production. I am not accountable for what you choose to do with this module.\r\n\r\nutinyec shows the mathematics behind eliptical curve cryptography (ECC) in pure python which is useful for educational purposes. C based solutions with python API can be compiled from these two libraries: [ucryptography](https://github.com/dmazzella/ucryptography) or [ucrypto](https://github.com/dmazzella/ucrypto), they use C for the cryptography which is MUCH faster than pure python.\r\n\r\nIf you want to convert this from micropython to regular python then change \"from uos import urandom\" to \"from os import urandom\" in both ec.py AND cryptography.py, then change 'from ucryptolib import aes' to 'from cryptolib import aes' in cryptography.py. \r\nThis package is very slow, and possibly unsafe as it has not been audited; if you need a cryptography solution in regular python please use pip install cryptography, or if you are using android you need to use 'apt install python-cryptography' as the rust dependency is dropped for functionality in Termux.\r\n\r\n## installation in Micropython\r\n`pip install utinyec` \r\nIn Thonny you can find it in Tools -> Manage Packages, then search for utinyec.\r\n\r\n## usage\r\n```python\r\n#Must be micropython, not regular python.\r\nprint('please be patient, this may take a while...')\r\n\r\n#init:\r\nfrom utinyec.cryptography import uECcrypto\r\nspecified_private_key_int = None #use a previous alices_session.get_private_key_int() in here to use the same key pair\r\ncurve_name = 'secp256r1' #see registry.py for a list of curve names\r\nalices_session = uECcrypto(curve_name, specified_private_key_int) # derive_shared_secret AES256 keys are \r\nprint('alices_session is ready...')\r\n\r\n#now we will generate a public_key position (x and y coordinates) which will represent the \"other\" public key being given to us\r\nbobs_session = uECcrypto(curve_name)\r\nprint('bobs_session is ready...')\r\n\r\n#exchange keys over network, see examples below\r\nalices_public_key = alices_session.get_public_key()\r\nbobs_public_key = bobs_session.get_public_key()\r\nprint(\"bob and alice exchange public keys...\")\r\n\r\n#temporary keypair example:\r\n#from utinyec import registry as reg\r\n#from utinyec import ec as tinyec\r\n#curve = reg.get_curve(curve_name)\r\n#keypair, bobs_public_key = tinyec.make_keypair(curve) \r\n#\r\n#this is one-sided, since we didn't really make an actual class. Not really that useful, but shows uECcrypto can be used as a component of a larger project from exposed api calls.\r\n#encrypted_cbc = alices_session.encrypt(plaintext, bobs_public_key, \"CBC\")\r\n#decrypted_cbc = alices_session.decrypt(encrypted_cbc, bobs_public_key, \"CBC\")\r\n#\r\n\r\n#example other_public_key formats:\r\n#other_public_key = tinyec.make_public_key()\r\n#\r\n#other_public_key = (24186427586325584408744247601395677827137854066598587263728946626505599356143, 54693647365151360530247677535261819666420276295191767359408775310662222563936)\r\n#\r\n#other_public_key = {\"x\":24186427586325584408744247601395677827137854066598587263728946626505599356143,\r\n# \"y\":54693647365151360530247677535261819666420276295191767359408775310662222563936,\r\n# \"curve\":curve_name} #specifying \"curve\" is optional, the class will use it's own if none is provided. Curve can be a name or the Curve class object\r\n#\r\n#the dictionary format is particularly useful as it is JSON serializable for network transmission, you can create it with {\"x\":alices_session.keypair.x,\"y\":alices_session.keypair.y}\r\n#then just import json (or ujson in micropython) and run ujson.dumps(public_key_dict) then use urequests with networking to send to another microcontroller for key exchange\r\n#remember, this has not been validated and should not be used in production, only as an example to demonstrate how an ECC key exchange could work in a network\r\n\r\n\r\n#cryptography demonstration\r\nplaintext = 'This is AES cryptographic'.strip() #secret message that alice will send to bob, note that blank spaces will be trimmed due to block padding\r\nprint('alice is encrypting her message...')\r\nencrypted_message = alices_session.encrypt(plaintext, bobs_public_key, \"CBC\")\t#alices_session will encrypt using the derived shared secret\r\nprint('alice has encrypted her message and sends it to bob...')\r\ndecrypted_message = bobs_session.decrypt(encrypted_message, alices_public_key, \"CBC\")\t#the second alices_session will derive the same shared secret with the other key combination, then decrypt the data\r\nprint('bob has decrypted alices message...')\r\n\r\n#analysis of results:\r\nprint(\"\")\r\nprint(\"RESULT:\")\r\n#what alice knows:\r\nprint(\"\")\r\nprint(\"alices_session private\",alices_session.keypair.priv) #alice shouldn't share this with anyone!\r\nprint('bobs_session public:',bobs_public_key)\r\nalices_shared_secret = alices_session.derive_shared_secret(bobs_public_key)\r\nprint('alices shared secret:', alices_shared_secret) #this is faster with caching enabled, also alice shouldn't share this with anyone!\r\n\r\n#what bob knows:\r\nprint(\"\")\r\nprint(\"bobs_session private\",bobs_session.keypair.priv) #bob shouldn't share this with anyone!\r\nprint('alices_session public:',alices_public_key)\r\nbobs_shared_secret = bobs_session.derive_shared_secret(alices_public_key)\r\nprint('bobs shared secret:',bobs_session.derive_shared_secret(alices_public_key) ) #this is faster with caching enabled, also bob shouldn't share this with anyone!\r\n\r\n#remember the man in the middle (MITM) knows both public keys, but should never know any private keys...\r\n#in theory with Elliptical Curve Cryptography (ECC), deriving a shared secret from two public keys is not possible. Remember this is an example and should not be used in production.\r\n\r\nprint(\"\")\r\nprint('Alices original message:',plaintext)\r\nprint('Encrypted message:', encrypted_message)\r\nprint('Alices message decrypted by Bob:', decrypted_message.strip())\r\n\r\nassert bobs_shared_secret == alices_shared_secret, \"Error: alice and bob derived different shared secrets!\"\r\nprint(\"bob and alice have correctly derived the same shared secret\")\r\nassert plaintext == decrypted_message.decode('utf-8').strip(), \"Error: bob's decrypted message is not the same as what alice sent!\"\r\nprint(\"bob has correctly read alice's message\")\r\nprint(\"Demonstration complete.\")\r\n\r\n\r\n#caching example\r\n#try toggling off by using enable_public_key_caching=False as an argument in a uECcrypto init or.set_public_key_caching(False)\r\n#if you do use caches (on by default) you can clear the cache by running .clear_public_key_cache()\r\nprint(\"\")\r\nprint(\"Demonstrating caching...\")\r\nimport utime\r\nprint(\"3\")\r\nutime.sleep(1)\r\nprint(\"2\")\r\nutime.sleep(1)\r\nprint(\"1\")\r\nutime.sleep(1)\r\nprint(\"GO\")\r\n\r\nplaintext2 = 'Alice likes eating apples.'.strip() #secret message that alice will send to bob, note that blank spaces will be trimmed due to block padding\r\nprint('alice is encrypting her second message...')\r\nencrypted_message2 = alices_session.encrypt(plaintext2, bobs_public_key, \"CBC\")\t#if caching is enabled this will be FAST\r\nprint('alice has encrypted her second message and sends it to bob...')\r\ndecrypted_message2 = bobs_session.decrypt(encrypted_message2, alices_public_key, \"CBC\")\t#if caching is enabled this will be FAST\r\nprint('bob has decrypted alices second message...')\r\nassert plaintext2 == decrypted_message2.decode('utf-8').strip(), \"Error: bob's decrypted message is not the same as what alice sent!\"\r\nprint(\"bob has correctly read alice's second message\")\r\nprint(\"Caching demonstration complete.\")\r\n```\r\n\r\n\r\n## PEM formatting\r\nUsing regular python (not micropython) you can use the cryptography module to convert from coordinate to standard PEM, or reverse.\r\nConversion may be useful when communicating with non-microcontroller devices, as devices using regular python can convert public keys to coordinates before sending them.\r\nI do not know of a method to convert formats within micropython, and such a method is outside my personal use case for this project. Send the github repository a pull request if you write one!\r\n\r\n### converting to and from PEM format\r\n```python\r\n#REGULAR python, NOT micropython\r\n#I provide some public key coordinates for this example, but you should derive your own from the uECCrypto class\r\npublic_key_coordinates = (27080695663519936575286139140947921079432612852248858477930157300769994068404, 89650813448058425836500999002714743992773189021923677962194438343503940101997)\r\n#the previous line is an EXAMPLE set of coordinates, you should use your own, see the next couple of lines which shows you where to get them\r\n#public_key_coordinates = (ecc_session.keypair.pub.x, ecc_session.keypair.pub.y)\r\n#public_key_coordinates = ecc_session.get_public_key()\r\n\r\nfrom cryptography.hazmat.primitives import serialization\r\nfrom cryptography.hazmat.primitives.asymmetric import ec\r\n\r\ndef get_pem_from_ecc_coordinates(public_key_coordinates):\r\n #define curve SECP256R1 (for example)\r\n curve = ec.SECP256R1()\r\n x_coordinate, y_coordinate = public_key_coordinates\r\n\r\n #create public key object\r\n ec_public_key = ec.EllipticCurvePublicNumbers(x_coordinate, y_coordinate, curve).public_key()\r\n\r\n #serialize public key to PEM format\r\n return ec_public_key.public_bytes(\r\n encoding=serialization.Encoding.PEM,\r\n format=serialization.PublicFormat.SubjectPublicKeyInfo\r\n )\r\n\r\ndef get_ecc_coordinates_from_pem(pem_data):\r\n # deserialize PEM data to public key object\r\n ec_public_key = serialization.load_pem_public_key(pem_data)\r\n \r\n # extract public numbers from the public key\r\n public_numbers = ec_public_key.public_numbers()\r\n \r\n # return x and y coordinates\r\n return (public_numbers.x, public_numbers.y)\r\n\r\n\r\n\r\n#USAGE:\r\nprint(\"original coordinates:\", public_key_coordinates)\r\n\r\n#convert to PEM\r\npem = get_pem_from_ecc_coordinates(public_key_coordinates)\r\nprint(\"derived PEM:\", pem.decode('utf-8')) #decode bytes to string format\r\n\r\n#and convert back to coordinates\r\nprocessed_public_key_coordinates = get_ecc_coordinates_from_pem(pem) #pem is given as bytes by the way, not a string!\r\nprint(\"derived coordinates:\", public_key_coordinates)\r\n```\r\n",
"bugtrack_url": null,
"license": "aGPLv3",
"summary": "A tiny library to perform unverified elliptic curve cryptography (ECC) with arithmetic operations in pure micropython. Not security verified for production.",
"version": "0.4.5",
"project_urls": {
"Homepage": "https://github.com/JackLawrenceCRISPR/utinyec"
},
"split_keywords": [
"elliptic",
" curves",
" crypto",
" tls",
" ssl",
" ecdhe",
" diffie-hellman"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "2ddd0b773041252413c369ecce341614c9c7200e23830ffa02d43922f5c15506",
"md5": "a8cc00e69fc1f739407be030e7386e98",
"sha256": "13c8d688bc24878a22ce0e5da96eea700ede8a5ef868c46ebf4f6f046f82bd0b"
},
"downloads": -1,
"filename": "utinyec-0.4.5-py3-none-any.whl",
"has_sig": false,
"md5_digest": "a8cc00e69fc1f739407be030e7386e98",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 30148,
"upload_time": "2024-12-15T15:01:23",
"upload_time_iso_8601": "2024-12-15T15:01:23.288706Z",
"url": "https://files.pythonhosted.org/packages/2d/dd/0b773041252413c369ecce341614c9c7200e23830ffa02d43922f5c15506/utinyec-0.4.5-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "a408737fc0858851b3a8396199029b5256df651bde72ebe48f4d71911a46c4c4",
"md5": "14295c7bf6629f6a68af0e0a46d88b58",
"sha256": "52e7915f89450fe3b0e9dec7188545c8c878652746929214dcbd1af59ad2916f"
},
"downloads": -1,
"filename": "utinyec-0.4.5.tar.gz",
"has_sig": false,
"md5_digest": "14295c7bf6629f6a68af0e0a46d88b58",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 33787,
"upload_time": "2024-12-15T15:01:25",
"upload_time_iso_8601": "2024-12-15T15:01:25.133260Z",
"url": "https://files.pythonhosted.org/packages/a4/08/737fc0858851b3a8396199029b5256df651bde72ebe48f4d71911a46c4c4/utinyec-0.4.5.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-12-15 15:01:25",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "JackLawrenceCRISPR",
"github_project": "utinyec",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "utinyec"
}