Professional Documents
Culture Documents
PS-IV Practical 1 - Merged
PS-IV Practical 1 - Merged
Aim: T o l
earnbasicsofcryptograph y by creatingmessage digestusingSH A - 2 5 6 .
Theory : SHA-256
All hashing algorithms are:
• Mathematical. Strict rules underlie the work an algorithm does, and those rules can’t be broken or
adjusted.
• Uniform. Choose one type of hashing algorithm, and data of any character count put through the system
will emerge at a length predetermined by the program.
• Consistent. The algorithm does just one thing (compress data) and nothing else.
• One way. Once transformed by the algorithm, it’s nearly impossible to revert the data to its original
state.
A cryptographic hash function takes an arbitrary block of data and calculates a fixed-size bit string (a digest),
such that different data results (with a high probability) in different digests.
classcryptography.hazmat.primitives.hashes.SHA256
SHA-256 is a cryptographic hash function from the SHA-2 family and is standardized by NIST. It produces a
256-bit message digest.
Program:
#Hashing.py
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
digest = hashes.Hash(hashes.SHA256(), backend=default_backend())
digest.update(b"123")
digest.update(b"456")
digest.update(b"789")
digest.update(b"abc")
digest.update(b"xy2")
hash = digest.finalize()
print(hash)
Output:
Conclusion: Thus we have successfully created message digest for given messages and found out
that message digest of same messages is same but a small change in the message completely changes the
message digest.
Practical 2
A digital signature is a mathematical technique used to validate the authenticity and integrity of a message,
software, or digital document.
Key Generation Algorithms: Digital signature is electronic signatures, which assure that the
message was sent by a particular sender. While performing digital transactions authenticity and integrity
should be assured, otherwise, the data can be altered or someone can also act as if he was the sender and
expect a reply.
Signing Algorithms: To create a digital signature, signing algorithms like email programs create a
one-way hash of the electronic data which is to be signed. The signing algorithm then encrypts the hash value
using the private key (signature key). This encrypted hash along with other information like the hashing
algorithm is the digital signature. This digital signature is appended with the data and sent to the verifier. The
reason for encrypting the hash instead of the entire message or document is that a hash function converts any
arbitrary input into a much shorter fixed-length value. This saves time as now instead of signing a long
message a shorter hash value has to be signed and moreover hashing is much faster than signing.
Signature Verification Algorithms : Verifier receives Digital Signature along with the data. It then
uses Verification algorithm to process on the digital signature and the public key (verification key) and
generates some value. It also applies the same hash function on the received data and generates a hash value.
Then the hash value and the output of the verification algorithm are compared. If they both are equal, then the
digital signature is valid else it is invalid.
Program:
#Signatures.py
def generate_keys():
private = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
public = private.public_key()
return private, public
# Creating a signature
# Verification of Strings
def verify(message, sig, public) :
message = bytes(str(message), 'utf-8')
try:
retval = public.verify(
sig,
message,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
return True
except InvalidSignature:
return False
except:
print("Error Executing public_key.verify")
return False
# Main Function
if __name__ == '__main__' :
pr, pu = generate_keys()
print(pr)
print(pu)
message = "This is a secret message"
sig = sign(message, pr)
print(sig)
correct = verify(message, sig, pu)
print(correct)
if correct:
print("Success! Good Sig")
else:
print("Error! Signature is bad")
Output:
Conclusion: Thus we have successfully created digital signature and verified it.
Practical 3
Theory: Blockchain
Blocks are data structures within the blockchain database, where transaction data in a
the most recent transactions not yet validated by the network. Once the data are
validated, the block is closed. Then, a new block is created for new transactions to be
altered or removed.
Blocks and the information within them must be verified by a network before
A Merkle tree totals all transactions in a block and generates a digital fingerprint of the entire set of
operations, allowing the user to verify whether it includes a transaction in the block.
Merkle trees are made by hashing pairs of nodes repeatedly until only one hash remains; this
hash is known as the Merkle Root or the Root Hash.
They're built from the bottom, using Transaction IDs, which are hashes of individual
transactions.
Each non-leaf node is a hash of its previous hash, and every leaf node is a hash of
transactional data.
Consider the following scenario: A, B, C, and D are four transactions, all executed on the same
block. Each transaction is then hashed, leaving you with:
Hash A
Hash B
Hash C
Hash D
The hashes are paired together, resulting in:
Hash AB
and
Hash CD
And therefore, your Merkle Root is formed by combining these two hashes: Hash ABCD.
Program:
class CBlock :
data = None
previousHash = None
previousBlock = None
self.data = data
self.previousBlock = previousBlock
if previousBlock != None:
self.previousHash = previousBlock.computeHash()
def computeHash(self) :
default_backend() )
digest.update(bytes(str(self.data),'utf8'))
digest.update(bytes(str(self.previousHash),'utf8'))
return digest.finalize()
if __name__=='__main__' :
B3 = CBlock(12345, B1)
n=0
n=n+1
if b.previousBlock.computeHash() == b.previousHash:
else:
print ("ERROR! Hash is not equal.")
Output:
Practical 4
A digital signature is a mathematical technique used to validate the authenticity and integrity of a
message, software, or digital document.
Program:
#Testing.py
import Signatures
pr, pu = Signatures.generate_keys()
message = "This is a secret message"
sig = Signatures.sign(message, pr)
Output:
Conclusion: Thus we have successfully tested the digital signature against hackers using Fruad
Signature and Tampered Message and found that both the Fruad Signature and Tampered Message are
detected during verification of signature.
Practical 5
Program:
#Transaction.py
import Signatures
#Signatures.sign
#Signatures.verify
class Tx:
inputs = None
outputs =None
sigs = None
reqd = None
def __init__(self):
self.inputs = []
self.outputs = []
self.sigs = []
self.reqd = []
def add_input(self, from_addr, amount):
self.inputs.append((from_addr, amount))
def add_output(self, to_addr, amount):
self.outputs.append((to_addr, amount))
def add_reqd(self, addr):
self.reqd.append(addr)
def sign(self, private):
message = self.__gather()
newsig = Signatures.sign(message, private)
self.sigs.append(newsig)
def is_valid(self):
total_in = 0
total_out = 0
message = self.__gather()
for addr,amount in self.inputs:
found = False
for s in self.sigs:
if Signatures.verify(message, s, addr) :
found = True
if not found:
print ("Reason for Failure: No good sig found for given message")
return False
if amount < 0:
return False
total_in = total_in + amount
for addr in self.reqd:
found = False
for s in self.sigs:
if Signatures.verify(message, s, addr) :
found = True
if not found:
return False
for addr,amount in self.outputs:
if amount < 0:
return False
total_out = total_out + amount
if total_out > total_in:
print("Reason for Failure: Outputs exceed inputs")
return False
return True
def __gather(self):
data=[]
data.append(self.inputs)
data.append(self.outputs)
data.append(self.reqd)
return data
Theory : Hash
Each block also includes a Hash- a unique identifier for the block and all of its contents. To better understand,
you can assume a hash to be equivalent to a fingerprint. It is always unique, and no two blocks can have the
same hash, just like in the case of fingerprints. As soon as a block is created, its hash gets generated
simultaneously. Tampering with a block changes its hash. Simply put, if fingerprints or hash of the block
change, it indicates that the block has been tampered with and is no longer the same block. So hash can be a
very powerful tool to detect any changes made in the block.
Another important element that every block contains is the hash of the previous block. This piece of
information is what links one block to another and makes the whole network safe and secure.
In the figure, block 4 contains the hash of block 3, block 3 contains the hash of block 2, and so on. As
discussed earlier, any change in the data of a block leads to a change in its hash. In the given figure, when
we change the data in block 2, the hash of block 2 gets changed as well, which makes the whole blockchain
unstable. This happens because each block contains hash of the previous block. When block 2 is tampered
with, the old hash becomes invalid, and a new hash is generated for the block. This affects all the subsequent
blocks in the chain and thus making all of them invalid. This unique property of the blockchain makes it
transparent and secure, as in any case of data tampering whole network gets to know which block got
compromised in the blockchain. Hash of the previous block links the block to another block in the Blockchain
Program:
#Blockchain.py
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
class someClass :
string = None
num = 328965
def __init__(self, mystring) :
self.string = mystring
def __repr__(self) :
return self.string + "^^^" + str(self.num)
class CBlock :
data = None
previousHash = None
previousBlock = None
def __init__(self, data, previousBlock) :
self.data = data
self.previousBlock = previousBlock
if previousBlock != None:
self.previousHash = previousBlock.computeHash()
def computeHash(self) :
digest = hashes.Hash(hashes.SHA256(), backend = default_backend() )
digest.update(bytes(str(self.data),'utf8'))
digest.update(bytes(str(self.previousHash),'utf8'))
return digest.finalize()
if __name__=='__main__' :
root = CBlock('I am root', None)
B1 = CBlock(b'I am a child', root)
B2 = CBlock('I am B1s Brother', root)
B3 = CBlock(12345, B1)
B4 = CBlock(someClass('Hi there!'), B3)
B5 = CBlock("Top Block", B4)
n=0
for b in [B1, B2, B3, B4, B5] :
n=n+1
if b.previousBlock.computeHash() == b.previousHash:
print("Data in block ",n, " = ", b.data)
print ("Success! Hash is good.")
else:
print ("ERROR! Hash is not good.")
Output:
Conclusion: Thus we have found out that any change in the data of a block leads to a change in its
hash, if fingerprints or hash of the block change, it indicates that the block has been tampered with and is no
longer the same block, which ultimately makes the whole blockchain unstable.
Practical 7
Theory :
● Here, we import Signatures.py to use the function generate_keys() through which we generate four public
and private key pairs for testing transactions.
● We also import Transactions.py to create transaction instances Tx1 and Tx2, to add input using add_input()
function, to add output using add_output function, to sign a message after gathering all the information of
the message we use sign() function.
● Thus, we create a transaction by first creating instance of a transaction, then by adding input (senders
public key, amount to be sent), by adding output (receivers public key, amount to be received) and lastly
by adding signature of sender.
● The validity of the transaction is tested by is_valid() function from Transactions.py.
Program :
#Test 7.py
#testing transactions with wrong signature
#testing transactions with multiple inputs but only one signature.
import Signatures
import Transactions
if __name__ == "__main__":
pr1, pu1 = Signatures.generate_keys()
pr2, pu2 = Signatures.generate_keys()
pr3, pu3 = Signatures.generate_keys()
pr4, pu4 = Signatures.generate_keys()
# Wrong signature
Tx5 = Transactions.Tx()
Tx5.add_input(pu1, 1)
Tx5.add_output(pu2, 1)
Tx5.sign(pr2)
n=0
for t in [Tx1, Tx2, Tx3, Tx4, Tx5]:
n=n+1
print("")
print ("Output for transaction", n)
if t.is_valid():
print("Success! Tx is valid")
else:
print("ERROR! Tx is invalid")
Output :
Conclusion: Thus we have found out that, by testing transactions with wrong signature and with
Theory :
● Here, we import Signatures.py to use the function generate_keys() through which we generate four public
and private key pairs for testing transactions.
● We also import Transactions.py to create transaction instances Tx1 and Tx2, to add input using add_input()
function, to add output using add_output function, to sign a message after gathering all the information of
the message we use sign() function.
● Thus, we create a transaction by first creating instance of a transaction, then by adding input (senders
public key, amount to be sent), by adding output (receivers public key, amount to be received) and lastly
by adding signature of sender.
● The validity of the transaction is tested by is_valid() function from Transactions.py.
Program:
#Test10.py
#testing negative
#testing modified transactions
import Signatures
import Transactions
if __name__ == "__main__":
pr1, pu1 = Signatures.generate_keys()
pr2, pu2 = Signatures.generate_keys()
pr3, pu3 = Signatures.generate_keys()
pr4, pu4 = Signatures.generate_keys()
# Negative values
Tx1 = Transactions.Tx()
Tx1.add_input(pu2, -1)
Tx1.add_output(pu1, 1)
Tx1.sign(pr2)
# Negative values
Tx2 = Transactions.Tx()
Tx2.add_input(pu2, 1)
Tx2.add_output(pu1, -1)
Tx2.sign(pr2)
# Modified Tx
Tx3 = Transactions.Tx()
Tx3.add_input(pu1, 1)
Tx3.add_output(pu2, 1)
Tx3.sign(pr1)
# outputs = [(pu2,1)]
# change to [(pu3,1)]
Tx3.outputs[0] = (pu3,1)
# Modified Tx
Tx4 = Transactions.Tx()
Tx4.add_input(pu2, 5)
Tx4.add_output(pu3, 5)
Tx4.sign(pr2)
# inputs = [(pu2,1)]
# change to [(pu3,1)]
Tx4.inputs[0] = (pu2,10)
n=0
for t in [Tx1, Tx2, Tx3, Tx4]:
n=n+1
print("")
print ("Output for transaction", n)
if t.is_valid():
print("ERROR! Bad Transaction is valid")
else:
print("Success! Bad Transaction is invalid")
Output:
Practical 9
ESCROW Transaction: An Escrow is an arrangement for a third party to hold the assets of a transaction
temporarily. The assets are kept in a third-party account and are only released when all terms of
the agreement have been met. The use of an escrow account in a transaction adds a degree of safety for both
parties.
The main purpose of an escrow is to ensure that everybody sticks to their end of the bargain. It can be
seen as a mediator of the transaction. It asserts that the transfer of assets only happens when all the
obligations of the transaction have been met.
Escrow is commonly used for contracts that have the following characteristics:
● A large transaction value – such as the purchase of a home
● The buyer needs to confirm the quality of work before payment
● The seller doesn’t want to undertake massive amounts of work without assurance of payment.
Program:
#Test 09.py
#testing transactions for output exceeding input
import Signatures
import Transactions
if __name__ == "__main__":
pr1, pu1 = Signatures.generate_keys()
pr2, pu2 = Signatures.generate_keys()
pr3, pu3 = Signatures.generate_keys()
pr4, pu4 = Signatures.generate_keys()
n=0
for t in [Tx1, Tx2, Tx3]:
n=n+1
print("")
print ("Output for transaction", n)
if t.is_valid():
print("ERROR! Bad Transaction is valid")
else:
print("Success! Bad Transaction is invalid")
Output:
Practical 10
Theory :
●
● Here, we import Signatures.py to use the function generate_keys() through which we generate four public
and private key pairs for testing transactions.
● We also import Transactions.py to create transaction instances Tx1 and Tx2, to add input using add_input()
function, to add output using add_output function, to sign a message after gathering all the information of
the message we use sign() function.
● Thus, we create a transaction by first creating instance of a transaction, then by adding input (senders
public key, amount to be sent), by adding output (receivers public key, amount to be received) and lastly
by adding signature of sender.
● The validity of the transaction is tested by is_valid() function from Transactions.py.
Program:
#Test10.py
#testing transactions for output exceeding input
import Signatures
import Transactions
if __name__ == "__main__":
pr1, pu1 = Signatures.generate_keys()
pr2, pu2 = Signatures.generate_keys()
pr3, pu3 = Signatures.generate_keys()
pr4, pu4 = Signatures.generate_keys()
n=0
for t in [Tx1, Tx2]:
n=n+1
print("")
print ("Output for transaction", n)
if t.is_valid():
print("ERROR! Bad Transaction is valid")
else:
print("Success! Bad Transaction is invalid")
Output: