Encryption, SHA Style (Python Lesson 54)

Advertisements

Hello everyone,

Michael here, and in today’s lesson, we’ll explore Python encryption, SHA style.

What is SHA encryption? SHA stands for Secure Hash Algorithm, and it is what’s known as a cryptographic hash function. The SHA algorithms were developed by the US National Security Agency (NSA) and published by the National Institute of Standards and Technology*. SHA is also not a symmetric-key or an asymmetric-key algorithm, as the main purpose of SHA is data security and integrity, not encrypting/decrypting communications.

Now, let’s dive into some SHA coding!

  • *The National Institute of Standards and Technology is an agency under the US Department of Commerce.

Types of SHA hashing functions

There are five different types of SHA hashing functions. Let’s explore each of them:

  • SHA-256-This hashing function will produce a 256-bit hash
  • SHA-384-This hashing function will produce a 384-bit (or 48-byte) hash
  • SHA-224-This hashing function will produce a 224-bit (or 28-byte) hash
  • SHA-512-This hashing function will produce a 512-bit (or 64-byte) hash; because of the long hash output, this is seen as one of the more secure hashing algorithms
  • SHA-1-This was the first SHA hashing function developed by the NSA and produced a 160-bit (or 20-byte) hash; however, this algorithm was found to have many security weaknesses and was thus phased out after 2010

Encrypting text, SHA style

Now that we know about the five different types of hashing functions, let’s see how they work on a text string in Python!

First, let’s import any necessary modules!

import hashlib

The hashlib module will be the only one we’ll need for this post. No need to pip install it since it comes built-in as long as your Python version is 2.5 or higher (and Python 2 has long since been sunset).

Next, let’s set the text we’ll use along with the SHA-256 hash of the text:

text = 'Michaels Programming Bytes: Byte sized programming classes for all coding learners'

SHA256 = hashlib.sha256(text.encode())
print(SHA256.hexdigest())

9854a9d55046c36afafbe47cbaa21ab85c8137d81955c3e344d76156a636a66b

As you see here, we used the name and tagline of this blog for our SHA demonstration. For our SHA encryption, keep these two methods in mind-.encode() and .hexdigest()-as we will use them throughout our demonstrations of the SHA hashing functions.

The .encode() method encodes the text string into a hexadecimal hash while the .hexdigest() method will display the hexadecimal hash.

As you can see here, we have generated a SHA-256 hash from our text with just two lines of code!

Now, if your curious what the decimal number of this hash equals, let’s check it out for fun:

68901140284153216712479841246928981776103555384517524587226404491787878442603

It’s a large, 77-digit number that’s roughly 68 quattuorvigintillion (this is a number followed by 75 zeroes). Quite a big number to come out of that hash!

Next, let’s try the SHA-384 encryption method:

SHA384 = hashlib.sha384(text.encode())
print(SHA384.hexdigest())

2777dfcb92166aa95f0796e3ed4edc12d7da9db3226c8831b40aca5576e8aafa106bb26b3eafe07fc9124238e5e7a6be

This time, we appear to have gotten a larger hash than we did from the SHA-256 function. What could be the decimal number of this hash?

6074720975275609937364706669124284930019665411073401970740713484318034757229843193804707918050212936621238163842750

Now, this number is even larger-it’s approximately 6 septentrigintillion (this is a number followed by 114 zeroes). I honestly never even seen such massive numbers, but as a numbers guy, massive numbers are fun to learn about!

Next, let’s try the SHA-224 method:

SHA224 = hashlib.sha224(text.encode())
print(SHA224.hexdigest())

8ed6b6c67f8f543d41472f2b0da146516cc6e28a20637d4fc3018518

Since SHA-224 uses less bits/bytes than the SHA-384 and SHA-256 functions, it makes sense that the generated hash would be shorter than the hashes generated from those functions.

I imagine that the decimal number would be shorter too:

15042673619469762912744046603652644639808686980332500102671983805720

Not surprisingly, the decimal number from this hash is also shorter, but still fairly massive-it’s approximately 15 unvigintillion (a number followed by 66 zeroes).

Next, let’s try the SHA-512 method:

SHA512 = hashlib.sha512(text.encode())
print(SHA512.hexdigest())

8d42047543b1bea09199838101c6ba0b5fb5d2631c1bb9b772333f2faa51b7cd0c53946e79758475929244d33e02b8e8cd994d79c63747ffdeeec3d6e9a66719

Since SHA-512 generates the largest hashes of all the SHA hashing functions, it’s not surprising that the generated hash is so large.

With a hash so large, I can only imagine what the decimal number would look like:

7398275510411850389223316484636795314726283670587045768118856918282820596862752633935810532260756989519458279298581715671072351034597240609479775136147225

The number above is approximately 7 quinquagintillion, which sounds like a word Dr. Seuss would make up, but it’s a number followed by 153 zeroes.

Last but not least, let’s try the retired, and original, SHA hashing function on our text-SHA-1:

SHA1 = hashlib.sha1(text.encode())
print(SHA1.hexdigest())

5ed4ecb61ff2323823781c514cbe2181e034a186

Since the hashes generated from the SHA-1 function use the least bytes (20) from the five functions we explored, the generated hash would also be the shortest of the five hashes we’ve generated.

With that said, it won’t be surprising that the decimal number of this hash is also fairly short:

541393510912863704690262334257797793251671187846

Even though this number is shorter than the other four numbers we got from the other four hashes, it’s still fairly massive-541 quattuordecillion (a number followed by 45 zeroes).

All in all, we got to see the power of SHA hashing functions through some simple text encryption. The massive decimal numbers we got from each of the generated hashes put the power of the SHA algorithm into perspective (plus, I had fun exploring massive numbers).

An interesting application of SHA hashing algorithms

Before we go, I wanted to explain an interesting application of the SHA hashing algorithm-Bitcoin mining. Yes, Bitcoin mining utilizes the SHA-256 algorithm during the mining process-this is because SHA-256 is both computationally efficient and offers a good deal of security throughout the process. Granted, SHA-512 could also offer a great deal of security, but the hashes also take up a lot of memory.

So, how would one mine Bitcoin? Check out this illustration below for an explanation:

For a very simplistic explanation of Bitcoin mining, a miner would need to utilize a very powerful computer (with processing power much greater than even your standard gaming laptop) to continually generate SHA-256 hashes until the correct hash is found. Once that happens, the miner is entitled to some Bitcoin as a reward. How much Bitcoin they get is determined by Bitcoin’s value at a given time-for instance, the reward as of August 2024 is 3.125 Bitcoin (or 3 1/8 Bitcoin).

Here’s the GitHub link to the script from this post (SHA is the script name)-https://github.com/mfletcher2021/blogcode/blob/main/SHA.py.

Thanks for reading!

Michael

Image Encryption (Python Lesson 53)

Advertisements

Hello everybody,

Michael here, and in today’s post (unofficially the summer of encryption series), we’ll learn how to encrypt images with Python!

During our encryption lessons, we’ve learned how to encrypt text and files with Python. We also learned some interesting mathematics behind RSA encryption too.

Anyway, let’s get started with our image encryption! Here’s the image we’ll be using:

Yes, it’s yours truly in front of the Deadpool & Wolverine movie poster, giving a thumbs up to indicate my approval (and I highly recommend seeing it). Anyway, onto the image encryption.

Encrypting the image

Before we encrypt the image, let’s first create a Fernet key. If you recall from my previous post, Fernet key encryption is a simple symmetric-key encryption method that utilizes the same key to encrypt and decrypt something (in this case, the image).

Encryption key generation!

Before we encrypt and decrypt the image, let’s generate a file for our image key! We’d generate the image encryption/decryption key in a similar manner to the way we generated the file encryption/decryption key (take a look at the code below to see how we generated the file for the image encryption/decryption key):

from cryptography.fernet import Fernet

imageKey = Fernet.generate_key()

with open(r'C:/Users/mof39/OneDrive/Documents/imageKey.key', 'wb') as keyFile:
    keyFile.write(imageKey)

After importing the Fernet module from cryptography.Fernet (install the cryptography package if you haven’t done so already), we create our imageKey then write it to the aptly-named imageKey file-with a .key extension-to a specific file path (you can use any location on your local drive that you can access).

What does the key look like? Let’s find out:

j8P7M-h1v6_KxFkNncCYIfRIQwWmxlLToatX3lg5Jcg=

This is the key we will need to both encrypt and decrypt the image.

It’s image encryption time!

Now, let’s actually encrypt the image. Here’s how to do so:

with open(r'C:/Users/mof39/OneDrive/Documents/imageKey.key', 'rb') as keyFile:
    encryptionKey = keyFile.read()
    
fernetEncryptionKey = Fernet(encryptionKey)

with open(r'C:/Users/mof39/Downloads/20240728_132146.jpg', 'rb') as image:
    originalImage = image.read()
    
encryptedImage = fernetEncryptionKey.encrypt(originalImage)

with open(r'C:/Users/mof39/OneDrive/Documents/encryptedImage.jpg', 'wb') as image:
    image.write(encryptedImage)

What exactly did we do here? Let me explain:

  • First we read the file containing the image encryption/decryption key into the IDE.
  • Next we turned the encryptionKey into a Fernet object.
  • We then read the image we’ll be using into the IDE and encrypted it using the Fernet object we created in the previous step.
  • Finally, we wrote the encrypted image to a specific location on the local drive.
  • Word of advice-I would use different names for the encrypted and decrypted images to avoid confusion.

Now, let’s see what our encrypted image looks like:

As you can see, we can’t view our encrypted image, which is a good thing since the whole point of encrypting the image is to keep it from being seen.

However, let’s see what happens when we open this image on Notepad:

As you can see, we get this beautiful-looking gibberish, which I would love for a would-be hacker to see if they tried to intercept the image transmission to my intended recipient.

Decrypting the image

Now that we have successfully encrypted the image, let’s now decrypt it. Here’s how we’ll accomplish that:

with open(r'C:/Users/mof39/OneDrive/Documents/imageKey.key', 'rb') as keyFile:
    imageKeyFile = keyFile.read()
    
fernetDecryptionKey = Fernet(imageKeyFile)

with open(r'C:/Users/mof39/OneDrive/Documents/encryptedImage.jpg', 'rb') as image:
    encryptedImage = image.read()
    
decryptedImage = fernetDecryptionKey.decrypt(encryptedImage)

with open(r'C:/Users/mof39/OneDrive/Documents/decryptedImage.jpg', 'wb') as image:
    image.write(decryptedImage)

What exactly did we do here to decrypt the image? Let’s explain:

  • First, we read the image key file (the one containing our encryption/decryption key) into the IDE.
  • Next, we created a fernetDecryptionKey object from the imageKeyFile-we’ll use this to decrypt the image.
  • We then read the encryptedImage onto the IDE and, using out fernetDecryptionKey, decrypted the image.
  • Last but not least, we wrote the decryptedImage onto the local drive with the name decryptedImage.
  • Yes I know fernetDecryptionKey and fernetEncryptionKey are the same thing. I just thought it would be easier to use different variable names since they are being used for different purposes (decryption and encryption respectively).

And now, the moment of truth…here’s our decrypted image:

Yup, there I am in all my Deadpool & Wolverine-loving decrypted glory!

Here’s the link to today’s script in GitHub-https://github.com/mfletcher2021/blogcode/blob/main/imageencryption.py

Thanks for reading,

Michael