序列密码-OTP 详细讲解 (Python实现)

目录

0. 何为序列密码?

1. OTP加解密原理 

2. 完整加解密过程

2-1 密钥生成函数

 2-2 加密函数

2-3 解密函数

2-4 综合一下

3. 举一反三

3-1 对图片的加密

3-2 对图片的解密

3-3 对视频的加密

3-4 对视频的解密

4. 最后



0. 何为序列密码?

序列密码(Stream Cipher),也称为流密码,是一种对称密钥加密算法。它将明文消息按字符或比特逐位进行加密,而不是像分组密码那样将明文分成固定长度的块进行加密。

  • 特点
    • 加密速度快:由于是逐位加密,不需要对明文进行分组和填充等操作,因此加密和解密速度通常较快,适用于对实时性要求较高的应用,如视频流、音频流的加密。
    • 错误传播小:在加密过程中,如果某个比特发生错误,只会影响到该比特对应的密文,不会像分组密码那样导致整个分组的错误传播,对后续的解密影响相对较小。
    • 需要同步:发送方和接收方必须保持严格的同步,以确保生成相同的密钥流。如果同步出现问题,例如数据丢失或插入,可能会导致解密失败。
    • 密钥流的安全性至关重要:因为序列密码的安全性主要依赖于密钥流的随机性不可预测性。如果密钥流被攻击者预测或破解,那么整个加密系统就会被攻破。

1. OTP加解密原理 

 一次性密码本(OTP)是一种理论上具有完美保密性的加密方案。其核心原理基于以下几点:

1. 密钥:使用一个与明文长度相同且 完全随机 的密钥,这个密钥 只能使用一次 ,使用后就丢弃。 (C是密文,P是明文,K是密钥)

2. 加密过程:将明文与密钥进行逐位异或(XOR)操作,得到密文。即:C=P^K

3. 解密过程:将密文与相同的密钥再次进行逐位异或操作,就可以还原出明文。 因为异或运算具有自反性,即 P=C^K=(P^K)^K


2. 完整加解密过程

2-1 密钥生成函数

import os
def generate_key(length):
    return os.urandom(length)

解释:

使用 os.urandom(length) 生成length 长度的随机密钥。

为什么不用 random 呢?因为random生成的是伪随机数,并非像 os.urandom 那样具备密码学安全的随机数。


 2-2 加密函数

def encrypt(plaintext): # 接收明文参数
    # 获取加密密钥
    key = generate_key(len(plaintext))
    # 异或加密,得到密文
    ciphertext = bytes([a ^ b for a, b in zip(plaintext, key)])
    return ciphertext, key

解释:

zip() 函数会从每个可迭代对象中依次取出一个元素,组成一个元组,然后将这些元组作为迭代器的元素返回。
(当最短的可迭代对象耗尽时,迭代过程就会停止,即最终返回的迭代器的长度等于最短的可迭代对象的长度。)

例如:

list1 = [1, 2, 3]
list2 = ['a', 'b', 'c']
zipped = zip(list1, list2)
# 结果: [(1, 'a'), (2, 'b'), (3, 'c')]

 注意使用 bytes() 转换为字节类型。


2-3 解密函数

def decrypt(ciphertext, key): # 接收两个参数
    # 异或运算的可逆性
    plaintext = bytes([a ^ b for a, b in zip(ciphertext, key)])
    return plaintext

2-4 综合一下

import os


def generate_key(length):
    return os.urandom(length)


def encrypt(plaintext):
    # 获取加密密钥
    key = generate_key(len(plaintext))
    # 异或加密
    ciphertext = bytes([a ^ b for a, b in zip(plaintext, key)])
    return ciphertext, key


def decrypt(ciphertext, key):
    plaintext = bytes([a ^ b for a, b in zip(ciphertext, key)])
    return plaintext

if __name__ == "__main__":
    input_text = input("文本: ")
    plaintext = input_text.encode('utf-8')

    加密
    ciphertext, key = encrypt(plaintext)
    print(f"密文: {ciphertext.hex()}\nkey: {key.hex()}")  # 输出十六进制,而不是字节

    解密
    decrypted_text = decrypt(ciphertext, key)
    print(f"解密后的明文: {decrypted_text.decode()}")

3. 举一反三

尝试着对视频,图片,.pptx等不同格式的文件加密和解密 : )


3-1 对图片的加密

def encryptImg(ImgPath):
    # 分段保存密钥和加密内容的集合
    keyList = []
    ciphertextList = []
    # 随机生成名字,os.urandom(3)是字节类型
    imgName = os.urandom(3).hex()

    try:
        # 打开图片,切记用 "b" 
        with open(ImgPath, "rb") as f:
            # 将片分段,这里是 4096一个段
            for chunk in iter(lambda: f.read(4096), b""):
                # 加密
                ciphertext, key = encrypt(chunk)
                # 保存每一段       
                keyList.append(key)
                ciphertextList.append(ciphertext)
    except Exception as e:
        print(f"ERROR:{e}")

    try:
        # 保存加密后的图片
        with open(f"image/e-{imgName}.png", "wb") as f:
            for text in ciphertextList:
                f.write(text)
        print("SUCC OF ENCRYPT")
    except Exception as e:
        print(f"ERROR:{e}")
    # 返回两个集合
    return ciphertextList, keyList

for chunk in iter(lambda: f.read(4096), b""): 中解释如下: 

  1. f.read(4096):这是文件对象 f 的一个方法,用于从文件中读取指定字节数的数据。在这里,4096 表示每次读取 4096 个字节的数据。如果文件剩余内容不足 4096 字节,则会读取剩余的全部内容。

  2. lambda: f.read(4096):这是一个匿名函数(lambda 函数),它没有名字,功能是调用文件对象 f 的 read 方法并返回读取到的内容。每次调用这个 lambda 函数,就会从文件中读取 4096 字节的数据。

  3. iter(func, sentinel):这是 Python 的内置函数 iter 的一种特殊用法。iter 函数通常用于创建一个迭代器,这里它接收两个参数:

    • func:是一个可调用对象(在这里是前面定义的 lambda 函数),每次迭代时会调用这个函数来获取下一个值
    • sentinel:是一个标记值,当 func 的返回值等于这个标记值时,迭代就会停止。在代码中,b""(空字节串)被用作标记值,意味着当 f.read(4096) 返回空字节串时,说明已经读取到了文件的末尾,迭代过程结束。

3-2 对图片的解密

def decryptImg(ciphertextList, keyList, outputPath):
    # 解密后的名字
    imgName = os.urandom(3).hex()
    try:
        plaintextList = []
        for ciphertext, key in zip(ciphertextList, keyList):
            # 解密
            plaintext = decrypt(ciphertext, key)
            plaintextList.append(plaintext)

        # 使用 os.path.join 正确组合路径
        output_file_path = os.path.join(outputPath, f"d-{imgName}.png")
        with open(output_file_path, "wb") as f:
            for text in plaintextList:
                f.write(text)
        print("SUCC OF DECRYPT")
    except Exception as e:
        print(f"ERROR:{e}")

3-3 对视频的加密

def encrypt_video(video_path):
    try:
        # 打开视频文件
        with open(video_path, 'rb') as f:
            video_data = f.read()
        # 获取密钥
        ciphertext, key = encrypt(video_data)
        # 保存视频路径
        encrypted_video_path = video_path + '.encrypted'
        # 保存加密后的视频文件
        with open(encrypted_video_path, 'wb') as f:
            f.write(ciphertext)
        # 保存密钥路径
        key_path = video_path + '.key'
        # 保存密钥文件
        with open(key_path, 'wb') as f:
            f.write(key)
        # 输出保存路径
        print(f"视频文件已加密并保存为 {encrypted_video_path},密钥保存为 {key_path}")
    except Exception as e:
        print(f"加密过程中出现错误: {e}")

3-4 对视频的解密

def decrypt_video(encrypted_video_path, key_path):
    try:
        # 打开解密文件
        with open(encrypted_video_path, 'rb') as f:
            # 提取
            encrypted_video = f.read()
        # 打开密钥文件
        with open(key_path, 'rb') as f:
            # 提取
            key = f.read()
        # 解密
        decrypted_video = decrypt(encrypted_video, key)
        # 解密后文件保存路径
        decrypted_video_path = encrypted_video_path.replace('.encrypted', '')
        # 保存
        with open(decrypted_video_path, 'wb') as f:
            f.write(decrypted_video)
        # 输出
        print(f"视频文件已解密并保存为 {decrypted_video_path}")
    except Exception as e:
        print(f"解密过程中出现错误: {e}")

4. 最后

对视频和图片的处理使用了两种不同的思路,大家自己尝试。

其他格式的文件可以自己动手写一下。: >

你可能感兴趣的:(python,密码学)