How to Use NumPy for Cryptographic Algorithms

Updated: January 23, 2024 By: Guest Contributor Post a comment

Introduction

NumPy is a foundational library for numerical computing in Python. While it’s most famous for its array object that can perform operations at lightning speed, it has many lesser-known applications, one of which is in the world of cryptography. In this guide, we’ll cover how to utilize the powerful NumPy library to implement cryptographic algorithms.

Before we jump into algorithms, let’s establish what cryptography is. In a nutshell, cryptography is the practice of secure communication in the presence of third parties. It uses algorithms to transform information into a secure format that only the intended recipient can decode and read using a secret key.

To get started, you’ll need to have NumPy installed. If you haven’t done so already, you can install it using pip install numpy.

Basic Operations in NumPy

NumPy’s primary feature is its array, or ndarray, which is optimized for mathematical operations. Before diving into the cryptographic context, let’s get familiar with some basic NumPy array operations:

import numpy as np

# Create an array
a = np.array([1, 2, 3])

# Element-wise addition
b = a + 2

# Print the result
print(b) # Output: [3 4 5]

Now that we have a grip on simple operations, let’s use NumPy to generate pseudorandom numbers, which is a crucial requirement in cryptographic applications:

import numpy as np

# Ensure reproducibility
np.random.seed(42)

# Generate an array of pseudorandom numbers
random_numbers = np.random.randint(0, 100, size=5)

# Print the result
print(random_numbers) # Output: [51 92 14 71 60]

Many cryptographic algorithms rely on random number generation, which we’ll use in later examples.

Bitwise Operations for Hash Functions

Hash functions are an essential class of cryptographic algorithms. They map data of arbitrary size to data of a fixed size. This fixed-size data, known as a hash, behaves quasi-randomly with respect to its input. Let’s perform some basic bitwise operations, which are the building blocks of hash functions:

import numpy as np

# Perform bitwise AND
bitwise_and = np.bitwise_and(13, 17)
print(bitwise_and) # Output: 1

# Perform bitwise XOR
bitwise_xor = np.bitwise_xor(17, 22)
print(bitwise_xor) # Output: 7

With these basics, you can construct simple hash functions using NumPy.

Implementing a Simple Encryption Scheme

A simple and historical encryption scheme is the Caesar cipher where each letter in the plaintext is ‘shifted’ a certain number of places down or up the alphabet. Though not cryptographically secure by modern standards, it’s a great way to learn fundamental concepts:

import numpy as np

def caesar_encrypt(plain_text, shift):
    encrypted_text = ''
    for char in plain_text:
        # Check if character is a letter
        if char.isalpha():
            # Shift character and assure the new character is a letter
            num = (ord(char) + shift) % 26
            if char.islower():
                encrypted_text += chr(num + ord('a'))
            else:
                encrypted_text += chr(num + ord('A'))
        else:
            encrypted_text += char
    return encrypted_text

# Usage example
message = 'NumPy Cryptography'
shift = 4
encrypted_message = caesar_encrypt(message, shift)
print(encrypted_message) # Output: 'RyqXc Egxvctvkqp'

Using arrays and vectorization in complex encryption algorithms can create a much more efficient method. But this classic example allows us to grasp the basic principles of substitutive ciphers.

Advanced: Implementing a One-Time Pad with NumPy

For this advanced example, we’ll utilize NumPy to develop a one-time pad, which is an encryption algorithm that’s theoretically unbreakable when used correctly. The key is a random string that’s as long as the plaintext. Each character in the plaintext is combined using XOR with a character from the key:

import numpy as np

def one_time_pad_encrypt(plain_text, key):
    # Convert text and key to arrays of integers
    text_array = np.frombuffer(plain_text.encode('utf-8'), dtype='uint8')
    key_array = np.frombuffer(key.encode('utf-8'), dtype='uint8')
    
    # Perform XOR operation
    encrypted_array = np.bitwise_xor(text_array, key_array)
    
    # Convert back to bytes then to a string
    encrypted_text = bytes(encrypted_array.tolist()).decode('utf-8')
    return encrypted_text

# Generate a random key of the same length as the message
message = 'Secure Message'
key = np.random.bytes(len(message))

# Encrypt message
encrypted_message = one_time_pad_encrypt(message, key)
print('Encrypted message:', encrypted_message)

# Decrypt message
decrypted_message = one_time_pad_encrypt(encrypted_message, key)
print('Decrypted message:', decrypted_message) # Output should be the original message

Performing encryption reliably in NumPy yields great efficiency benefits but does require some ingenuity to handle text and binary data.

Conclusion

In this tutorial, we explored how NumPy, a fast and versatile Python library, can be utilized for cryptographic purposes. We learned that by utilizing arrays and the diverse range of operations NumPy provides, we can efficiently implement basic cryptographic schemes in Python. Remember to keep learning and practicing to further hone your cryptography skills with NumPy.