视频流Hash

文章介绍了如何使用Python和OpenCV处理视频流,将其转换为二进制数据,并使用SHA-256算法计算哈希值。文章强调了逐块验证视频数据完整性的方法,以及在实际应用中如何使用现有加密库如hashlib。
摘要由CSDN通过智能技术生成

EXP4 视频流Hash

参考实验:

https://github.com/galadd/Stanford_Cryptography-I

一、读取视频流并转换为二进制数据格式

python调用opencv读取视频,可以直接调用cv2.VideoCapture()函数直接读取视频或摄像头,通过传入数字来读取对应的摄像头,或者通过传入一个路径字符串来读取对应的视频文件。

然后用read()逐个读取视频帧并用cv2.imencode()[1].tobytes()转换为字节形式。其中cv2.imencode()函数将图像编码为指定格式(例如JPEG、PNG等),并返回一个包含编码图像数据的numpy数组。第一个参数表示编码后的图像格式为JPEG格式,第二个参数表示要编码的图像。该函数返回一个包含两个元素的元组,其中第一个元素是一个布尔值,表示编码是否成功,第二个元素是一个包含编码后图像数据的numpy数组。接着使用.tobytes()方法将numpy数组转换为二进制数据。

def video_to_bytes(self):  # 读入视频流存储到二进制变量中
        video = cv2.VideoCapture(self.__video)
        frames = "".encode("utf-8")
        while True:
            ret, frame = video.read()  # 读取视频帧。返回值为一个布尔值和一个NumPy数组,布尔值表示读取是否成功,当读取到视频的最后一帧时,read()会返回False;NumPy数组则是表示读取到的视频帧的图像数据。
            if not ret:  # 检查是否已经读到最后一帧
                break
            # 将帧转换为字节串
            binary_frame = cv2.imencode(".jpg", frame)[1].tobytes()
            # 将图像编码为指定格式(例如JPEG、PNG等),并返回一个包含编码图像数据的numpy数组。
            # 返回一个包含两个元素的元组,其中第一个元素是一个布尔值,表示编码是否成功,第二个元素是一个包含编码后图像数据的numpy数组。
            frames += binary_frame
        video.release()    # 释放资源
        return frames

这样做有问题,运行时间很长,且视频二哈希值计算结果是"0f9214e9d48303efc6366b5a923f19875fc02b48d38c88f2af2f445de969ab8e”,计算错误,说明不能一帧读取照片,而应该用read()方法读取流文件。

倒着读取文件,从指定位置开始读取指定大小的数据:file_obj.seek(last_pos - read_size) data = file_obj.read(read_size)。并且打开文件使用with open(self.__video, 'rb') as file语句,以二进制模式读取文件,文件内容以字节(bytes)为单位进行读取和写入而非字符。

二、SHA-256算法简介

在这里插入图片描述

对于任意长度的消息,SHA256都会产生一个256位的哈希值,称作消息摘要,通常用一个长度为64的十六进制字符串表示。

待哈希的消息在进行哈希计算之前首先要进行以下两个步骤:

①对消息进行填充,使得补充后的长度是512b的整数倍;

②以512b为单位将消息进行分块处理为 M ( 1 ) , M ( 2 ) , . . . , M ( N ) M^{(1)},M^{(2)},...,M^{(N)} M(1),M(2),...,M(N)

然后分别对每个消息区块进行处理,其中初始哈希值 H ( 0 ) H^{(0)} H(0)固定:

H ( i ) = H ( i − 1 ) + C M ( i ) ( H ( i − 1 ) ) H^{(i)}=H^{(i-1)}+C_{M^{(i)}}(H^{(i-1)}) H(i)=H(i1)+CM(i)(H(i1))

其中 C C C是SHA-256的压缩函数,+是 m o d 2 32 mod2^{32} mod232加法,即将前一个区块的哈希值和本区块消息值加在一起,并对 2 32 2^{32} 232取余即可得到消息区块的哈希值

1.常量初始化

SHA-256算法中用到了8个哈希初值以及64个哈希常量。

初始哈希取自自然数中前面8个素数(2,3,5,7,11,13,17,19)的平方根的小数部分,并且取前面的32位。例如 2 ≈ 0.414213562373095048 \sqrt{2} \approx 0.414213562373095048 2 0.414213562373095048,而 0.414213562373095048 ≈ 61 6 − 1 + a 1 6 − 2 + 0 ∗ 1 6 − 3 + . . . 0.414213562373095048 \approx 616^{-1} + a16^{-2} + 0*16^{-3} + ... 0.4142135623730950486161+a162+0163+... ,则前12位对应0x6a09e667。

以此类推:

H 1 ( 0 ) = 6 a 09 e 667 H 2 ( 0 ) = b b 67 a e 85 H 3 ( 0 ) = 3 c 6 e f 372 . . . . . . H_1^{(0)}=6a09e667 \\ H_2^{(0)}=bb67ae85 \\ H_3^{(0)}=3c6ef372 \\ ...... H1(0)=6a09e667H2(0)=bb67ae85H3(0)=3c6ef372......

其他64个常数取自自然数中前64个素数的立方根的小数部分的前32位。

2.消息预处理

  • 填充比特:假设消息M的二进制编码长度为l位。首先在消息末尾补上一位"1", 然后再补上k个"0", 其中k为下列方程的最小非负整数:

l + 1 + k ≡ 448 m o d 512 l+1+k \equiv 448 mod 512 l+1+k448mod512

  • 附加长度值:接着将原始数据(第一步填充前的消息)的长度信息补到已经进行了填充操作的消息之后。SHA-256用一个长为64位的数据表示原始消息的长度。

3.计算消息摘要

  • 拆分消息:将M拆分为N个大小为512b的块。

  • 构造64个字:对于每一块,将其分解为16个32b的big-endian的字,记为w[0], …, w[15]。前16个字直接由消息的第i个块分解得到,其余的字由如下迭代公式得到:

    W t = σ 1 ( W t − 2 ) + W t − 7 + σ 0 ( W t − 15 ) + W t − 16 W_t= \sigma_1(W_{t-2})+W_{t-7}+\sigma_0(W_{t-15})+W_{t-16} Wt=σ1(Wt2)+Wt7+σ0(Wt15)+Wt16

  • 进行64次加密循环完成一次迭代:

在这里插入图片描述

哈希计算算法定义如下:

  • F o r   i   =   1 → N ( N 为填充后的消息块数 ) For\ i\ =\ 1\rightarrow N\left( N\text{为填充后的消息块数} \right) For i = 1N(N为填充后的消息块数)

    用第(i-1)个中间哈希值对a,b,c,d,e,f,g,h进行初始化, 当i=1时,即使用初始化哈希,表示如下:

    a ← H 1 ( i − 1 ) b ← H 2 ( i − 1 ) . . . h ← H 8 ( i − 1 ) a \leftarrow H_1^{(i-1)} \\ b \leftarrow H_2^{(i-1)} \\ ... \\ h \leftarrow H_8^{(i-1)} aH1(i1)bH2(i1)...hH8(i1)

  • 应用SHA-256压缩函数更新a,b,…,h:

    F o r   j   =   0   →   63 For\ j \ = \ 0 \ \rightarrow \ 63 For j = 0  63

    在这里插入图片描述

    其中逻辑函数定义如下:
    在这里插入图片描述

    运算符号定义如下:
    在这里插入图片描述

  • 计算第i个中间哈希值 H ( i ) H^{(i)} H(i)

    H 1 ( i ) ← a + H 1 ( i − 1 ) H 2 ( i ) ← b + H 2 ( i − 1 ) . . . H 8 ( i ) ← h + H 8 ( i − 1 ) H_1^{(i)} \leftarrow a + H_1^{(i-1)} \\ H_2^{(i)} \leftarrow b + H_2^{(i-1)} \\ ... \\ H_8^{(i)} \leftarrow h + H_8^{(i-1)} \\ H1(i)a+H1(i1)H2(i)b+H2(i1)...H8(i)h+H8(i1)

    H ( N ) = ( H 1 ( N ) , H 2 ( N ) , . . . , H 8 ( N ) ) H^{(N)}=(H_1^{(N)},H_2^{(N)},...,H_8^{(N)}) H(N)=(H1(N),H2(N),...,H8(N))为M最终的哈希值。

💡 Python中SHA-256哈希运算举例:

import hashlib

# 创建一个SHA-256对象
sha256 = hashlib.sha256()

# 更新哈希对象的数据,在原消息后追加消息并更新哈希值
sha256.update(b'Hello, world!')

# 计算哈希值
hash_value = sha256.digest()

# 将哈希值转换为16进制字符串表示
hex_value = hash_value.hex()

print(hex_value)  # 输出:'c8b5b685638bba8dbe173f70f8c06912df9c2b08cf3c3a8f90ab1cdac8ebf925'

(也可使用Crypto.SHA256):

sha256 = SHA256.new()
sha256.update(chunk)
if last_hash:
    sha256.update(last_hash)
last_hash = sha256.digest()

三、题目思想

网站不计算整个文件的哈希值,而是将文件分成 1KB 的数据块(1024 字节)。它计算最后一个块的哈希并将值附加到倒数第二个块。然后它计算这个增强的倒数第二个块的散列,并将得到的散列附加到最后的第三个块。此过程从最后一个块继续到第一个块,如下图所示:
在这里插入图片描述
网站将最终的哈希值通过认证信道分发给用户。

在用户端,浏览器每次下载文件的一个数据块,以及上述所示的附加哈希值,并进行验证,若验证通过则播放第一个视频块。具体的,通过验证与是否相等来验证第一个区块的完整性;通过验证与是否相等来验证第二个区块的完整性;以此类推直至最后一个区块。这样,每个块都会在收到时进行验证和播放,无需等到整个文件下载完毕。容易证明,只要哈希函数满足抗碰撞性质,那么攻击者的篡改行为无法通过上述完整性验证。

本次实验使用 SHA-256 作为哈希函数,实现上述视频验证程序。每一个哈希值以二进制数据的形式和视频数据块进行链接。若视频数据大小不是 1KB的整倍数,那么最后一个区块可以小于 1KB,其余的数据区块则需要刚好是1KB 的整倍数。

程序能够根据输入的文件计算得到相应的,以验证收到文件的正确性。具体的,请在实验报告中给出视频1的值(使用 hex 编码)。

测试用例:视频2的值为:

03c08f4ee0b576fe319338139c045c89c3e8e9409633bea29442e21425006ea8


对于 SHA-256 的实现,允许使用现有的加密库,例如 PyCrypto (Python)、Crypto++ (C++) 或任何其他库。

四、实验代码

# -*- coding = utf-8 -*-
# @Time : 2023/5/7 14:56
# @Author : cherry
# @Software : PyCharm

import os
import hashlib

class Hash:  # 定义Hash方法
    def __init__(self, video_name, block_size):
        self.__video = video_name
        self.__size = os.path.getsize(self.__video)   # 获取文件的大小(以字节为单位)
        self.__normal_size = block_size
        self.__last_size = self.__normal_size if self.__size % self.__normal_size == 0 else self.__size % self.__normal_size

    def read_reversed(self, file_obj):   # 倒序读取文件
        block_num = 0    # 分组个数
        last_pos = self.__size    # 最后读取的位置
        while last_pos > 0:    # 没有读到最前面
            read_size = self.__last_size if block_num == 0 else self.__normal_size    # 读取最后一块时不用按照整块读取
            file_obj.seek(last_pos - read_size)   # 改变读写指针的位置
            data = file_obj.read(read_size)    # 读取指定字节的数据
            if not data:   # 数据为空
                break
            block_num += 1
            last_pos -= read_size
            yield data   # 返回一个迭代器对象,允许函数暂停和继续执行的状态保持,这样生成器就可以迭代生成一系列的值,而不是一次性生成并返回所有的值

    def hash(self):  # 计算hash值
        # 读取文件内容
        with open(self.__video, 'rb') as file:   # 二进制模式读取文件时,文件内容以字节(bytes)为单位进行读取和写入而非字符
            last_hash = b''    # 最后一个hash值
            for block in self.read_reversed(file):
                # 创建一个SHA-256对象
                sha256 = hashlib.sha256()
                sha256.update(block)   # 将提供的字节数据追加到当前的消息中,并更新摘要
                if last_hash:
                    sha256.update(last_hash)    # 追加上一个hash
                last_hash = sha256.digest()
        return last_hash.hex()   # 十六进制字符串

if __name__ == "__main__":
    video_1 = "./6.1.intro.mp4"
    video_2 = "./6.2.birthday.mp4"
    block_size = 1024
    hash1 = Hash(video_1, block_size)
    hash2 = Hash(video_2, block_size)
    hash_value2 = "03c08f4ee0b576fe319338139c045c89c3e8e9409633bea29442e21425006ea8"

    hash_test2 = hash2.hash()
    print("测试视频2的h0值为:", hash_test2)
    # 验证是否正确
    if hash_test2 == hash_value2:
        print("测试通过!")
        hash_test1 = hash1.hash()
        print("测试视频1的h0值为:", hash_test1)
    else:
        print("测试失败,请检查代码!")

运行结果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值