主頁 >  其他 > 資訊摘要函式(Hash函式)的設計與性質驗證

資訊摘要函式(Hash函式)的設計與性質驗證

2020-12-14 12:50:20 其他

1.資訊摘要函式(Hash函式)的設計與性質驗證實驗

2.實驗目的:資訊摘要函式(Hash函式)的設計與性質驗證,

2.1實驗設備:PC機 一臺/人

2.2實驗原理:

2.2.1.資訊摘要函式具有固定的輸出數位,

2.2.1資訊摘要函式滿足不可求逆,不可偽造和在可行時間之內找不到碰撞等特性,

3.實驗內容及注意事項:

資訊摘要函式的設計與Hash值的性質驗證

實驗步驟:

3.1設計符合原理要求的資訊摘要函式H(m),

3.2對于如下明文資訊m:

There was a grocery shop in a town. Plenty of mice lived in that grocery shop. Food was in plenty for them. They ate everything and spoiled all the bags. They also wasted the bread, biscuits and fruits of the shop. The grocer got really worried. So, he thought "I should buy a cat and let it stay at the grocery. Only then I can save my things." He bought a nice, big fat cat and let him stay there. The cat had a nice time hunting the mice and killing them. The mice could not move freely now. They were afraid that anytime the cat would eat them up. The mice wanted to do something. They held a meeting and all of them tweeted "We must get rid of the cat. Can someone give a suggestion"?  All the mice sat and brooded. A smart looking mouse stood up and said, "The cat moves softly. That is the problem. If we can tie a bell around her neck, then things will be fine. We can know the movements of the cat". “Yes, that is answer," stated all the mice. An old mouse slowly stood up and asked, "Who would tie the bell?" After some moments there was no one there to answer this question. 

呼叫H(m)演算法,產生Hash值,

3.3隨機改變m的任意一位字符,產生新的Hash值,

如此實驗10次,記新的Hash值為 H1,H2,…H10

3.4設計函式計算H0和Hi 的相似度 i=1,2,…10

3.5分析結果,得出結論

4.實驗程序

4.1設計 哈希函式Hash(m)的思路

? MD5摘要演算法是Hash摘要演算法中的一種摘要演算法,具有不可求逆性,不可偽造性,MD5 演算法自誕生之日起,就有很多人試圖證明和發現它的不安全之處,即存在碰撞(在對兩個不同的內容使用 MD5演算法運算的時候,有可能得到一對相同的結果值),2009年,中國科學院的謝濤和馮登國僅用了 的碰撞演算法復雜度,破解了MD5的碰撞抵抗,該攻擊在普通計算機上運行只需要數秒鐘 ,因此MD5演算法不具備實驗中要求的“在可行時間之內找不到碰撞”這個特性,
? 根據MD5的存在的缺點,為了加強MD5加密演算法的安全性(本身是不可逆的),從而加入了新的演算法部分即加鹽值,加鹽值是隨機生成的一組字串,可以包括隨機的大小寫字母、數字、字符,位數可以根據要求而不一樣,使用不同的加鹽值產生的最終密文是不一樣的,由于使用加鹽值以后的密碼相當的安全,即便是你獲得了其中的salt和最終密文,破解也是一個耗費相當多時間的程序,可以說是破解單純MD5的好幾倍,并且同一個明文在經過加鹽(這個鹽是隨機的,不同的)后進行MD5演算法產生的密文也是不同的,這就使得MD5演算法的抗碰撞性得到大幅度增強,因此可以滿足本次實驗要求,

4.2MD5+鹽的實作步驟

1).首先獲取需要進行md5普摘要演算法的明文text,
2).隨機生成加鹽值(可以由字母,數字,字符組成,位數可以自己決定,這里使用16位的字串)salt字串.
3)將生成的salt字串追加到明文text中,得到組合的字串 mergeText = text +salt
4)使用md5普通摘要演算法獲取mergeText 的hash值得到一個32位的字串decodeText,
5).將鹽salt字串的每個字符逐個插入到字串decodeText是 i *3+1(i = 0,1,2,…16)的位置上,得到一個新的48位的字串 decode,
6)最終的decode字串就是所要的密文,位長為48個字符,

該代碼已經用c++實作,具體請看:C++實作MD5摘要演算法加鹽salt值

4.3具體代碼實作

4.3.1MD5的普通加密演算法

4.3.1.1.將二進制位元組陣列轉換為16進制的字串
/* Convert byte array to hex string. */
string MD5::bytesToHexString(const byte *input, size_t length) {
	string str;
	str.reserve(length << 1);
	for (size_t i = 0; i < length; i++) {
		int t = input[i];
		int a = t / 16;
		int b = t % 16;
		str.append(1, HEX[a]);
		str.append(1, HEX[b]);
	}
	return str;
}
string EncryptUtil::md5Hex(const string& text) {
	//首先更新md5中需要進行摘要的字串
	this->md5->update(text);
	//使用md5的方法生成對應的摘要后的字串
	return this->md5->toString();

}

4.3.2產生鹽值字串的演算法

/**
產生鹽值字串salt
*/
string EncryptUtil::createSalt()
{
	//隨機生成兩個亂數
	int num1 = rand() % 99999999;
	int num2 = rand() % 99999999;
	string salt = "";
	//將這兩個亂數轉換為字串后追加到salt中
	salt.append(to_string(num1));
	salt.append(to_string(num2));
	//salt不夠十六位就在后面加0
	int len = salt.size();
	if (len < 16) {
		for (int i = 0; i < 16 - len; i++) {
			salt.append("0");
		}
	}
	return salt;
}

4.3.3獲取md5普通摘要函式+鹽值salt操作后的最終密文

/**
* 加鹽MD5
* @param text : 需要進行加鹽后摘要的text明文
* @return string:回傳進行摘要演算法后的字串
*/
string EncryptUtil::encryptBySalt(const string& text) {
	//產生鹽值字串salt
	string salt = createSalt();
	//將產生的salt追加到需要進行摘要的明文text中,得到由明文text和salt組成的字串
	string merge_str =  text + salt;
	//使用md5演算法進行摘要計算得到相應的32位的密文
	string encodeText = md5Hex(merge_str);
	char cs [48];
	//將32位的密文和產生的16位的salt進行重組形成新的48位的字串,
	//這里表示,在新的字符陣列cs中,salt中的字符添加到cs字符陣列中位置是i*3+1的位置,其它位置由摘要密文encodeText中的字符填充
	for (int i = 0; i < 48; i += 3) {
		cs[i] = encodeText[i / 3 * 2];
		char c = salt[i / 3];
		cs[i + 1] = c;
		cs[i + 2] = encodeText[i / 3 * 2 + 1];
	}
	//將組合成的cs字符陣列轉化為字串得到48位的加鹽后形成的md5摘要密文
	string code = "";
	for (int i = 0; i < 48; i++)
	{
	  code.append(1,cs[i]);
	}
	return code;
}

4.3.4校驗加鹽后是否和原文一致

/**
 * 校驗加鹽后是否和原文一致
 * @param text : 進行摘要演算法的明文
 * @param encryptText  : 進行加鹽摘要演算法后的密文
 * @return 如果加鹽后和原明文text一致則回傳true,否則回傳false
 */
 bool EncryptUtil::verify( string text,  string encryptText) {
	char md5Text[32];
	char salt[16];
	//首先將加鹽后md5演算法摘要得到的encryptText密文進行拆分成一個32位的普通md5摘要演算法密文md5Text和一個16位的字串salt,
	//這里就是進行salt和md5摘要密文形成48位新的字串encryptText時的逆向操作,encryptText中salt[i] =  encryptText[ 3* i + 1], i = 0,1,2,...16
	for (int i = 0; i < 48; i += 3) {
		md5Text[i / 3 * 2] = encryptText[i];
		md5Text[i / 3 * 2 + 1] = encryptText[i + 2];
		salt[i / 3] = encryptText[i + 1];
	}
	//將拆分的16個隨機字串追salt加到明文text中
	for (int  i = 0; i < 16; i++)
	{
		text.append(1,salt[i]);
	}
	//使用md5摘要演算法進行生成密文得到enCs2
	string enCs2 = md5Hex(text);
	//比較兩個字串相等,如果相等則回傳true,只要有一個字符不相等就回傳false
	for (int i = 0; i < enCs2.size(); i++)
	{
		if (enCs2[i] != md5Text[i])
		{
			return false;
		}
	}
	return true;
}

4.3.5 逐個比較兩個密文相同位置上字符是否相同來計算兩個明文進行md5加密后的相似度,

/**
  逐個比較兩個密文相同位置上字符是否相同來計算兩個明文進行md5加密后的相似度,
 @param str1: md5摘要演算法后的字串
 @param str2:md5摘要演算法后的字串
 @return double: 回傳相似度
 */
double EncryptUtil::similarityDegree(const string& str1, const string& str2)
{
	if (str1.size() <= 0 && (str1.size() != str2.size()))
	{
		return 0;
	}
	int count = 0;
	//統計對應位置上字符相同的個數
	for (int i = 0; i < str1.size(); i++)
	{
		if (str1[i] == str2[i])
		{
			count++;
		}
	}
	//計算相似度
	double result = count * 1.0 / str1.size();
	return result;
}

4.3.6為了方便,將明文保存到檔案中,然后讀取檔案內容作為字串

string loadTextToString(const string textFilePath) {
	ifstream* ism = new ifstream(textFilePath,ios::app|ios::binary);
	if (ism->is_open())
	{
		string text = "";
		char ch;
		while (!ism->eof())
		{
			ism->get(ch);
			text.append(1,ch);
		}
		return text;
	}
	else {
		cout << "打開檔案失敗!" << endl;
		return "";
	}
}

5.驗證實驗內容

設計函式驗證獲取H0到H10的密文,并計算相似度,

string randomReplace(string text,int index,string replaceChar) {
	return text.replace(index,1,replaceChar);
}
void test() {
	string text = loadTextToString("hashtext.txt");
	cout << "需要加密的明文為:\n" << text << endl;
	EncryptUtil util;
	string md0 = util.encryptBySalt(text);
	cout << "md5加鹽后加密的md5值H0:" + md0 << endl;
	vector<string> vec;
	for (int i = 0; i < 10; i++)
	{
		//替換十個字符,每個原來的字符+1
		string  newStr =	randomReplace(text, i * 5,string(1, text[i * 5] + 1));
		//計算每個替換后字符的hash值
		 string encodeText =util.encryptBySalt(newStr);
		vec.push_back(encodeText);
	}
	for (size_t i = 0; i < 10; i++)
	{
		cout << "md5加鹽后加密的md5值H"+ to_string(i + 1) + ":" + vec[i]<< endl;
	}
	for (size_t i = 0; i < 10; i++)
	{
		//計算H0和H的相似度
		cout << "H0和H" + to_string(i + 1) + "的相似度:" + to_string(util.similarityDegree(md0, vec[i])) << endl;
	}
}

[外鏈圖片轉存中...(img-GMQD1fac-1607789521383)]

md5加鹽后加密的md5值H0:051293435697829a9bc1de2ff8ed0cb0a908706504302100
md5加鹽后加密的md5值H1:061233445697859a2bc8de3ff0ed0cb0a908706504302100
md5加鹽后加密的md5值H2:021203415697819a2bc1de9ff1ed6cb0a908706504302100
md5加鹽后加密的md5值H3:091243475677819a6bc4de1ff3ed0cb0a908706504302100
md5加鹽后加密的md5值H4:031203425687869a4bc8de6ff3ed0cb0a908706504302100
md5加鹽后加密的md5值H5:011253485697809a1bc5de5ff0ed7cb0a908706504302100
md5加鹽后加密的md5值H6:031203485607829a1bc5de3ff6ed6cb0a908706504302100
md5加鹽后加密的md5值H7:031203425617829a1bc5de0ff8ed0cb0a908706504302100
md5加鹽后加密的md5值H8:011293475627869a2bc0de0ff9ed0cb0a908706504302100
md5加鹽后加密的md5值H9:021223425667899a1bc2de7ff0ed0cb0a908706504302100
md5加鹽后加密的md5值H10:091293415627829a3bc6de5ff5ed0cb0a908706504302100
H0和H1的相似度:0.833333
H0和H2的相似度:0.833333
H0和H3的相似度:0.812500
H0和H4的相似度:0.812500
H0和H5的相似度:0.812500
H0和H6的相似度:0.812500
H0和H7的相似度:0.854167
H0和H8的相似度:0.833333
H0和H9的相似度:0.812500
H0和H10的相似度:0.854167

6.拓展

6.1驗證md5經過摘要演算法后和原來明文一致

void test9() {
	string text = loadTextToString("hashtext.txt");
	EncryptUtil util;
	string encodeText = util.encryptBySalt(text);
	string encodeText1 = util.encryptBySalt(text);
	cout << "md5加鹽后加密的md5值:" + encodeText << endl;
	cout << "md5加鹽后加密的md5值:" + encodeText1 << endl;
	if (encodeText.compare(encodeText1) == 0)
	{
		cout << "兩次生成的加鹽密文相等!" << endl;
	}
	else {
		cout << "兩次生成的加鹽密文不相等!" << endl;
	}
	bool fla = util.verify(text, encodeText);
	if (fla == true)
	{
		cout << "原來的明文和加鹽后的明文一致得到驗證!" << endl;
	}
	else {
		cout << "原來的明文和加鹽后的明文不一致!" << endl;
	}

}

[外鏈圖片轉存中...(img-eCm0qnl5-1607789521389)]

md5加鹽后加密的md5值:071263445647839a7bc0de0ff0ed0cb0a908706504302100
md5加鹽后加密的md5值:021253435637849a1bc6de1ff8ed9cb0a908706504302100
兩次生成的加鹽密文不相等!
原來的明文和加鹽后的明文一致得到驗證!

由結果知道,雖然加鹽后得到的密文不一樣,但是相同的明文產生的密文是可以通過驗證知道原來的明文是否一致的,

7.完整的代碼

#pragma once
#include<iostream>
#include<string>
#include"Md5.h"
#include<time.h>
using namespace std;

class EncryptUtil
{
public:
	EncryptUtil();
/**
*@param text:需要進行普通md5摘要演算法計算的明文
*@return string:回傳普通md5摘要算計算后的字串
*/
	string md5Decode(const string& text);
/**
產生鹽值字串salt
*/
	string createSalt();

/**
* 加鹽MD5
* @param text : 需要進行加鹽后摘要的text明文
* @return string:回傳進行摘要演算法后的字串
*/
	string encryptBySalt(const string& text);
/**
 * 獲取十六進制字串形式的MD5摘要
 * @param text: 需要進行摘要演算法的明文
 * @return string: 回傳進行普通md5摘要后的字串
 */
	string md5Hex(const string& src);
/**
 * 校驗加鹽后是否和原文一致
 * @param text : 進行摘要演算法的明文
 * @param encryptText  : 進行加鹽摘要演算法后的密文
 * @return 如果加鹽后和原明文text一致則回傳true,否則回傳false
 */
	bool verify( string text,  string md5Text);
/**
  逐個比較兩個密文相同位置上字符是否相同來計算兩個進行md5加密后的相似度,
 @param str1: md5摘要演算法后的字串
 @param str2:md5摘要演算法后的字串
 @return double: 回傳相似度
 */
	double similarityDegree(const string& str1, const string& str2);
	~EncryptUtil();
private:
	MD5* md5 = nullptr;
	
};


#include "EncryptUtil.h"



EncryptUtil::EncryptUtil()
{
	//初始化MD5這個類
	this->md5 = new MD5();
	//設定亂數種子,在產生鹽時可以產生亂數,
	srand(time(NULL));
}

/**
*@param text:需要進行普通md5摘要演算法計算的明文
*@return string:回傳普通md5摘要算計算后的字串
*/
string EncryptUtil::md5Decode(const string & text)
{
	//呼叫md5Hex(text)函式即可
	 return this->md5Hex(text);
}
/**
產生鹽值字串salt
*/
string EncryptUtil::createSalt()
{
	//隨機生成兩個亂數
	int num1 = rand() % 99999999;
	int num2 = rand() % 99999999;
	string salt = "";
	//將這兩個亂數轉換為字串后追加到salt中
	salt.append(to_string(num1));
	salt.append(to_string(num2));
	//salt不夠十六位就在后面加0
	int len = salt.size();
	if (len < 16) {
		for (int i = 0; i < 16 - len; i++) {
			salt.append("0");
		}
	}
	//cout << "產生的鹽值字串為:" <<salt<< endl;
	return salt;
}

/**
* 加鹽MD5
* @param text : 需要進行加鹽后摘要的text明文
* @return string:回傳進行摘要演算法后的字串
*/
string EncryptUtil::encryptBySalt(const string& text) {
	//產生鹽值字串salt
	string salt = createSalt();
	//將產生的salt追加到需要進行摘要的明文text中,得到由明文text和salt組成的字串
	string merge_str =  text + salt;
	//使用md5演算法進行摘要計算得到相應的32位的密文
	string encodeText = md5Hex(merge_str);
	char cs [48];
	//將32位的密文和產生的16位的salt進行重組形成新的48位的字串,
	//這里表示,在新的字符陣列cs中,salt中的字符添加到cs字符陣列中位置是i*3+1的位置,其它位置由摘要密文encodeText中的字符填充
	for (int i = 0; i < 48; i += 3) {
		cs[i] = encodeText[i / 3 * 2];
		char c = salt[i / 3];
		cs[i + 1] = c;
		cs[i + 2] = encodeText[i / 3 * 2 + 1];
	}
	//將組合成的cs字符陣列轉化為字串得到48位的加鹽后形成的md5摘要密文
	string code = "";
	for (int i = 0; i < 48; i++)
	{
	  code.append(1,cs[i]);
	}
	return code;
}
/**
 * 獲取十六進制字串形式的MD5摘要
 * @param text: 需要進行摘要演算法的明文
 * @return string: 回傳進行普通md5摘要后的字串
 */
string EncryptUtil::md5Hex(const string& text) {
	//首先更新md5中需要進行摘要的字串
	this->md5->update(text);
	//使用md5的方法生成對應的摘要后的字串
	return this->md5->toString();

}
/**
 * 校驗加鹽后是否和原文一致
 * @param text : 進行摘要演算法的明文
 * @param encryptText  : 進行加鹽摘要演算法后的密文
 * @return 如果加鹽后和原明文text一致則回傳true,否則回傳false
 */
 bool EncryptUtil::verify( string text,  string encryptText) {
	char md5Text[32];
	char salt[16];
	//首先將加鹽后md5演算法摘要得到的encryptText密文進行拆分成一個32位的普通md5摘要演算法密文md5Text和一個16位的字串salt,
	//這里就是進行salt和md5摘要密文形成48位新的字串encryptText時的逆向操作,encryptText中salt[i] =  encryptText[ 3* i + 1], i = 0,1,2,...16
	for (int i = 0; i < 48; i += 3) {
		md5Text[i / 3 * 2] = encryptText[i];
		md5Text[i / 3 * 2 + 1] = encryptText[i + 2];
		salt[i / 3] = encryptText[i + 1];
	}
	//將拆分的16個隨機字串追salt加到明文text中
	for (int  i = 0; i < 16; i++)
	{
		text.append(1,salt[i]);
	}
	//使用md5摘要演算法進行生成密文得到enCs2
	string enCs2 = md5Hex(text);
	//比較兩個字串相等,如果相等則回傳true,只要有一個字符不相等就回傳false
	for (int i = 0; i < enCs2.size(); i++)
	{
		if (enCs2[i] != md5Text[i])
		{
			return false;
		}
	}
	return true;
}
 /**
  逐個比較兩個密文相同位置上字符是否相同來計算兩個明文進行md5加密后的相似度,
 @param str1: md5摘要演算法后的字串
 @param str2:md5摘要演算法后的字串
 @return double: 回傳相似度
 */
double EncryptUtil::similarityDegree(const string& str1, const string& str2)
{
	if (str1.size() <= 0 && (str1.size() != str2.size()))
	{
		return 0;
	}
	int count = 0;
	//統計對應位置上字符相同的個數
	for (int i = 0; i < str1.size(); i++)
	{
		if (str1[i] == str2[i])
		{
			count++;
		}
	}
	//計算相似度
	double result = count * 1.0 / str1.size();
	return result;
}

 

EncryptUtil::~EncryptUtil()
{
}

#pragma once
#ifndef MD5_H
#define MD5_H
#include <string>
#include <fstream>
using namespace std;
/* Type define */
typedef unsigned char byte;
typedef unsigned int uint32;



/* MD5 declaration. */
class MD5 {
public:
	MD5();
	MD5(const void *input, size_t length);
	MD5(const string &str);
	MD5(ifstream &in);
	void update(const void *input, size_t length);
	void update(const string &str);
	void update(ifstream &in);
	const byte* digest();
	string toString();
	
private:
	void reset();
	void update(const byte *input, size_t length);
	void final();
	void transform(const byte block[64]);
	void encode(const uint32 *input, byte *output, size_t length);
	void decode(const byte *input, uint32 *output, size_t length);
	string bytesToHexString(const byte *input, size_t length);
	/* class uncopyable */
	MD5(const MD5&);
	MD5& operator=(const MD5&);
private:
	uint32 _state[4];	/* state (ABCD) */
	uint32 _count[2];	/* number of bits, modulo 2^64 (low-order word first) */
	byte _buffer[64];	/* input buffer */
	byte _digest[16];	/* message digest */
	bool _finished;		/* calculate finished ? */
	static const byte PADDING[64];	/* padding for calculate */
	static const char HEX[16];
	static const size_t BUFFER_SIZE = 1024;
};

#endif/*MD5_H*/
#include "md5.h"



/* Constants for MD5Transform routine. */
#define S11 7
#define S12 12
#define S13 17
#define S14 22
#define S21 5
#define S22 9
#define S23 14
#define S24 20
#define S31 4
#define S32 11
#define S33 16
#define S34 23
#define S41 6
#define S42 10
#define S43 15
#define S44 21


/* F, G, H and I are basic MD5 functions.
*/
#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))
#define G(x, y, z) (((x) & (z)) | ((y) & (~z)))
#define H(x, y, z) ((x) ^ (y) ^ (z))
#define I(x, y, z) ((y) ^ ((x) | (~z)))

/* ROTATE_LEFT rotates x left n bits.
*/
#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))

/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4.
Rotation is separate from addition to prevent recomputation.
*/
#define FF(a, b, c, d, x, s, ac) { \
	(a) += F ((b), (c), (d)) + (x) + ac; \
	(a) = ROTATE_LEFT ((a), (s)); \
	(a) += (b); \
}
#define GG(a, b, c, d, x, s, ac) { \
	(a) += G ((b), (c), (d)) + (x) + ac; \
	(a) = ROTATE_LEFT ((a), (s)); \
	(a) += (b); \
}
#define HH(a, b, c, d, x, s, ac) { \
	(a) += H ((b), (c), (d)) + (x) + ac; \
	(a) = ROTATE_LEFT ((a), (s)); \
	(a) += (b); \
}
#define II(a, b, c, d, x, s, ac) { \
	(a) += I ((b), (c), (d)) + (x) + ac; \
	(a) = ROTATE_LEFT ((a), (s)); \
	(a) += (b); \
}
const byte MD5::PADDING[64] = { 0x80 };
const char MD5::HEX[16] = {
	'0', '1', '2', '3',
	'4', '5', '6', '7',
	'8', '9', 'a', 'b',
	'c', 'd', 'e', 'f'
};

/* Default construct. */
MD5::MD5() {
	reset();
}

/* Construct a MD5 object with a input buffer. */
MD5::MD5(const void *input, size_t length) {
	reset();
	update(input, length);
}

/* Construct a MD5 object with a string. */
MD5::MD5(const string &str) {
	reset();
	update(str);
}

/* Construct a MD5 object with a file. */
MD5::MD5(ifstream &in) {
	reset();
	update(in);
}

/* Return the message-digest */
const byte* MD5::digest() {
	if (!_finished) {
		_finished = true;
		final();
	}
	return _digest;
}

/* Reset the calculate state */
void MD5::reset() {

	_finished = false;
	/* reset number of bits. */
	_count[0] = _count[1] = 0;
	/* Load magic initialization constants. */
	_state[0] = 0x67452301;
	_state[1] = 0xefcdab89;
	_state[2] = 0x98badcfe;
	_state[3] = 0x10325476;
	
}

/* Updating the context with a input buffer. */
void MD5::update(const void *input, size_t length) {
	update((const byte*)input, length);
}

/* Updating the context with a string. */
void MD5::update(const string &str) {
	update((const byte*)str.c_str(), str.length());
}

/* Updating the context with a file. */
void MD5::update(ifstream &in) {

	if (!in)
		return;

	std::streamsize length;
	char buffer[BUFFER_SIZE];
	while (!in.eof()) {
		in.read(buffer, BUFFER_SIZE);
		length = in.gcount();
		if (length > 0)
			update(buffer, length);
	}
	in.close();
}

/* MD5 block update operation. Continues an MD5 message-digest
operation, processing another message block, and updating the
context.
*/
void MD5::update(const byte *input, size_t length) {

	uint32 i, index, partLen;

	//_finished = false;
	this->reset();
	/* Compute number of bytes mod 64 */
	index = (uint32)((_count[0] >> 3) & 0x3f);

	/* update number of bits */
	if ((_count[0] += ((uint32)length << 3)) < ((uint32)length << 3))
		_count[1]++;
	_count[1] += ((uint32)length >> 29);

	partLen = 64 - index;

	/* transform as many times as possible. */
	if (length >= partLen) {

		memcpy(&_buffer[index], input, partLen);
		transform(_buffer);

		for (i = partLen; i + 63 < length; i += 64)
			transform(&input[i]);
		index = 0;

	}
	else {
		i = 0;
	}

	/* Buffer remaining input */
	memcpy(&_buffer[index], &input[i], length - i);
}

/* MD5 finalization. Ends an MD5 message-_digest operation, writing the
the message _digest and zeroizing the context.
*/
void MD5::final() {

	byte bits[8];
	uint32 oldState[4];
	uint32 oldCount[2];
	uint32 index, padLen;

	/* Save current state and count. */
	memcpy(oldState, _state, 16);
	memcpy(oldCount, _count, 8);

	/* Save number of bits */
	encode(_count, bits, 8);

	/* Pad out to 56 mod 64. */
	index = (uint32)((_count[0] >> 3) & 0x3f);
	padLen = (index < 56) ? (56 - index) : (120 - index);
	update(PADDING, padLen);

	/* Append length (before padding) */
	update(bits, 8);

	/* Store state in digest */
	encode(_state, _digest, 16);

	/* Restore current state and count. */
	memcpy(_state, oldState, 16);
	memcpy(_count, oldCount, 8);
}

/* MD5 basic transformation. Transforms _state based on block. */
void MD5::transform(const byte block[64]) {

	uint32 a = _state[0], b = _state[1], c = _state[2], d = _state[3], x[16];

	decode(block, x, 64);

	/* Round 1 */
	FF(a, b, c, d, x[0], S11, 0xd76aa478); /* 1 */
	FF(d, a, b, c, x[1], S12, 0xe8c7b756); /* 2 */
	FF(c, d, a, b, x[2], S13, 0x242070db); /* 3 */
	FF(b, c, d, a, x[3], S14, 0xc1bdceee); /* 4 */
	FF(a, b, c, d, x[4], S11, 0xf57c0faf); /* 5 */
	FF(d, a, b, c, x[5], S12, 0x4787c62a); /* 6 */
	FF(c, d, a, b, x[6], S13, 0xa8304613); /* 7 */
	FF(b, c, d, a, x[7], S14, 0xfd469501); /* 8 */
	FF(a, b, c, d, x[8], S11, 0x698098d8); /* 9 */
	FF(d, a, b, c, x[9], S12, 0x8b44f7af); /* 10 */
	FF(c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */
	FF(b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */
	FF(a, b, c, d, x[12], S11, 0x6b901122); /* 13 */
	FF(d, a, b, c, x[13], S12, 0xfd987193); /* 14 */
	FF(c, d, a, b, x[14], S13, 0xa679438e); /* 15 */
	FF(b, c, d, a, x[15], S14, 0x49b40821); /* 16 */

	/* Round 2 */
	GG(a, b, c, d, x[1], S21, 0xf61e2562); /* 17 */
	GG(d, a, b, c, x[6], S22, 0xc040b340); /* 18 */
	GG(c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */
	GG(b, c, d, a, x[0], S24, 0xe9b6c7aa); /* 20 */
	GG(a, b, c, d, x[5], S21, 0xd62f105d); /* 21 */
	GG(d, a, b, c, x[10], S22, 0x2441453); /* 22 */
	GG(c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */
	GG(b, c, d, a, x[4], S24, 0xe7d3fbc8); /* 24 */
	GG(a, b, c, d, x[9], S21, 0x21e1cde6); /* 25 */
	GG(d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */
	GG(c, d, a, b, x[3], S23, 0xf4d50d87); /* 27 */
	GG(b, c, d, a, x[8], S24, 0x455a14ed); /* 28 */
	GG(a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */
	GG(d, a, b, c, x[2], S22, 0xfcefa3f8); /* 30 */
	GG(c, d, a, b, x[7], S23, 0x676f02d9); /* 31 */
	GG(b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */

	/* Round 3 */
	HH(a, b, c, d, x[5], S31, 0xfffa3942); /* 33 */
	HH(d, a, b, c, x[8], S32, 0x8771f681); /* 34 */
	HH(c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */
	HH(b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */
	HH(a, b, c, d, x[1], S31, 0xa4beea44); /* 37 */
	HH(d, a, b, c, x[4], S32, 0x4bdecfa9); /* 38 */
	HH(c, d, a, b, x[7], S33, 0xf6bb4b60); /* 39 */
	HH(b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */
	HH(a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */
	HH(d, a, b, c, x[0], S32, 0xeaa127fa); /* 42 */
	HH(c, d, a, b, x[3], S33, 0xd4ef3085); /* 43 */
	HH(b, c, d, a, x[6], S34, 0x4881d05); /* 44 */
	HH(a, b, c, d, x[9], S31, 0xd9d4d039); /* 45 */
	HH(d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */
	HH(c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */
	HH(b, c, d, a, x[2], S34, 0xc4ac5665); /* 48 */

	/* Round 4 */
	II(a, b, c, d, x[0], S41, 0xf4292244); /* 49 */
	II(d, a, b, c, x[7], S42, 0x432aff97); /* 50 */
	II(c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */
	II(b, c, d, a, x[5], S44, 0xfc93a039); /* 52 */
	II(a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */
	II(d, a, b, c, x[3], S42, 0x8f0ccc92); /* 54 */
	II(c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */
	II(b, c, d, a, x[1], S44, 0x85845dd1); /* 56 */
	II(a, b, c, d, x[8], S41, 0x6fa87e4f); /* 57 */
	II(d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */
	II(c, d, a, b, x[6], S43, 0xa3014314); /* 59 */
	II(b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */
	II(a, b, c, d, x[4], S41, 0xf7537e82); /* 61 */
	II(d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */
	II(c, d, a, b, x[2], S43, 0x2ad7d2bb); /* 63 */
	II(b, c, d, a, x[9], S44, 0xeb86d391); /* 64 */

	_state[0] += a;
	_state[1] += b;
	_state[2] += c;
	_state[3] += d;
}

/* Encodes input (ulong) into output (byte). Assumes length is
a multiple of 4.
*/
void MD5::encode(const uint32 *input, byte *output, size_t length) {

	for (size_t i = 0, j = 0; j < length; i++, j += 4) {
		output[j] = (byte)(input[i] & 0xff);
		output[j + 1] = (byte)((input[i] >> 8) & 0xff);
		output[j + 2] = (byte)((input[i] >> 16) & 0xff);
		output[j + 3] = (byte)((input[i] >> 24) & 0xff);
	}
}

/* Decodes input (byte) into output (ulong). Assumes length is
a multiple of 4.
*/
void MD5::decode(const byte *input, uint32 *output, size_t length) {

	for (size_t i = 0, j = 0; j < length; i++, j += 4) {
		output[i] = ((uint32)input[j]) | (((uint32)input[j + 1]) << 8) |
			(((uint32)input[j + 2]) << 16) | (((uint32)input[j + 3]) << 24);
	}
}

/* Convert byte array to hex string. */
string MD5::bytesToHexString(const byte *input, size_t length) {
	string str;
	str.reserve(length << 1);
	for (size_t i = 0; i < length; i++) {
		int t = input[i];
		int a = t / 16;
		int b = t % 16;
		str.append(1, HEX[a]);
		str.append(1, HEX[b]);
	}
	return str;
}

MD5 & MD5::operator=(const MD5 &)
{
	return *this;
}

/* Convert digest to string value */
string MD5::toString() {
	return bytesToHexString(digest(), 16);
}

測驗檔案

#include<iostream>
#include"Md5.h"
#include<string>
#include<fstream>
#include"EncryptUtil.h"
#include<time.h>
#include<vector>
using namespace std;
 
string loadTextToString(const string textFilePath) {
	ifstream* ism = new ifstream(textFilePath,ios::app|ios::binary);
	if (ism->is_open())
	{
		
		string text = "";
		char ch;
		while (!ism->eof())
		{
			ism->get(ch);
			text.append(1,ch);
		}
		return text;
	}
	else {
		cout << "打開檔案失敗!" << endl;
		return "";
	}
}

void test2() {
	
	
}
void test1() {
	string text = loadTextToString("hashtext.txt");
	MD5 md5(text);
	cout << md5.toString();
}
void test3() {
	string s = "11";
	s.append(to_string(121));
	cout << s;
}
void test4() {
	string text = loadTextToString("hashtext.txt");
	EncryptUtil util;
	string str = util.md5Decode(text);
	cout << "\n普通加密后的md5值:" + str << endl;
	cout << "普通加密后的md5值:" + util.md5Decode(text) << endl;
	string encodeText =util.encryptBySalt(text);
	string encodeText1 = util.encryptBySalt(text);
	cout << "md5加鹽后加密的md5值:" + encodeText << endl;
	cout << "md5加鹽后加密的md5值:" + encodeText1 << endl;
	if (encodeText.compare(encodeText1) == 0)
	{
		cout << "兩次生成的加鹽秘鑰相等!" << endl;
	}
	else {
		cout << "兩次生成的加鹽秘鑰不相等!" << endl;
	}
	bool fla = util.verify(text,encodeText);
	if (fla == true)
	{
		cout << "相等!"<<endl;
	}
	else {
		cout << "不相等" << endl;
	}

}
void test5() {
	string text = loadTextToString("hashtext.txt");
	cout << endl;

}
void test6() {
	srand(time(NULL));
	cout << rand() % 99999999 <<"  "<<  rand()% 99999999 <<endl;
	cout << rand() % 99999999 << "  " << rand() % 99999999 << endl;
}
void test7() {
	string text = loadTextToString("hashtext.txt");
	cout << "需要加密的明文為:\n" << text<<endl;
	EncryptUtil util;
	string encodeText = util.encryptBySalt(text);
	cout << "md5加鹽后加密的md5值:" + encodeText << endl;
	

}
string randomReplace(string text,int index,string replaceChar) {

	
	
	return text.replace(index,1,replaceChar);
}
void test() {
	string text = loadTextToString("hashtext.txt");
	cout << "需要加密的明文為:\n" << text << endl;
	EncryptUtil util;
	string md0 = util.encryptBySalt(text);
	cout << "md5加鹽后加密的md5值H0:" + md0 << endl;
	vector<string> vec;
	for (int i = 0; i < 10; i++)
	{
		//替換十個字符,每個原來的字符+1
		string  newStr =	randomReplace(text, i * 5,string(1, text[i * 5] + 1));
		//計算每個替換后字符的hash值
		 string encodeText =util.encryptBySalt(newStr);
		vec.push_back(encodeText);
	}
	for (size_t i = 0; i < 10; i++)
	{
		cout << "md5加鹽后加密的md5值H"+ to_string(i + 1) + ":" + vec[i]<< endl;
	}
	for (size_t i = 0; i < 10; i++)
	{
		//計算H0和H的相似度
		cout << "H0和H" + to_string(i + 1) + "的相似度:" + to_string(util.similarityDegree(md0, vec[i])) << endl;
	}
}
void test8() {
	string text = loadTextToString("hashtext.txt");
	cout << "需要加密的明文為:\n" << text << endl;
	EncryptUtil util;
	string md0 = util.encryptBySalt(text);
	cout << "md5加鹽后加密的md5值H0:" + md0 << endl;
	string text1 = 	text.replace(10,1,"a");
	string md1 = util.encryptBySalt(text1);
	cout << "md5加鹽后加密的md5值H1:" + md1 << endl;
	cout << "md0和md1相似度:" << util.similarityDegree(md0,md1)<<endl;

}
void test9() {
	string text = loadTextToString("hashtext.txt");
	EncryptUtil util;
	string encodeText = util.encryptBySalt(text);
	string encodeText1 = util.encryptBySalt(text);
	cout << "md5加鹽后加密的md5值:" + encodeText << endl;
	cout << "md5加鹽后加密的md5值:" + encodeText1 << endl;
	if (encodeText.compare(encodeText1) == 0)
	{
		cout << "兩次生成的加鹽密文相等!" << endl;
	}
	else {
		cout << "兩次生成的加鹽密文不相等!" << endl;
	}
	bool fla = util.verify(text, encodeText);
	if (fla == true)
	{
		cout << "原來的明文和加鹽后的明文一致得到驗證!" << endl;
	}
	else {
		cout << "原來的明文和加鹽后的明文不一致!" << endl;
	}

}
int main() {
	// test8();
	
		
	test9();
	system("pause");
	return 0;
}

8.看完記得點贊哦,筆記整理不易,

9.本博客已經同步到個人博客,如有需要請移步:http://moyisuiying.com/index.php/cppstudy/information-secure/433.html

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/234362.html

標籤:其他

上一篇:兩大AI技術集于一身,有道詞典筆3從0到1的飛躍

下一篇:Lecture1-Shell

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more