Skip to content
This repository was archived by the owner on Jan 13, 2023. It is now read-only.

Commit bf4915b

Browse files
authored
Merge pull request #294 from lzpap/tutorial6
Add Tutorials `6. Store Encrypted Data` and `7. Fetch Encrypted Data`
2 parents 7635bb9 + 585a453 commit bf4915b

File tree

3 files changed

+323
-2
lines changed

3 files changed

+323
-2
lines changed

docs/tutorials.rst

Lines changed: 215 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@ Tangle.
380380
:lines: 15-30
381381
:lineno-start: 15
382382

383-
Just like in the prevoius example, we will poll for information until we find
383+
Just like in the previous example, we will poll for information until we find
384384
a non-zero balance. :py:meth:`~Iota.get_account_data` without arguments
385385
generates addresses from ``index`` 0 until it finds the first unused. Then, it
386386
queries the node about bundles of those addresses and sums up their balance.
@@ -505,9 +505,222 @@ Once the bundle is confirmed, try rerunning the script from
505505
should be decremented by 1i, and you should see a new address, which was
506506
actually the ``change_address``.
507507

508+
6. Store Encrypted Data
509+
-----------------------
510+
511+
In this example, you will learn how to:
512+
513+
- **Convert Python data structures to JSON format.**
514+
- **Encrypt data and include it in a zero-value transaction.**
515+
- **Store the zero-value transaction with encrypted data on the Tangle.**
516+
517+
.. warning::
518+
519+
We will use the ``simple-crypt`` external library for encryption/decryption.
520+
Before proceeding to the tutorial, make sure you install it by running::
521+
522+
pip install simple-crypt
523+
524+
Code
525+
~~~~
526+
.. literalinclude:: ../examples/tutorials/06_store_encrypted.py
527+
:linenos:
528+
529+
Discussion
530+
~~~~~~~~~~
531+
.. literalinclude:: ../examples/tutorials/06_store_encrypted.py
532+
:lines: 1-18
533+
:lineno-start: 1
534+
535+
We will use the ``encrypt`` method to encipher the data, and ``b64encode`` for
536+
representing it as ASCII characters. ``getpass`` will prompt the user for a
537+
password, and the ``json`` library is used for JSON formatting.
538+
539+
We will need an address to upload the data, therefore we need to supply the
540+
seed to the ``Iota`` API instance. The address will be generated from this
541+
seed.
542+
543+
.. literalinclude:: ../examples/tutorials/06_store_encrypted.py
544+
:lines: 20-26
545+
:lineno-start: 20
546+
547+
The data to be stored is considered confidential information, therefore we
548+
can't just put it on the Tangle as plaintext so everyone can read it. Think of
549+
what would happen if the world's most famous secret agent's identity was leaked
550+
on the Tangle...
551+
552+
.. literalinclude:: ../examples/tutorials/06_store_encrypted.py
553+
:lines: 28-29
554+
:lineno-start: 28
555+
556+
Notice, that ``data`` is a Python ``dict`` object. As a common way of exchanging
557+
data on the web, we would like to convert it to JSON format. The ``json.dumps()``
558+
method does exactly that, and the result is a JSON formatted plaintext.
559+
560+
.. literalinclude:: ../examples/tutorials/06_store_encrypted.py
561+
:lines: 31-40
562+
:lineno-start: 31
563+
564+
Next, we will encrypt this data with a secret password we obtain from the user.
565+
566+
.. note::
567+
568+
When you run this example, please remember the password at least until the
569+
next tutorial!
570+
571+
The output of the ``encrypt`` method is a ``bytes`` object in Python3 and
572+
contains many special characters. This is a problem, since we can only convert
573+
ASCII characters from ``bytes`` directly into :py:class:`TryteString`.
574+
575+
Therefore, we first encode our binary data into ASCII characters with `Base64`_
576+
encoding.
577+
578+
.. literalinclude:: ../examples/tutorials/06_store_encrypted.py
579+
:lines: 42-58
580+
:lineno-start: 42
581+
582+
Now, we are ready to construct the transfer. We convert the encrypted `Base64`_
583+
encoded data to trytes and assign it to the :py:class:`ProposedTransaction`
584+
object's ``message`` argument.
585+
586+
An address is also needed, so we generate one with the help of
587+
:py:meth:`~Iota.get_new_addresses` extended API method. Feel free to choose the
588+
index of the generated address, and don't forget, that the method returns a
589+
``dict`` with a list of addresses, even if it contains only one.
590+
For more detailed explanation on how addresses are generated in PyOTA,
591+
refer to the :ref:`Generating Addresses` page.
592+
593+
We also attach a custom :py:class:`Tag` to our :py:class:`ProposedTransaction`.
594+
Note, that if our ``trytes_encrypted_data`` was longer than the maximum payload
595+
of a transaction, the library would split it accross more transactions that
596+
together form the transfer bundle.
597+
598+
.. literalinclude:: ../examples/tutorials/06_store_encrypted.py
599+
:lines: 60-66
600+
:lineno-start: 60
601+
602+
Finally, we use :py:meth:`Iota.send_transfer` to prepare the transfer and
603+
send it to the network.
604+
605+
Click on the link to check your transaction on the Tangle Explorer.
606+
607+
The tail transaction (a tail transaction is the one with index 0 in the bundle)
608+
hash is printed on the console, because you will need it in the next tutorial,
609+
and anyway, it is a good practice to keep a reference to your transfers.
610+
611+
In the next example, we will try to decode the confidential information from
612+
the Tangle.
613+
614+
7. Fetch Encrypted Data
615+
-----------------------
616+
617+
In this example, you will learn how to:
618+
619+
- **Fetch bundles from the Tangle based on their tail transaction hashes.**
620+
- **Extract messages from a bundle.**
621+
- **Decrypt encrypted messages from a bundle.**
622+
623+
.. warning::
624+
625+
We will use the ``simple-crypt`` external library for encryption/decryption.
626+
Before proceeding to the tutorial, make sure you install it by running::
627+
628+
pip install simple-crypt
629+
630+
Code
631+
~~~~
632+
.. literalinclude:: ../examples/tutorials/07_fetch_encrypted.py
633+
:linenos:
634+
635+
Discussion
636+
~~~~~~~~~~
637+
.. literalinclude:: ../examples/tutorials/07_fetch_encrypted.py
638+
:lines: 1-14
639+
:lineno-start: 1
640+
641+
In contrast to `6. Store Encrypted Data`_ where we intended to encrypt data, in
642+
this tutorial we will do the reverse, and decrypt data from the Tangle.
643+
Therefore, we need the ``decrypt`` method from ``simplecrypt`` library and the
644+
``b64decode`` method from ``base64`` library.
645+
646+
Furthermore, ``getpass`` is needed to prompt the user for a decryption
647+
password, and ``json`` for deserializing JSON formatted string into Python
648+
object.
649+
650+
.. literalinclude:: ../examples/tutorials/07_fetch_encrypted.py
651+
:lines: 16-17
652+
:lineno-start: 16
653+
654+
To fetch transactions or bundles from the Tangle, a reference is required to
655+
retreive them from the network. Transactions are identified by their
656+
transaction hash, while a group of transaction (a bundle) by bundle hash.
657+
Hashes ensure the integrity of the Tangle, since they contain verifiable
658+
information about the content of the transfer objects.
659+
660+
``input()`` asks the user to give the tail transaction hash of the bundle
661+
that holds the encrypted messages. The tail transaction is the first in the
662+
bundle with index 0. Copy and paste the tail transaction hash from the console
663+
output of `6. Store Encrypted Data`_ when prompted.
664+
665+
.. literalinclude:: ../examples/tutorials/07_fetch_encrypted.py
666+
:lines: 19-21
667+
:lineno-start: 19
668+
669+
Next, we fetch the bundle from the Tangle with the help of the
670+
:py:meth:`~Iota.get_bundles` extended API command. It takes a list of tail
671+
transaction hashes and returns the bundles for each of them. The response
672+
``dict`` contains a ``bundles`` key with the value being a list of bundles
673+
in the same order as the input argument hashes. Also note, that the bundles
674+
in the response are actual PyOTA :py:class:`Bundle` objects.
675+
676+
To simplify the code, several operations are happening on line 21:
677+
678+
- Calling :py:meth:`~Iota.get_bundles` that returns a ``dict``,
679+
- accessing the ``'bundles'`` key in the ``dict``,
680+
- and taking the first element of the the list of bundles in the value
681+
associated with the key.
682+
683+
.. literalinclude:: ../examples/tutorials/07_fetch_encrypted.py
684+
:lines: 23-39
685+
:lineno-start: 23
686+
687+
The next step is to extract the content of the message fields of the
688+
transactions in the bundle. We call :py:meth:`Bundle.get_messages` to carry
689+
out this operation. The method returns a list of unicode strings, essentially
690+
the ``signature_message_fragment`` fields of the transactions, decoded from
691+
trytes into unicode characters.
692+
693+
We then combine these message chunks into one stream of characters by using
694+
``string.join()``.
695+
696+
We know that at this stage that we can't make sense of our message, because it
697+
is encrypted and encoded into `Base64`_. Let's peel that onion layer by layer:
698+
699+
- On line 28, we decode the message into bytes with ``b64decode``.
700+
- On line 31, we ask the user for thr decryption password (from the previous
701+
tutorial).
702+
- On line 36, we decrypt the bytes cipher with the password and decode the
703+
result into a unicode string.
704+
- Since we used JSON formatting in the previous tutorial, there is one
705+
additional step to arrive at our original data. On line 39, we deserialize
706+
the JSON string into a Python object, namely a ``dict``.
707+
708+
.. literalinclude:: ../examples/tutorials/07_fetch_encrypted.py
709+
:lines: 41-42
710+
:lineno-start: 41
711+
712+
If everything went according to plan and the user supplied the right password,
713+
we should see our original data printed out to the console.
714+
715+
Now you know how to use the Tangle for data storage while keeping privacy.
716+
When you need more granular access control on how and when one could read
717+
data from the Tangle, consider using `Masked Authenticated Messaging`_ (MAM).
718+
508719
.. _PyOTA Bug Tracker: https://github.com/iotaledger/iota.py/issues
509720
.. _bytestring: https://docs.python.org/3/library/stdtypes.html#bytes
510721
.. _tryte alphabet: https://docs.iota.org/docs/getting-started/0.1/introduction/ternary#tryte-encoding
511722
.. _Tangle Explorer: https://utils.iota.org
512723
.. _Account Module: https://docs.iota.org/docs/client-libraries/0.1/account-module/introduction/overview
513-
.. _spending twice from the same address: https://docs.iota.org/docs/getting-started/0.1/clients/addresses#spent-addresses
724+
.. _spending twice from the same address: https://docs.iota.org/docs/getting-started/0.1/clients/addresses#spent-addresses
725+
.. _Base64: https://en.wikipedia.org/wiki/Base64
726+
.. _Masked Authenticated Messaging: https://docs.iota.org/docs/client-libraries/0.1/mam/introduction/overview?q=masked%20auth&highlights=author;authent
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"""
2+
Encrypt data and store it on the Tangle.
3+
4+
simplecrypt library is needed for this example (`pip install simple-crypt`)!
5+
"""
6+
from iota import Iota, TryteString, Tag, ProposedTransaction
7+
from simplecrypt import encrypt
8+
from base64 import b64encode
9+
from getpass import getpass
10+
11+
import json
12+
13+
# Declare an API object
14+
api = Iota(
15+
adapter='https://nodes.devnet.iota.org:443',
16+
seed=b'YOURSEEDFROMTHEPREVIOUSTUTORIAL',
17+
testnet=True,
18+
)
19+
20+
# Some confidential information
21+
data = {
22+
'name' : 'James Bond',
23+
'age' : '32',
24+
'job' : 'agent',
25+
'address' : 'London',
26+
}
27+
28+
# Convert to JSON format
29+
json_data = json.dumps(data)
30+
31+
# Ask user for a password to use for encryption
32+
password = getpass('Please supply a password for encryption:')
33+
34+
print('Encrypting data...')
35+
# Encrypt data
36+
# Note, that in Python 3, encrypt returns 'bytes'
37+
cipher = encrypt(password, json_data)
38+
39+
# Encode to base64, output contains only ASCII chars
40+
b64_cipher = b64encode(cipher)
41+
42+
print('Constructing transaction locally...')
43+
# Convert to trytes
44+
trytes_encrypted_data = TryteString.from_bytes(b64_cipher)
45+
46+
# Generate an address from your seed to post the transfer to
47+
my_address = api.get_new_addresses(index=42)['addresses'][0]
48+
49+
# Tag is optional here
50+
my_tag = Tag(b'CONFIDENTIALINFORMATION')
51+
52+
# Prepare a transaction object
53+
tx = ProposedTransaction(
54+
address=my_address,
55+
value=0,
56+
tag=my_tag,
57+
message=trytes_encrypted_data,
58+
)
59+
60+
print('Sending transfer...')
61+
# Send the transaction to the network
62+
response = api.send_transfer([tx])
63+
64+
print('Check your transaction on the Tangle!')
65+
print('https://utils.iota.org/transaction/%s/devnet' % response['bundle'][0].hash)
66+
print('Tail transaction hash of the bundle is: %s' % response['bundle'].tail_transaction.hash)
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""
2+
Decrypt data fetched from the Tangle.
3+
4+
simplecrypt library is needed for this example (`pip install simple-crypt`)!
5+
"""
6+
from iota import Iota
7+
from simplecrypt import decrypt
8+
from base64 import b64decode
9+
from getpass import getpass
10+
11+
import json
12+
13+
# Declare an API object
14+
api = Iota('https://nodes.devnet.iota.org:443', testnet=True)
15+
16+
# Prompt user for tail tx hash of the bundle
17+
tail_hash = input('Tail transaction hash of the bundle: ')
18+
19+
print('Looking for bundle on the Tangle...')
20+
# Fetch bundle
21+
bundle = api.get_bundles(tail_hash)['bundles'][0]
22+
23+
print('Extracting data from bundle...')
24+
# Get all messages from the bundle and concatenate them
25+
b64_encrypted_data = "".join(bundle.get_messages())
26+
27+
# Decode from base64
28+
encrypted_data = b64decode(b64_encrypted_data)
29+
30+
# Prompt for passwword
31+
password = getpass('Password to be used for decryption:')
32+
33+
print('Decrypting data...')
34+
# Decrypt data
35+
# decrypt returns 'bytes' in Python 3, decode it into string
36+
json_data = decrypt(password, encrypted_data).decode('utf-8')
37+
38+
# Convert JSON string to python dict object
39+
data = json.loads(json_data)
40+
41+
print('Succesfully decrypted the following data:')
42+
print(data)

0 commit comments

Comments
 (0)