检验身份的算法设计

在复杂的网络环境中,你所收到的文件可能在传输的过程中已经被别人篡改过.而收到消息之后,没有验证其正确性就直接拿过来使用导致的后果可能是不堪设想的.
在这里插入图片描述

那么我们怎么才能知道文件在传输的过程中是否被人修改过呢?

  • 我们可以通过 数字签名 的方式来验证用户的身份.当然也可以通过数字签名的方式来验证某个二进制文件在传输过程中是否被别人修改过.

  • 下述故事只是为了说明该种情况.

小时候特别喜欢玩手机游戏,但是总会出现游戏中的装备太差不能过关的现情况.于是我总会想着下载破解版 的游戏.😏。这样我就可以买到任何自己想要的装备了,但是当你下载完破解版的游戏,往手机上装载的时候.在安装界面就会出现一串红色的文字,提示你说该应用签名不一致,请谨慎安装.

那么问题来了,在这个过程中手机端是怎么检查到这个app被修改过的呢?
这个检测过程其实就是对比原始文件的数字签名和收到的文件的签名是否一致,若一致就说明没有被修改过,如果不一致的话就说明该文件已经被篡改过,使用过程中存在风险.

数字签名
  • 数字签名:

就是只有信息的发送者才能产生的别人无法伪造的一段数字串.

  • 数字签名的功效

  • 1.确定消息确实是由发送方签名并发出来的,因为别人不能假冒发送方才持有的数字签名

  • 2.数字签名能确定消息的完整性,因为数字签名的特点是它代表了文件的特征,文件如果发生改变,数字摘要的值也将发生变化.不同的文件将得到不同的数字摘要.

  • 常用的数字签名方法

1.HASH算法

  • 此算法主要包括MD(信息摘要)SHA算法(安全散列算法)

2.Digital Signature Algorithm (DSA), ECDSA (Elliptic Curve Digital Signature Algorithm),椭圆曲线数字签名算法,微软产品的序列号验证算法使用的就是ECDSA与传统的数字签名算法相比,速度快,强度高,签名短

计算机签名MD5

MD5是由Ron Rivest在1991设计的一种信息摘要(message-digest )算法,当给定任意长度的信息,MD5会产生一个固定的128位“指纹”或者叫信息摘要。从理论的角度,所有的信息产生的MD5值都不同,也无法通过给定的MD5值产生任何信息,即不可逆。

MD5功能特点
  • 1.输入任意长度的信息,经过处理,输出为128位的信息(数字指纹)
  • 2.不同的输入得到的不同的结果(唯一性)。要使两个不同的信息产生相同的摘要,操作数量级在2^64次方。
  • 3.根据128位的输出结果不可能反推出输入的信息。根据给定的摘要反推原始信息,它的操作数量级在2^128次。
算法步骤
添加填充位
  • 信息的最尾部(不是每一块的尾部)要进行填充,使其最终的长度length(以bit为单位)满足lenght%512=448,这一步在计算MD5值的时候必须要执行,即使信息的原始长度恰好符合上述要求.

填充的规则:第一个bit填充位填’1’,后续的bit填充位都填’0’,最终使消息的总体长度恰好符合上述要求.总之,至少要填充1bit,至多填充512bit

void MD5::calculateMD5Final() {
	//函数用于对整体文件数据的最后位数进行填充
	// lastByte: 小于64个字节,表示最后一块数据大小
	unsigned char* p = _chunk + _lastByte;
	//p记录需要填充的起始位置
	//填充位的前八位 : 1000 0000 0x80 
	*p++ = 0x80;
	size_t remainFillByte = _chunkByte - _lastByte - 1;//判断需要填充的位数
	// 剩余填充字节数 小于8 个字节
	if (remainFillByte < 8) {
		// 从下一个自己二开始 全部填 0
		memset(p, 0, remainFillByte);
		calculateMD5((size_t*)_chunk);
		memset(_chunk, 0, _chunkByte);
		//将新出现在空间的中的512位全部置为0
		//最后将数组中的后8位置为文章长度
	}
	else {
		// 填充 0 后 剩余字节数 大于等于 8 个字节  8个字节前 中间全部填 0 
		memset(p, 0, remainFillByte);
	}
	// 最后的64 bit 存放原始文档的bit长度

	// char 类型 最大范围 256 最终类型 还是要以long long 类型存放
	//_chunk = _totalByte * 8;
	((unsigned long long*) _chunk)[7] = _totalByte * 8;
	//最后的字节存放文档的长度
	calculateMD5((size_t*)_chunk);

}

添加bit长度
  • 在增加填充位之后,添加一个64bit大小的length,length表示原始消息(未填充之前)的bit长度极端情况,如果消息长度超过2^64, 那么只使用前2 ^ 64bit的消息。

  • 这一步完成之后,消息的最终长度变为(length + 64) % 512 = 0,即length为512的整数倍

  • 从这里再去看第一步,至少需要填充 8 bit,我们假设几种情况分析一下:
    首先要明确一个字符占1byte(8bit, 中文字符的话占16bit),所以原始信息bit长度一定是8的倍数

需要注意必须:1.必须要进行填充,2.最后一块数据的最后64位必须是原始文件的长度值

  • 假设1:消息原始长度%512=448

这时候原始长度符合要求,但是根据填充规则,仍然要至少填1bit的 ‘1’ ,后面还剩63bit,不够规定的64位用来添加长度,所以需要再添加一块数据(512bit),这样后面63bit填0,新加的数据前448bit需要填充位0.后面的64位用来填数据原始长度,一定要记住长度值是放在最后一块数据的最后64bit

在这里插入图片描述

最后一块数据的大小刚好为448的时候,由于需要对最后一块信息进行填充,所以添加一个bit的‘1’,在此之后剩余的63位少于规定的最后64bit用来填充原始数据长度,所以选择再添加一块大小为512bit的数据块用来计算MD5值

  • 假设2:消息原始长度%512<448

此时最后一块数据长度不大于440bit,最后64bit填数据长度可以用来存储原始数据的长度.由于需要填充的bit位不小于8,所以需要在真实的信息数据之后添加一个‘1’ 和若干个‘0’

在这里插入图片描述

  • 假设3:消息原始长度%512>448

此时最后一块 数据长度不小于456,最多504,剩余bit不够添加64位长度,和假设1相同,需要增加一块数据,最后64位添加长度,其余填充0

在这里插入图片描述

结论:最少填充8bit,最少填充内容1000 0000,16进制即为0x80。

初始化MD buffer

用4个word buffer(A,B,C,D)存储计算的结果,这里A,B,C,D各为一个32bit的变量,这些变量初始化为下面的十六进制值,低字节在前

/*
word A: 01 23 45 67
word B: 89 ab cd ef
word C: fe dc ba 98
word D: 76 54 32 10
*/
// 初始化A,B,C,D
_atemp = 0x67452301;
_btemp = 0xefcdab89;
_ctemp = 0x98badcfe;
_dtemp = 0x10325476;

处理过程中要用一个含有64个元素的表K[1…64],表中的元素值由sin函数构建,K[i]等于2^(32) * abs(sin(i))的整数部分,即:

/*
K[i] = floor(2^(32) * abs(sin(i + 1))) // 因为此处i从0开始,所以需要sin(i + 1)
*/
for (int i = 0; i < 64; i++)
{
	_k[i] = (size_t)(abs(sin(i + 1)) * pow(2, 32));
}
  • 代码
void MD5::init(){
	// 初始化 ABCD
	_a = 0x67452301;
	_b = 0xefcdab89;
	_c = 0x98badcfe;
	_d = 0x10325476;
	
	//用来记录每次循环左移所需要移动的位数
	size_t s[] = { 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7,
		12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
		4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10,
		15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 };
	// TODO

	memcpy(_sft, s, sizeof(s));
	// k[i] 2^32 * abs(sin(i))
	for (int i = 0; i < 64; ++i) {
		_k[i] = (size_t)(pow(2, 32)*abs(sin(i + 1)));
	}
}
按512位数据逐块处理输入信息

512bit数据为一个处理单位,暂且称为一个数据块chunk,每个chunk经过4个函数(F, G, H, I)处理,这四个函数输入为3个32位(4字节)的值,产生一个32位的输出。四个函数如下所示:

/*
F(x,y,z) = (x & y) | ((~x) & z)
G(x,y,z) = (x & z) | ( y & (~z))
H(x,y,z) = x ^ y ^ z
I(x,y,z) = y ^ (x | (~z))
*/
  • 代码
	size_t F(size_t x, size_t y, size_t z) {
		return (x & y) | ((~x) & z);
	}
	size_t G(size_t x, size_t y, size_t z) {
		return (x & z) | (y & (~z));
	}
	size_t H(size_t x, size_t y, size_t z) {
		return x ^ y ^ z;
	}
	size_t I(size_t x, size_t y, size_t z) {
		return y ^ (x | (~z));
	}
数据处理

在处理一个chunk(512bit)的数据时,会把这个chunk再细分成16组4字节数据,一个chunk经过4轮进行处理,每轮都会把chunk的所有数据处理一遍,每轮有16个相似的子操作,所以一个chunk的数据要进行64个子操作。
计算之前先保存MD buffer的当前值:
a = A, b = B, c = C, d = D
第一轮:F函数处理(0 <= i <= 15)

在这里插入图片描述
在这里插入图片描述

F = F(b, c, d)
d = c
c =b
b = b + shift((a + F + k[i] + chunk[g]), s[i])
a = d

第二轮:G函数处理 (16 <= i <= 31)

在这里插入图片描述
在这里插入图片描述

G = G(b, c, d)
d = c
c =b
b = b + shift((a + G + k[i] + chunk[g]), s[i])
a = d

第三轮:H函数处理(32 <= i <= 47)
在这里插入图片描述
在这里插入图片描述

H = H(b, c, d)
d = c
c =b
b = b + shift((a + H + k[i] + chunk[g]), s[i])
a = d

第四轮:I函数处理(48 <= i <= 63)

在这里插入图片描述

在这里插入图片描述

I = I(b, c, d)
d = c
c =b
b = b + shift((a + I + k[i] + chunk[g]), s[i])
a = d

图中的<<<s表示循环左移操作,每次左移的位数,在算法中也是和i一一对应的,也就是我们这的shift表示的含义。

/*
s[ 0..15] = { 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22 }
s[16..31] = { 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20 }
s[32..47] = { 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23 }
s[48..63] = { 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 }
*/
size_t s[] = { 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7,
12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10,
15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 };

从上图中可以看出g和i也存在一种确定的对应关系,关系如下:

if (0 <= i < 16) g = i;
if (16 <= i < 32) g = (5 * i + 1) % 16;
if (32 <= i < 48) g = (3 * i + 5) % 16;
if(48 <= i < 63) g = (7 * i) % 16;

一个chunk数据处理完之后,更新MD buffer的值A, B, C, D

A = a + A;
B = b + B;
C = c + C;
D = d + D;
  • 代码实现
void MD5::calculateMD5(size_t* chunk) {
	//建立大体框架
	//函数用于将所得的内容数据块进行分批次操作

	size_t a = _a;
	size_t b = _b;
	size_t c = _c;
	size_t d = _d;
	// chunk[g]
	// f 是 哈希函数返回值 
	size_t f, g;

	// 64 次变换, 4轮操作,每一轮操作:16个子操作

	for (size_t i = 0; i < 64; ++i) {
		if (0 <= i && i < 16) {
			f = F(b, c, d);
			g = i; 
		}
		else if (16 <= i && i < 32) {
			f = G(b, c, d);
			g = (5 * i + 1) % 16;
		}
		else if (32 <= i && i < 48) {
			f = H(b, c, d);
			g = (3 * i + 5) % 16;
		}
		else {
			f = I(b, c, d);
			g = (7 * i) % 16;
		}

		//更新数据块中的值
		size_t dtmp = d;
		d = c;
		c = b;
		b = b + shiftLeftRotate(a + f + _k[i] + chunk[g], _sft[i]);
		//f哈希函数的返回值,_chunk[g]当前用到的数据块,
		//_sft[i] 循环左移的位数
		a = dtmp; //a=没有更新前的d(dtmp暂存之前的d值)
	}

	_a += a;
	_b += b;
	_c += c;
	_d += d;
}

循环左移的操作

在这里插入图片描述

  • 循环左移函数
	size_t shiftLeftRotate(size_t num, size_t n) {
		return (num << n) | (num >> (32 - n));
		//循环左移n位
	} 
  • 将四个buffer中的数据转换成16进制的字符串
std::string MD5::changeHex(size_t num) {
	//用来将对应的整形数组转成16进制字符串形式
	static std::string strMap = "0123456789abcdef";
	std::string ret;
	std::string byteStr;
	for (int i = 0; i < 4; ++i) {
		byteStr = "";
		size_t b  = (num >> (i * 8)) & 0xff;
		for (int j = 0; j < 2; ++j) {
			byteStr.insert(0, 1, strMap[b % 16]);
			b /= 16;
		}
		ret += byteStr;
	}
	return ret;
}
摘要输出

这一步拼接4个buffer(A,B,C,D)中的摘要信息,以A中的低位字节开始,D的高位字节结束。最终的输出是128bit摘要信息的16进制表示,故最后输出一个32长度的摘要信息。

//比如一个数,它的16进制表示为: 0x23456789, 
//他所对应的8个摘要信息为从低位字节的89开始,高位字节的23结束,即: 89674523
std::string MD5::getFiledMDd5(const char* filename){
	//读取文件
	std::ifstream fin(filename, std::ifstream::binary);
	if (fin.is_open()) {
		while (!fin.eof()) {
			fin.read((char*)_chunk, _chunkByte);
			if (_chunkByte != fin.gcount())
				break;

			_totalByte += _chunkByte;
			calculateMD5((size_t*)_chunk);
			//计算当前资源的MD5值
		}
		_lastByte = fin.gcount();
		_totalByte += _lastByte;
		calculateMD5Final();
	}

	return (changeHex(_a) + changeHex(_b) + changeHex(_c) + changeHex(_d));
}
白盒检测
用windows自带工具验证读取文件时MD5代码的正确性:
CertUtil -hashfile "文件路径" MD5
  • 检测结果

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 对于含有网络攻击的人工神经网络同步算法的设计与仿真,需要考虑以下几个方面: 1. 网络攻击类型:不同类型的网络攻击对同步算法的影响不同,因此需要先了解可能面临的网络攻击类型,包括拒绝服务攻击、恶意软件攻击、数据篡改攻击等。 2. 同步算法的设计:同步算法需要考虑网络攻击的影响,包括如何检测攻击并进行响应,如何保持神经网络的同步等。 3. 仿真环境的搭建:需要搭建一个仿真环境,包括神经网络模型、攻击模型、同步算法模型等,以便进行仿真实验。 4. 仿真实验的分析与评估:在仿真环境中进行多组实验,分析同步算法的性能以及在不同类型网络攻击下的表现,评估算法的有效性和可行性。 总体来说,含有网络攻击的人工神经网络同步算法的设计与仿真需要综合考虑多个因素,包括攻击类型、同步算法设计、仿真环境的搭建以及实验评估等。 ### 回答2: 人工神经网络同步算法设计与仿真中存在网络攻击是一个重要的研究领域。网络攻击会干扰神经网络的正常运行,影响算法的同步性能和可靠性。因此,为了保证算法在网络攻击的环境下仍能有效同步,需要设计相应的防御策略。 首先,针对恶意节点的攻击,可以采用节点识别和过滤的方法。通过监测节点的行为模式和通信数据,识别出那些可能进行攻击的节点,并将其从网络中剔除,以保证算法的正常运行。 其次,针对拓扑攻击,可以采用拓扑验证和容错机制进行防御。通过比对节点之间的通信拓扑结构,检测并剔除那些引入恶意拓扑变化的节点,从而防止拓扑攻击对同步算法造成的影响。 另外,还可以采用加密和鉴权技术来保护通信数据的安全性。通过对通信数据进行加密,可以防止攻击者窃取信息和篡改数据。同时,引入鉴权机制,确保只有经过授权的节点才能参与同步算法的运行。 最后,为了提高网络攻击下的同步算法的鲁棒性,可以采用容错机制和自适应调整策略。容错机制可以使算法具备一定的容错能力,即使网络中存在攻击节点,依然能够保证算法的正常执行。并且,算法需要具备自适应调整能力,能够根据网络攻击的情况自动调整参数和策略,以适应实际环境。 综上所述,含有网络攻击的人工神经网络同步算法设计与仿真是一个重要而复杂的问题。需要综合考虑节点识别、拓扑验证、加密鉴权、容错机制和自适应调整等策略,以保证算法在攻击环境下的鲁棒性和可靠性。同时,还需要进行大量的仿真实验,验证算法的性能和效果,为实际应用提供有效的指导。 ### 回答3: 人工神经网络同步算法设计与仿真是一项重要的研究课题,但在现实中面临着网络攻击的威胁。为了确保算法能够在网络攻击的环境中正常工作,我们需要采取一系列的措施来保护人工神经网络的安全。 首先,我们可以在设计同步算法时引入网络防御机制,例如使用加密算法保护神经网络的通信过程,这样可以提高攻击者获取数据的难度。其次,我们还可以通过身份验证和授权的方式限制对人工神经网络的访问,只允许授权用户进行操作,以防止攻击者直接入侵网络。 另外,我们可以采用日志记录和监控系统来实时监测网络活动,及时发现可能的攻击行为。一旦发现异常行为,我们可以立即采取措施进行响应,例如断开与攻击者的连接或者封禁其IP地址,以阻止攻击进一步发展。 此外,在算法设计和仿真过程中,我们可以利用渗透测试的方法来模拟各种网络攻击情景,以验证我们的算法对于不同类型攻击的抵抗能力。通过不断实践和优化,我们可以进一步提高人工神经网络同步算法的抗攻击能力。 总之,人工神经网络同步算法在设计和仿真过程中需要考虑网络攻击因素,采取相应的防御措施来保护网络安全。只有在一个安全的环境中,我们才能更好地研究和应用这些算法,推动人工智能技术的发展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值