Hands-On Blockchain for Python Developers
上QQ阅读APP看书,第一时间看更新

Signing data in blockchain

In blockchain, we use two keys to sign data, to authenticate a message and protect it from being altered by unauthorized users. The two keys are as follows:

  • Private key
  • Public key

The secrecy of the private key is guarded and it is not made known to the public. On the other hand, you let the public key be given out in public. You tell everyone, hey, this is my public key.

Let's generate the private key. To do this, we need openssl software. You can install this by doing the following:

$ sudo apt-get install openssl

So, Nelson generates the private key, which is the nelsonkey.pem file. He must keep this key secret. It is generated as follows:

$ openssl genrsa -out nelsonkey.pem 1024

From the private key, Nelson generates the public key:

$ openssl rsa -in nelsonkey.pem -pubout > nelsonkey.pub

Nelson can share this public key, nelsonkey.pub, with everyone. Now, in the real world we could set up a simple dictionary of the public key and its owner as follows:

{
'Nelson': 'nelsonkey.pub',
'Marie': 'mariekey.pub',
'Sky': 'skykey.pub'
}

We will now look at how Nelson can prove that he is the only one who can make changes to his history.

First, let's create a Python virtual environment:

$ python3 -m venv blockchain
$ source blockchain/bin/activate
(blockchain) $

Next, install the library:

(blockchain) $ pip install --upgrade pip
(blockchain) $ pip install wheel (blockchain) $ pip install cryptography

This is the Python script that can be used to sign the message. Name this script verify_message.py (refer to the code file in the following GitLab link for the full code: https://gitlab.com/arjunaskykok/hands-on-blockchain-for-python-developers/blob/master/chapter_01/verify_message.py):

from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization

# Generate private key
#private_key = rsa.generate_private_key(
# public_exponent=65537,
# key_size=2048,
# backend=default_backend()
#)
...
...

# Message validation executed by other people
public_key.verify(
signature,
message,
padding.PSS(mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH),
hashes.SHA256())

When executing this script, nothing will happen, as expected. This means that the message is verified with the signature from the public key. The signature can only be created by Nelson because you need the private key in order to create a signature. However, to verify the message with the signature, you only need the public key.

Let's take a look at a case in which Marie tries to falsify the facts with a script named falsify_message.py. Marie tries to put Nelson hates cat in the history database as follows:

from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization

message = b'Nelson hates cat'
signature = b'Fake Signature'

with open("nelsonkey.pub", "rb") as key_file:
public_key = serialization.load_pem_public_key(
key_file.read(),
backend=default_backend())

public_key.verify(
signature,
message,
padding.PSS(mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH),
hashes.SHA256())

Here's how the verify method works. Nelson calculates the hash from the message, then encrypts it with his private key. The result is the signature. For example, if Sky wants to verify the signature, he has the message and the signature. He calculates the hash of the message. Then, he decrypts the signature using the public key. The result is compared to the hash of the message. If it is the same, then everything is well. If not, either the message has been altered or the private key used to sign the message is different.

When doing this, you would get the following output:

So, what does the signature look like? Go back to verify_message.py and append this line to the end of the file. Then, run the script again:

print(signature)

The signature looks like this:

Every message has a different signature, and it's impossible for Marie to guess the signature in order to falsify the message. So, with the private key and the public key, we can verify whether or not the message is indeed from someone authorized, even if we communicate on an unsecured channel.

So with the private key, Nelson could create a signature that is unique to the message it tries to sign:

Everyone in the world who has Nelson's public key can verify that Nelson did indeed write Message A. Nelson can prove he did write Message A by showing Signature A. Everyone can take those two inputs and verify the truth:

So, to validate whether or not it is Nelson who wrote Nelson likes cat, input the following (refer to the code file in the following GitLab link for the full code: https://gitlab.com/arjunaskykok/hands-on-blockchain-for-python-developers/blob/master/chapter_01/validate_message.py): 

# validate_message.py
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization

def fetch_public_key(user):
with open(user + "key.pub", "rb") as key_file:
public_key = serialization.load_pem_public_key(
key_file.read(),
backend=default_backend())
return public_key

# Message coming from user
message = b"Nelson likes cat"

# Signature coming from user, this is very specific to public key.
# Download the public key from Gitlab repository of this code so this signature matches the message.
# Otherwise, you should generate your own signature.
signature =
...
...
padding.PSS(mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH),
hashes.SHA256())