• 算法系列2《RSA》


         

    1. RSA介绍

        RSA公钥加密算法是1977年由Ron Rivest、Adi Shamirh和LenAdleman在(美国麻省理工学院)开发的。RSA取名来自开发他们三者的名字。RSA是目前最有影响力的公钥加密算法,它能够抵抗到目前为止已知的所有密码攻击,已被ISO推荐为公钥数据加密标准。RSA算法基于一个十分简单的数论事实:将两个大素数相乘十分容易,但那时想要对其乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥。

                        

        RSA的安全性依赖于大数的因子分解,但并没有从理论上证明破译RSA的难度与大数分解难度等价。即RSA的重大缺陷是无法从理论上把握它的保密性能如何,而且密码学界多数人士倾向于因子分解不是NPC问题。

     

        RSA的缺点主要有:A)产生密钥很麻烦,受到素数产生技术的限制,因而难以做到一次一密。B)分组长度太大,为保证安全性,n 至少也要600BITS以上,使运算代价很高,尤其是速度较慢,较对称密码算法慢几个数量级;且随着大数分解技术的发展,这个长度还在增加,不利于数据格式的标准化。目前,SET(Secure Electronic Transaction)协议中要求CA采用2048BITS长的密钥,其他实体使用1024比特的密钥。C)RSA密钥长度随着保密级别提高,增加很快。下表列出了对同一安全级别所对应的密钥长度。

     

          保密级别         

            对称密钥长度(bit)         

              RSA密钥长度(bit)           

               ECC密钥长度(bit)           

              保密年限              

    80

    80

    1024

    160

    2010

    112

    112

    2048

    224

    2030

    128

    128

    3072

    256

    2040

    192

    192

    7680

    384

    2080

    256

    256

    15360

    512

    2120

     

     

        该可逆算法是经批准用于加密和生成数字签名的算法。公钥指数的值只允许是3和216+1。该算法产生的密文及数字签名的长度与模长相等。

     

    描述

    最大长度

    认证中心公钥模

    248字节

    发卡行公钥模

    248字节

    IC卡公钥模

    248字节

     

        认证中心公钥模的长度NCA,发卡行公钥模的长度NI,IC卡公钥模的长度NIC,必须满足NIC≤NI≤NCA和NPE≤NI≤NCA。

     

        注: IC卡中一个记录的长度最长不超过254字节(包括Tag和Length),因而实际IC卡公钥和发卡行公钥长度应小于最大长度248字节。命令数据长度最长为255字节,响应数据最长为256字节,动态签名数据作为IC卡响应数据,也限制了IC卡公钥的最大长度。

     


         如卡片支持DDA和CDA,包含IC卡证书的记录模板的长度,即IC卡公钥证书长度加上证书(’9F46’)和记录模板(’70’)的Tag和Length不超过254字节,则IC卡公钥长度不超过247字节,因而发卡行公钥长度最大长度也不超过247字节。

        根据发卡行应用数据长度不同,IC卡公钥最大长度在205到240之间。如果Generate AC命令响应包含其他可选数据,IC卡公钥最大长度还应减去这些数据的长度(包括Tag和Length)。如果卡片应用支持INTERNALAUTHENTICATION格式二,IC卡公钥最大长度还应减去7字节。

     

        在选择公钥模长时,应该考虑到比较密钥的生命周期同预期的因数分解进程。

        发卡行公钥指数和IC卡公钥指数的值由发卡行决定。认证中心,发卡行和IC卡公钥指数必须等于3或216+1。

        标识本数字签名算法的公钥算法标识必须编码为十六进制‘01’。





    2.RSA算法源码


    <<RSA.h>>

    // LargeInt.h
    // 大整数类的声明,包含了质数检查,随机数生成,以及RSA密钥生成及加密解密函数。
    
    //////////////////////////////////////////////////////////////////////////////
    
    //RSA加密里所说的1024 位加密、2048位加密,是不是只要 
    //CLargeInt::CreateRandom(e,1024); 
    //这句代码里的参数设置成1024、2048就行了? 
    //
    ////不是的,2048bit,是指P*Q的积是2048bit,也就是说,当你P和Q都设置为1024的时候,加密强度是2048bit,事实上P和Q的位数可以不等,并且过分接近的P和Q将导致破解难度极大降低。
    //
    //
    //_data 和 _len 这两个变量到底起什么作用? 
    //_data 占了4092个字节的大小,_len=64时,只输出了512个字节。 
    ////_data和_len其实是模拟了一个动态数组,用来容纳长度不定的大整数。_data 占据的大小是预先设置好的,这个数值在LargeInt.h中有定义:
    //enum LIMIT{ LENGTH = 0x3FF,SMALLPRIMES = 5000};
    //LENGTH 可以根据需要修改。
    //_len是以4字节为单位的目前已经占用的数组的长度,_len = 64就是当前大整数的长度是64*4 = 256字节
    //输出的字节是以16进制表示的字符串,每个字节需要两个符号来表示,所以长度是256*2 = 512字节。
    
    
    
    //必须保证T_DWORD是4字节。
    typedef unsigned long T_DWORD;
    
    class CLargeInt  
    {
    public:
    	//说明:
    	//基本的操作函数之所以没有设置成运算符的重载,主要是因为,
    	//大数运算的过程中,经常需要额外的参数,例如错位相加减的offset值。
    	//以及某些额外的结果
    	//例如,除法运算的商和余数。
    	//为了代码尽量通用,都写成了静态成员函数。
    	//运算符的重载可以通过对这些静态成员函数进一步封装来实现。
    
    	//复制
    	static void Copy(const CLargeInt& source,CLargeInt& target);
    
    	//比较
    	static bool LargerEqual(const CLargeInt& source,const CLargeInt& target,T_DWORD offset = 0);
    	static bool Equal(const CLargeInt& source,const CLargeInt& target);
    
    	//和T_DWORD类型交互的底层操作函数
    	static void Mul(const CLargeInt& faciend,T_DWORD multiplier,CLargeInt& product);
    	static void Div(const CLargeInt& dividend,T_DWORD divisor,CLargeInt& quotient,T_DWORD& residual);
    
    	//移位:
    	//右移一位,用来取代 /2 操作。
    	static void RCR(CLargeInt& n);
    	//左移一位,用来取代 *2 操作。
    	static void RCL(CLargeInt& n);
    
    	//CLargeInt类型底层操作函数,
    	static void Add(const CLargeInt& augend, const CLargeInt& addend, CLargeInt& sum,T_DWORD offset = 0);
    	static void Sub(const CLargeInt& minuend,const CLargeInt& subtrahend, CLargeInt& difference,T_DWORD offset = 0);
    	static void Mul(const CLargeInt& faciend, const CLargeInt& multiplier, CLargeInt& product);
    	static void Div(const CLargeInt& dividend, const CLargeInt& divisor, CLargeInt& quotient,CLargeInt& residual);
    	static void ExpMod(const CLargeInt& source, const CLargeInt& exponent, const CLargeInt& modulo,CLargeInt& result);
    
    	//高阶的测试
    	static bool RabinMillerTest(const CLargeInt& source, const CLargeInt& base);
    	static bool Coprime(const CLargeInt &source, const CLargeInt &target);
    
    	//RSA算法相关函数:
    	static bool IsPrime(const CLargeInt &n);
    	static void CreatePrime(CLargeInt &n);
    	static bool RSACreate( const CLargeInt &p,const CLargeInt & q,const CLargeInt &e,CLargeInt &d,CLargeInt &n);		
    	static void RSAEncode( const CLargeInt &n,const CLargeInt &d,const CLargeInt &m,CLargeInt &c);
    	static void RSADecode( const CLargeInt &n,const CLargeInt &e,const CLargeInt &c,CLargeInt &m);
    	static string RSADecode(const char* N, const char* E, const char* I);
    	//建立随机数。
    	static void CreateRandom(CLargeInt &n,T_DWORD bitcount);
    public:
    	CLargeInt(T_DWORD value = 0);
    	CLargeInt(const char* str);
    	CLargeInt(const CLargeInt& other);
    
    	~CLargeInt();
    
    private:
    	enum LIMIT{ LENGTH = 0x3FF, SMALLPRIMES = 5000};
    	T_DWORD _len;
    	mutable T_DWORD _data[LENGTH]; //之所以用 mutable 是因为需要在计算中获取指针,并且有时还需要在不改变数值的情况下修改某些数组元素。
    
    	//辅助用的小质数表和相关的函数。
    	static std::vector<T_DWORD> _smallPrimes;
    	static void InitSmallPrimes(T_DWORD count = SMALLPRIMES);
    	static bool SmallPrimeTest(const CLargeInt& n);
    	string GetHexStr();
    };
    
    
    // LARGEINT_HEADER


    <<RSA.CPP>>


    std::vector<T_DWORD> CLargeInt::_smallPrimes;
    void CLargeInt::InitSmallPrimes(T_DWORD	count)
    {
    	if (!_smallPrimes.size()) //尚未初始化
    	{
    		_smallPrimes.reserve(count);
    		_smallPrimes.push_back(3);
    		_smallPrimes.push_back(5);
    		_smallPrimes.push_back(7);
    		_smallPrimes.push_back(11);
    		_smallPrimes.push_back(13);
    		T_DWORD	i = 0;
    		for( i = 17; _smallPrimes.size() < count; i+= 2)
    		{
    			bool bePrime = true;
    			for( T_DWORD j = 0; j <	_smallPrimes.size(); j++ )
    			{
    				if( ( _smallPrimes[j] *	_smallPrimes[j]	) > i )	//已经不可能了。
    					break;
    				if( (i % _smallPrimes[j]) == 0)	//可以整除
    					bePrime	= false;				
    			}
    			if( bePrime ) //是素数
    			{
    				_smallPrimes.push_back(i);
    			}
    		}
    	}
    }
    
    bool CLargeInt::SmallPrimeTest(const CLargeInt&	n)
    {
    	if (n._data[0] == 2 && n._len == 1) 
    	{
    		return true; //2是质数。
    	}
    
    	if (n._len == 1	&& n._data[0] <= _smallPrimes.back()) //整数比表中的数据小。
    	{
    		if (std::binary_search(_smallPrimes.begin(),_smallPrimes.end(),n._data[0])) //找到,n存在于质数表中。
    		{
    			return true; //通过测试,数n是质数。
    		}
    
    		return false; //没有通过,n是合数。
    	}
    	else
    	{
    		CLargeInt temp;
    		T_DWORD	r;
    		for( T_DWORD i = 0; i <	_smallPrimes.size(); i++)
    		{
    			Div(n,_smallPrimes[i],temp,r); //依次检查是否能整除。
    			if( r == 0) return false; //能整除,说明 n 有小于它的质因子。n 是合数。
    		}
    
    		return true; //	n 没有表中已有的质因子,n有可能是质数。
    	}
    }
    
    bool CLargeInt::IsPrime(const CLargeInt	&n)
    {
    	InitSmallPrimes();
    	if (n._data[0]&1) //奇数
    	{
    		if (n._len == 1	&& n._data[0] <= _smallPrimes.back())
    		{
    			return SmallPrimeTest(n);  //是小质数表中的一个质数。
    		}
    		else
    		{
    			if( SmallPrimeTest(n) )	//通过小质数测试
    			{
    				for( int i = 0;	i < 5; i++)
    				{
    					CLargeInt a(_smallPrimes[ _smallPrimes.size() -	i]);
    					if( !RabinMillerTest(n,a) ) return false;
    				}
    
    				return true; //99.9%的可能是质数。
    			}
    			else 
    			{
    				return false;
    			}
    		}
    	}
    
    	return false;
    }
    
    void CLargeInt::CreatePrime(CLargeInt &n)
    {
    	n._data[0] |= 0x01;
    	while (!IsPrime(n))
    	{
    		Add(n, 2, n); //步进2,寻找最接近的下一个质数。这个算法并不是寻找质数最好的算法,但是它可以保证寻找到相邻的质数。
    	}
    }
    
    CLargeInt::CLargeInt(T_DWORD value) : _len(1)
    {
    	_data[0] = value;
    }
    
    CLargeInt::CLargeInt(const CLargeInt& other)
    {
    	Copy(other,*this);
    }
    
    CLargeInt::CLargeInt(const char	* str):	_len(1)
    {
    	_data[0] = 0;
    
    	//if(str && strlen(str) >	2 && ( strncmp(str,"0x",2) == 0	|| strncmp(str,"0X",2) == 0 ) )
    	if (str)
    	{
    		CLargeInt temp;
    		char ch;
    		T_DWORD	n = 0;
    		const char * p = str;
    		while (*p)
    		{
    			ch	= *p;
    			if ((ch >= '0') && (ch <= '9'))
    			{
    				n = ch - '0';
    			}
    			else if ((ch >= 'a') && (ch <= 'f'))    
    			{
    				n = ch - 'a' + 10;       //a->f分别代表10->15   a的AscII为97,所以这里应该减去87
    			}
    			else if ((ch >= 'A') && (ch <= 'F'))
    			{
    				n = ch - 'A' + 10;       //同上
    			}
    			p++;
    			Mul(*this, 16, temp);
    			Add(temp, n, *this);
    		}
    	}
    	//else
    	//{//这里应该用来处理除了0x开头的16进制字符串的情况。
    	//	__asm int 3;
    	//}
    }
    
    CLargeInt::~CLargeInt()
    {
    
    }
    
    void CLargeInt::Copy(const CLargeInt& source, CLargeInt& target)
    {
    	if (&source != &target) //避免自身拷贝。
    	{
    		target._len = source._len;
    
    		//相信库函数能提供最高效的实现。
    		memcpy(target._data, source._data, source._len * 4);
    	}
    }
    
    bool CLargeInt::Equal(const CLargeInt& source, const CLargeInt& target)
    {
    	if (source._len	== target._len)
    	{
    		//相信库函数能提供最高效的实现。
    		return !(memcmp(source._data, target._data,source._len * 4));
    	}
    	else
    	{
    		return false;
    	}
    }
    
    bool CLargeInt::LargerEqual(const CLargeInt& source,const CLargeInt& target,T_DWORD offset )
    {
    	if( source._len	> (target._len + offset) ) return true;
    	else if( source._len < (target._len + offset) )	return false;
    	else
    	{
    		int end	= offset;
    		for( int i = source._len - 1; i	>= end ; i--)
    		{
    			T_DWORD	ii = i - offset;
    			if( source._data[i] > target._data[ii] ) return	true; //大于
    			else if( source._data[i] < target._data[ii] )	return false;
    		}
    		return true; //相等。
    	}
    
    }
    
    void CLargeInt::Add(const CLargeInt &augend, const CLargeInt &addend, CLargeInt	&sum,T_DWORD offset)
    {
    	T_DWORD	len,len1,len2; //len绝对不能<1,否则会出错。
    	T_DWORD	*p1 = 0;
    	T_DWORD	*p2 = 0;
    	T_DWORD	*p3 = 0;
    
    	if( augend._len	>= (addend._len+offset)	)
    	{
    		len = augend._len - offset;
    		len1 = addend._len;
    		len2 =	len - len1;
    		p1 = augend._data+offset;
    		p2 = addend._data;
    		p3 = sum._data+offset;
    	}
    	else
    	{
    		len = addend._len;
    		if( augend._len	<= offset )
    		{
    			augend._data[offset] = 0;
    			len1 = 1;
    			len2 = addend._len - len1;
    		}
    		else
    		{
    			len1 = augend._len - offset;
    			len2 = addend._len - len1;
    		}		
    		p1 = addend._data;
    		p2 = augend._data + offset;
    		p3 = sum._data + offset;
    	}
    
    	__asm
    	{
    		pushad;
    		//载入计算地址
    		mov esi,p1;
    		mov ebx,p2;
    		mov edi,p3;
    
    		mov eax,len1;
    		sal eax,2;
    
    		//计算末地址
    		add esi,eax;
    		add ebx,eax;
    		add edi,eax;
    
    		mov ecx,eax;
    		add ecx,4*7;
    		and ecx,0xFFFFFFE0; //ecx - ecx%32,这里的ecx就变成了偏移址,同时也是累加器。
    		neg ecx; //取负,用作累加。
    
    		sar eax,2; //回复到原来的长度
    
    		neg eax; //取负
    		and eax,dword ptr 7;
    		jz labeladd0; //如果低位是0,说明长度至少有8(因为长度为0是非法的)
    
    		mov edx,dword ptr 0xC;
    		mul edx; //eax变为相对偏移址。
    
    		lea edx,labeladd1; //载入起始地址
    		sub edx,dword ptr 0xC;
    		add eax,edx; //计算出跳转地址
    
    		clc; //清除标志位。
    		jmp eax; //跳转
    labeladdloop:
    		popf;
    labeladd0:
    		mov eax,[esi+ecx+0*4];
    		adc eax,[ebx+ecx+0*4];
    		mov [edi+ecx+0*4],eax;
    labeladd1:
    		mov eax,[esi+ecx+1*4];
    		adc eax,[ebx+ecx+1*4];
    		mov [edi+ecx+1*4],eax;
    		//labeladd2:
    		mov eax,[esi+ecx+2*4];
    		adc eax,[ebx+ecx+2*4];
    		mov [edi+ecx+2*4],eax;
    		//labeladd3:
    		mov eax,[esi+ecx+3*4];
    		adc eax,[ebx+ecx+3*4];
    		mov [edi+ecx+3*4],eax;
    		//labeladd4:
    		mov eax,[esi+ecx+4*4];
    		adc eax,[ebx+ecx+4*4];
    		mov [edi+ecx+4*4],eax;
    		//labeladd5:
    		mov eax,[esi+ecx+5*4];
    		adc eax,[ebx+ecx+5*4];
    		mov [edi+ecx+5*4],eax;
    		//labeladd6:
    		mov eax,[esi+ecx+6*4];
    		adc eax,[ebx+ecx+6*4];
    		mov [edi+ecx+6*4],eax;
    		//labeladd7:
    		mov eax,[esi+ecx+7*4];
    		adc eax,[ebx+ecx+7*4];
    		mov [edi+ecx+7*4],eax;
    		//labeladd8:
    		pushf;
    		add ecx,32;
    		jnz labeladdloop;
    
    		//运行到这里了,说明两数重合部分已经处理完了。开始处理两数相差的部分。
    
    		mov ecx,len2; //载入长度
    
    		xor ebx,ebx; //因为ebx指向的缓冲区废弃不用了,所以使用已经空闲的ebx寄存器。
    		cmp ebx,ecx; //判断ecx是否等于ebx(0)
    		jz labelcarry; //如果相等,说明已经处理完了,跳转到处理最后一次进位的地方。
    
    labelfix:
    		popf; //弹出上次的标志位。
    
    		mov eax,[esi+ebx*4];
    		adc eax,0;	
    		mov [edi+ebx*4],eax;
    
    		//如果这里没有进位,剩下的部分可以直接拷贝。拷贝的长度就是ecx-1
    		jnc labelcopy;
    
    		pushf; //保存标志位。
    
    		inc ebx; //步进
    		dec ecx;
    		jnz labelfix; //循环。
    
    labelcarry:
    		popf; //弹出标志位
    		jnc labelend; //没有进位就直接结束
    		//没有跳转说明有进位:
    		mov dword ptr [edi+ebx*4],dword	ptr 0x00000001;	//直接置1
    		lea eax,len;
    		inc [eax]; //总长度+1
    		inc ecx; //补一次。
    labelcopy:
    		dec ecx;
    		sal ecx,2;
    		cmp edi,esi; //比较源地址和目标地址,如果相同就跳过。
    		jz labelend;
    
    		sal ebx,2;
    		add ebx,4;
    
    		add edi,ebx;
    		add esi,ebx;
    
    		rep movs dword ptr [edi],dword ptr [esi];
    
    labelend:
    		popad;
    	}
    	sum._len = len + offset;	
    }
    
    void CLargeInt::Sub(const CLargeInt& minuend,const CLargeInt& subtrahend,CLargeInt& difference,T_DWORD offset)
    {
    	T_DWORD	len,len1,len2; //len绝对不能<1,否则会出错。
    	T_DWORD	*p1 = 0;
    	T_DWORD	*p2 = 0;
    	T_DWORD	*p3 = 0;
    
    	if( minuend._len >= (subtrahend._len+offset) )
    	{
    		len = minuend._len- offset;
    		len1 = subtrahend._len;
    		len2 =	len - len1;
    		p1 = minuend._data + offset;
    		p2 = subtrahend._data;
    		p3 = difference._data +	offset;
    	}
    	else
    	{
    		__asm int 3; //出错。
    	}
    
    	__asm
    	{
    		pushad;
    		//载入计算地址
    		mov esi,p1;
    		mov ebx,p2;
    		mov edi,p3;
    
    		mov eax,len1;
    		sal eax,2;
    
    		//计算末地址
    		add esi,eax;
    		add ebx,eax;
    		add edi,eax;
    
    		mov ecx,eax;
    		add ecx,4*7;
    		and ecx,0xFFFFFFE0; //ecx - ecx%32,这里的ecx就变成了偏移址,同时也是累加器。
    		neg ecx; //取负,用作累加。
    
    		sar eax,2; //回复到原来的长度
    
    		neg eax; //取负
    		and eax,dword ptr 7;
    		jz labeladd0; //如果低位是0,说明长度至少有8(因为长度为0是非法的)
    
    		mov edx,dword ptr 0xC;
    		mul edx; //eax变为相对偏移址。
    
    		lea edx,labeladd1; //载入起始地址
    		sub edx,dword ptr 0xC;
    		add eax,edx; //计算出跳转地址
    
    		clc; //清除标志位。
    		jmp eax; //跳转
    labeladdloop:
    		popf;
    labeladd0:
    		mov eax,[esi+ecx+0*4];
    		sbb eax,[ebx+ecx+0*4];
    		mov [edi+ecx+0*4],eax;
    labeladd1:
    		mov eax,[esi+ecx+1*4];
    		sbb eax,[ebx+ecx+1*4];
    		mov [edi+ecx+1*4],eax;
    		//labeladd2:
    		mov eax,[esi+ecx+2*4];
    		sbb eax,[ebx+ecx+2*4];
    		mov [edi+ecx+2*4],eax;
    		//labeladd3:
    		mov eax,[esi+ecx+3*4];
    		sbb eax,[ebx+ecx+3*4];
    		mov [edi+ecx+3*4],eax;
    		//labeladd4:
    		mov eax,[esi+ecx+4*4];
    		sbb eax,[ebx+ecx+4*4];
    		mov [edi+ecx+4*4],eax;
    		//labeladd5:
    		mov eax,[esi+ecx+5*4];
    		sbb eax,[ebx+ecx+5*4];
    		mov [edi+ecx+5*4],eax;
    		//labeladd6:
    		mov eax,[esi+ecx+6*4];
    		sbb eax,[ebx+ecx+6*4];
    		mov [edi+ecx+6*4],eax;
    		//labeladd7:
    		mov eax,[esi+ecx+7*4];
    		sbb eax,[ebx+ecx+7*4];
    		mov [edi+ecx+7*4],eax;
    		//labeladd8:
    		pushf;
    		add ecx,32;
    		jnz labeladdloop;
    
    		//运行到这里了,说明两数重合部分已经处理完了。开始处理两数相差的部分。
    
    		mov ecx,len2; //载入长度,
    		//只处理p1的buf
    
    		xor ebx,ebx; //因为ebx指向的缓冲区废弃不用了,所以使用已经空闲的ebx寄存器。
    		cmp ebx,ecx; //判断ecx是否等于ebx
    		jz labelcarry; //如果相等,说明已经处理完了,跳转到处理最后一次进位的地方。
    
    labelfix:
    		popf; //弹出上次的标志位。
    
    		mov eax,[esi+ebx*4];
    		sbb eax,0;	
    		mov [edi+ebx*4],eax;
    
    		//如果这里没有进位,剩下的部分可以直接拷贝。拷贝的长度就是ecx-1
    		jnc labelcopy;
    
    		pushf; //保存标志位。
    
    		inc ebx; //步进
    		dec ecx;
    		jnz labelfix; //循环。
    
    labelcarry:
    		popf; //弹出标志位
    		jnc labelend; //没有进位就直接结束
    		//没有跳转说明有进位:
    		int 3; //说明减数不够,结果为负
    		mov dword ptr [edi+ebx*4],dword	ptr 0x00000001;	//直接置1
    		lea eax,len;
    		dec [eax]; //总长度-1
    		inc ecx; //补一次。
    labelcopy:
    		dec ecx;
    		sal ecx,2;
    		cmp edi,esi; //比较源地址和目标地址,如果相同就跳过。
    		jz labelend;
    
    		sal ebx,2;
    		add ebx,4;
    
    		add edi,ebx;
    		add esi,ebx;
    
    		rep movs dword ptr [edi],dword ptr [esi];
    
    labelend:
    		popad;
    	}
    	difference._len	= len+offset;
    	for( T_DWORD i = difference._len-1; i >	0; i--)
    	{
    		if( difference._data[i]	== 0 ) //末尾是0
    		{
    			difference._len--;
    		}
    		else break;
    	}
    }
    
    
    void CLargeInt::Mul(const CLargeInt& faciend,T_DWORD multiplier,CLargeInt& product)
    {
    	T_DWORD	len; //len绝对不能<1,否则会出错。
    	T_DWORD	*p1 = 0;
    	T_DWORD	*p2 = 0;
    	T_DWORD	*p3 = 0;
    
    	len = faciend._len;
    	p1 = faciend._data;
    	p2 = 0;
    	p3 = product._data;
    
    	memset(p3,0,(len+1)*4);
    	__asm
    	{
    		pushad;
    		//载入计算地址
    		mov esi,p1;
    		mov edi,p3;
    		mov ebx,multiplier
    
    			mov eax,len;
    		sal eax,2;
    
    		//计算末地址
    		add esi,eax;
    		add edi,eax;
    
    		mov ecx,eax;
    		add ecx,4*7;
    		and ecx,0xFFFFFFE0; //ecx - ecx%32,这里的ecx就变成了偏移址,同时也是累加器。
    		neg ecx; //取负,用作累加。
    
    		sar eax,2; //回复到原来的长度
    
    		neg eax; //取负
    		and eax,dword ptr 7;
    		jz labeladd0; //如果低位是0,说明长度至少有8(因为长度为0是非法的)
    
    		mov edx,dword ptr 0xe; //0xe是跳转地址的系数。
    		mul edx; //eax变为相对偏移址。
    
    		lea edx,labeladd1; //载入起始地址
    		sub edx,dword ptr 0xe;
    		add eax,edx; //计算出跳转地址
    
    		clc; //清除标志位。
    		jmp eax; //跳转
    labeladdloop:
    labeladd0:
    		mov eax,[esi+ecx+0*4];
    		mul ebx;
    		add [edi+ecx+0*4],eax;
    		adc [edi+ecx+1*4],edx;
    labeladd1:
    		mov eax,[esi+ecx+1*4];
    		mul ebx;
    		add [edi+ecx+1*4],eax;
    		adc [edi+ecx+2*4],edx;
    		//labeladd2:
    		mov eax,[esi+ecx+2*4];
    		mul ebx;
    		add [edi+ecx+2*4],eax;
    		adc [edi+ecx+3*4],edx;
    		//labeladd3:
    		mov eax,[esi+ecx+3*4];
    		mul ebx;
    		add [edi+ecx+3*4],eax;
    		adc [edi+ecx+4*4],edx;
    		//labeladd4:
    		mov eax,[esi+ecx+4*4];
    		mul ebx;
    		add [edi+ecx+4*4],eax;
    		adc [edi+ecx+5*4],edx;
    		//labeladd5:
    		mov eax,[esi+ecx+5*4];
    		mul ebx;
    		add [edi+ecx+5*4],eax;
    		adc [edi+ecx+6*4],edx;
    		//labeladd6:
    		mov eax,[esi+ecx+6*4];
    		mul ebx;
    		add [edi+ecx+6*4],eax;
    		adc [edi+ecx+7*4],edx;
    		//labeladd7:
    		mov eax,[esi+ecx+7*4];
    		mul ebx;
    		add [edi+ecx+7*4],eax;
    		adc [edi+ecx+8*4],edx;
    		//labeladd8:
    		add ecx,32;
    		jnz labeladdloop;
    
    		//labelend:
    		popad;
    	}
    	product._len = len+1;
    
    	for( T_DWORD i = product._len-1; i > 0;	i--)
    	{
    		if( product._data[i] ==	0 ) //末尾是0
    		{
    			product._len--;
    		}
    		else break;
    	}
    
    }
    
    void CLargeInt::RCR(CLargeInt& n)
    {
    	T_DWORD	len; //len绝对不能<1,否则会出错。
    	T_DWORD	*p1 = 0;	
    	len = n._len;
    	p1 = n._data;
    
    	__asm
    	{
    		pushad;		
    
    		//载入计算地址
    		mov esi,p1;
    
    		mov eax,len;
    		mov ecx,eax;
    
    		add ecx,dword ptr 15;
    		and ecx,dword ptr 0xFFFFFFF0;
    
    		mov eax,ecx;
    		sal ecx,dword ptr 2;
    
    		sub eax,len; //求初始偏移。
    
    		mov ebx,dword ptr 4; //偏移倍率
    		mul ebx;		
    
    		lea ebx,label0;
    		add eax,ebx;
    		clc;
    		jmp eax;
    labelloop:
    		popf;
    label0:
    		rcr dword ptr [esi + ecx - 1*4],1;
    		//label1:
    		rcr dword ptr [esi + ecx - 2*4],1;
    		//label2:
    		rcr dword ptr [esi + ecx - 3*4],1;
    		//label3:
    		rcr dword ptr [esi + ecx - 4*4],1;
    		//label4:
    		rcr dword ptr [esi + ecx - 5*4],1;
    		//label5:
    		rcr dword ptr [esi + ecx - 6*4],1;
    		//label6:
    		rcr dword ptr [esi + ecx - 7*4],1;
    		//label7:
    		rcr dword ptr [esi + ecx - 8*4],1;
    		//label8:
    		rcr dword ptr [esi + ecx - 9*4],1;
    		//label9:
    		rcr dword ptr [esi + ecx - 10*4],1;
    		//label10:
    		rcr dword ptr [esi + ecx - 11*4],1;
    		//label11:
    		rcr dword ptr [esi + ecx - 12*4],1;
    		//label12:
    		rcr dword ptr [esi + ecx - 13*4],1;
    		//label13:
    		rcr dword ptr [esi + ecx - 14*4],1;
    		//label14:
    		rcr dword ptr [esi + ecx - 15*4],1;
    		//label15:
    		rcr dword ptr [esi + ecx - 16*4],1;
    		//label16:
    		pushf;
    		sub ecx,dword ptr 64;
    		jnz labelloop;
    		popf;
    		popad;
    	}
    	for( T_DWORD i = n._len-1; i > 0; i--)
    	{
    		if( n._data[i] == 0 ) //末尾是0
    		{
    			n._len--;
    		}
    		else break;
    	}
    }
    
    void CLargeInt::RCL(CLargeInt& n)
    {
    	T_DWORD	len = n._len; //len绝对不能<1,否则会出错。
    	T_DWORD	*p1 = 0;
    
    	n._data[len] = 0;
    	n._len++;
    
    	len++;
    	p1 = n._data;
    
    	__asm
    	{
    		pushad;		
    
    		//载入计算地址
    		mov esi,p1;
    
    		mov eax,len;
    		mov ecx,eax;
    
    		sal eax,dword ptr 2;
    		add esi,eax;
    
    		add ecx,dword ptr 15;
    		and ecx,dword ptr 0xFFFFFFF0;
    
    		mov eax,ecx;
    		sal ecx,dword ptr 2;
    
    		neg ecx;
    
    		sub eax,len; //求初始偏移。
    		jz label0;
    
    		mov ebx,dword ptr 4; //偏移倍率
    		mul ebx;		
    
    		lea ebx,label1;
    		sub ebx,4;
    		add eax,ebx;
    		clc;
    		jmp eax;
    labelloop:
    		popf;
    label0:
    		rcl dword ptr [esi + ecx + 0*4],1;
    label1:
    		rcl dword ptr [esi + ecx + 1*4],1;
    		//label2:
    		rcl dword ptr [esi + ecx + 2*4],1;
    		//label3:
    		rcl dword ptr [esi + ecx + 3*4],1;
    		//label4:
    		rcl dword ptr [esi + ecx + 4*4],1;
    		//label5:
    		rcl dword ptr [esi + ecx + 5*4],1;
    		//label6:
    		rcl dword ptr [esi + ecx + 6*4],1;
    		//label7:
    		rcl dword ptr [esi + ecx + 7*4],1;
    		//label8:
    		rcl dword ptr [esi + ecx + 8*4],1;
    		//label9:
    		rcl dword ptr [esi + ecx + 9*4],1;
    		//label10:
    		rcl dword ptr [esi + ecx + 10*4],1;
    		//label11:
    		rcl dword ptr [esi + ecx + 11*4],1;
    		//label12:
    		rcl dword ptr [esi + ecx + 12*4],1;
    		//label13:
    		rcl dword ptr [esi + ecx + 13*4],1;
    		//label14:
    		rcl dword ptr [esi + ecx + 14*4],1;
    		//label15:
    		rcl dword ptr [esi + ecx + 15*4],1;
    		//label16:
    		pushf;
    		add ecx,dword ptr 64;
    		jnz labelloop;
    		popf;
    		popad;
    	}
    	for( T_DWORD i = n._len-1; i > 0; i--)
    	{
    		if( n._data[i] == 0 ) //末尾是0
    		{
    			n._len--;
    		}
    		else break;
    	}
    }
    
    void CLargeInt::Mul(const CLargeInt& faciend, const CLargeInt& multiplier, CLargeInt& product)
    {
    	CLargeInt temp;
    	product._len = (faciend._len + multiplier._len);
    	memset(product._data, 0, product._len * 4);
    
    	for (T_DWORD i = 0; i <	multiplier._len; i++)
    	{
    		Mul(faciend,multiplier._data[i],temp);
    		Add(product,temp,product,i);
    	}
    	for (T_DWORD i = product._len - 1; i > 0;	i--)
    	{
    		if( product._data[i] ==	0 ) //末尾是0
    		{
    			product._len--;
    		}
    		else
    		{
    			break;
    		}
    	}
    }
    
    void CLargeInt::Div(const CLargeInt& dividend,T_DWORD divisor,CLargeInt& quotient,T_DWORD& residual)
    {
    	T_DWORD	len; //len绝对不能<1,否则会出错。
    	T_DWORD	*p1 = 0;
    	T_DWORD	*p2 = 0;
    
    	T_DWORD	*pr = &residual;
    
    	len = dividend._len;
    	quotient._len =	dividend._len;
    	p1 = dividend._data;
    	p2 = quotient._data;
    
    	__asm
    	{
    		pushad;		
    
    		//载入计算地址
    		mov esi,p1;
    		mov edi,p2;
    
    		mov eax,len;
    		mov ecx,eax;
    
    		add ecx,dword ptr 7;
    		and ecx,dword ptr 0xFFFFFFF8;
    
    		mov eax,ecx;
    		sal ecx,dword ptr 2;
    
    		sub eax,len; //求初始偏移。
    
    		mov ebx,dword ptr 0x0A;	//偏移倍率
    		mul ebx;		
    
    		lea edx,label0;
    		add eax,edx;
    
    		xor edx,edx;
    
    		mov ebx,divisor;
    
    		clc;
    		jmp eax;
    labelloop:
    
    label0:
    		mov eax,[esi + ecx - 1*4];
    		div ebx;
    		mov [edi + ecx - 1*4],eax;
    		//label1:
    		mov eax,[esi + ecx - 2*4];
    		div ebx;
    		mov [edi + ecx - 2*4],eax;
    		//label2:
    		mov eax,[esi + ecx - 3*4];
    		div ebx;
    		mov [edi + ecx - 3*4],eax;
    		//label3:
    		mov eax,[esi + ecx - 4*4];
    		div ebx;
    		mov [edi + ecx - 4*4],eax;
    		//label4:
    		mov eax,[esi + ecx - 5*4];
    		div ebx;
    		mov [edi + ecx - 5*4],eax;
    		//label5:
    		mov eax,[esi + ecx - 6*4];
    		div ebx;
    		mov [edi + ecx - 6*4],eax;
    		//label6:
    		mov eax,[esi + ecx - 7*4];
    		div ebx;
    		mov [edi + ecx - 7*4],eax;
    		//label7:
    		mov eax,[esi + ecx - 8*4];
    		div ebx;
    		mov [edi + ecx - 8*4],eax;
    		//label8:
    		sub ecx,dword ptr 32;
    		jnz labelloop;
    
    		//运行到这里说明已经计算完毕,最后保存余数。
    		mov ecx,pr;
    		mov [ecx],edx;
    		popad;
    	}
    
    	for (T_DWORD i = quotient._len-1; i > 0; i--)
    	{
    		if (quotient._data[i] == 0) //末尾是0
    		{
    			quotient._len--;
    		}
    		else 
    		{
    			break;
    		}
    	}
    }
    
    void CLargeInt::Div(const CLargeInt& dividend,const CLargeInt& divisor,CLargeInt& quotient,CLargeInt& residual)
    {
    	if( dividend._len < divisor._len )
    	{
    		quotient._len =	1;
    		quotient._data[0] = 0;
    		Copy(dividend,residual);
    	}
    	else
    	{
    		if( divisor._len == 1)
    		{			
    			Div(dividend,divisor._data[0],quotient,residual._data[0]);
    			residual._len =	1;
    		}
    		else
    		{
    			CLargeInt d,r;
    
    			//满位算法
    			T_DWORD	head = divisor._data[divisor._len-1];
    			__asm
    			{
    				pushad;
    				mov eax,head;
    				bsr ecx,eax;
    				mov edx,dword ptr 31;
    				sub edx,ecx;
    				mov ecx,edx;
    				mov eax,dword ptr 0x01;
    				sal eax,cl;
    				mov head,eax;
    				popad;
    			}
    
    			Mul(dividend,head,d);
    			Mul(divisor,head,r);
    
    			quotient._len =	d._len - r._len	+ 1;
    			quotient._data[quotient._len - 1] = 0;
    
    			T_DWORD	newhead	= r._data[r._len - 1];
    			//以上处理的目的是使得被除数最高位的1移动到DWORD的最高位。
    			//处理之后可以极大减少商的上下限之间的差距。
    
    			T_DWORD	highpart = 0;
    			T_DWORD	lowpart	 = 0;
    			T_DWORD	max = 0,min = 0;
    			T_DWORD	offset = 0;
    			CLargeInt temp;
    			for( T_DWORD i = d._len-1; i >=	r._len-1; i-- )
    			{				
    				offset = i - (r._len - 1);	
    				lowpart	= d._data[i];
    
    				__asm
    				{//这段汇编代码的作用是求商的上下限。min & max
    					pushad;
    					mov edx,highpart;
    					mov eax,lowpart;
    					mov ecx,newhead;
    					div ecx;
    					mov max,eax;
    
    					inc ecx;
    					jz _mov;
    					mov edx,highpart;
    					mov eax,lowpart;
    					div ecx;
    					mov edx,eax;
    _mov:
    					mov min,edx;
    					popad;
    				}
    
    				if (max == 0)
    				{
    					highpart = d._data[i];
    					quotient._data[offset] = 0;
    				}
    				else
    				{
    					Mul(r,max,temp);
    					if ( max	!= min )
    					{
    						while ( !LargerEqual(d,temp,offset) )
    						{//这里使用的其实是试商法,
    							//由于max和min的差距已经很小,所以这里不再折半试商。
    							max--;
    							Sub(temp,r,temp);
    						}
    					}	
    
    					quotient._data[offset] = max; //保存商。
    					Sub(d,temp,d,offset); //求本阶的余数。
    					highpart = d._data[i]; //载入余数。
    				}
    			}
    
    			//清除高位的0。
    			for(T_DWORD i = quotient._len-1; i > 0; i--)
    			{
    				if( quotient._data[i] == 0 ) //末尾是0
    				{
    					quotient._len--;
    				}
    				else break;
    			}
    			//除法完成,计算余数:
    			Div(d,head,residual,newhead);
    
    			if ( newhead != 0 ) 
    			{
    				//余数不为0,说明出错。
    				__asm int 3;
    			}
    		}
    
    	}
    }
    
    void CLargeInt::ExpMod(const CLargeInt&	source,const CLargeInt&	exponent,const CLargeInt& modulo,CLargeInt& result)
    {
    	CLargeInt n,e,r(1),temp,temp1;
    	Copy(source,n);
    	Copy(exponent,e);
    
    	while( e._len >	1 || e._data[e._len - 1] > 1 )
    	{
    		if( e._data[0] &1 ) 
    		{
    			Mul(r,n,temp);
    			Div(temp,modulo,temp1,r);
    		}
    		RCR(e);
    		Mul(n,n,temp);
    		Div(temp,modulo,temp1,n);
    	}
    	Mul(r,n,temp);
    	Div(temp,modulo,temp1,result);
    }
    
    bool CLargeInt::RabinMillerTest(const CLargeInt& source,const CLargeInt& base)
    {
    	CLargeInt m,temp(1);
    
    	Sub(source,temp,m);
    
    	T_DWORD	count =	0;
    	while(!(m._data[0] & 0x01) ) 
    	{
    		count++;
    		RCR(m);
    	}
    	/*
    	这里注释掉的是旧的算法,可以用后面的等效算法取代。
    	改变的算法使得计算ExpMod的次数由 count 减少到 1
    	虽然理论上计算次数大大减少了,但是实际效果似乎只是减少了50%左右的运算量。
    
    	for( T_DWORD i = 0; i< count;	i++)
    	{
    	CLargeInt mod;
    	ExpMod(base,m,source,mod);
    	if( mod._data[0] == 1	&& mod._len == 1 ) 
    	{
    	return true;
    	}
    	else
    	{
    	Sub(source,mod,temp);
    	if(  temp._data[0] ==	1 && temp._len == 1) 
    	{
    	return true;
    	}
    	}
    	RCL(m);		
    	}
    	*/
    
    	CLargeInt mod;
    	ExpMod(base,m,source,mod);
    	if( mod._data[0] == 1 && mod._len == 1 ) 
    	{
    		return true;
    	}
    	else
    	{
    		Sub(source,mod,temp);
    		if(  temp._data[0] == 1	&& temp._len ==	1) 
    		{
    			return true;
    		}
    	}
    	for( T_DWORD i = 1; i< count; i++)
    	{
    		CLargeInt next,t;
    		Mul(mod,mod,next);
    		Div(next,source,t,mod);
    		if( mod._data[0] == 1 && mod._len == 1 ) 
    		{
    			return true;
    		}
    		else
    		{
    			Sub(source,mod,temp);
    			if(  temp._data[0] == 1	&& temp._len ==	1) 
    			{
    				return true;
    			}
    		}			
    	}
    
    	return false;
    }
    
    bool CLargeInt::RSACreate( const CLargeInt &p,const CLargeInt &	q,const	CLargeInt &e,CLargeInt &d,CLargeInt &n)
    {//由已知的P,Q,E计算N,D,完成RSA密钥生成。
    	Mul(p,q,n);
    	CLargeInt a(e),b(n);
    	Sub(b,p,b);
    	Sub(b,q,b);
    	Add(b,1,b);
    
    	if( !Coprime(b,e) ) return false;
    	//求D的算法,通过辗转相除,最终求得需要的D值。
    	CLargeInt k1,c1;
    	Div(b,a,k1,c1);
    	if( c1._len == 1 && c1._data[0]	== 0 ) return false;
    	if( c1._len == 1 && c1._data[0]	== 1 ) 
    	{ 
    		CLargeInt temp;
    		Sub(e,1,temp);
    		Mul(temp,k1,d);
    		Add(d,1,d);
    		return true; 
    	}
    
    	CLargeInt k2,c2,ka2;
    	Div(a,c1,k2,c2);
    	if( c2._len == 1 && c2._data[0]	== 0 ) return false;
    	Mul(k1,k2,ka2);
    	Add(ka2,1,ka2);
    	if( c2._len == 1 && c2._data[0]	== 1 ) { Copy(ka2,d); return true; }
    
    
    	CLargeInt k3,c3,ka3;
    	Div(c1,c2,k3,c3);
    	if( c3._len == 1 && c3._data[0]	== 0 ) return false;
    	Mul(k3,ka2,ka3);
    	Add(ka3,k1,ka3);
    	if( c3._len == 1 && c3._data[0]	== 1 ) 
    	{ 
    		Copy(ka3,d);
    
    		CLargeInt temp,temp1,temp2;
    		Mul(d,e,temp);
    		Div(temp,b,temp1,temp2);
    		Add(temp2,1,temp2);
    		if( Equal(temp2,b) )
    		{
    			Mul(d,d,temp1);
    			Div(temp1,b,temp2,d);
    			Mul(d,e,temp1);
    			Div(temp1,b,temp2,d);
    		}
    		return true; 
    	}
    
    	CLargeInt kn_2(k2),cn_2(c2),ka_2(ka2);
    	CLargeInt kn_1(k3),cn_1(c3),ka_1(ka3);
    
    	CLargeInt kn,cn,kan;
    	do{
    		Div(cn_2,cn_1,kn,cn);
    		Mul(kn,ka_1,kan);
    		Add(kan,ka_2,kan);
    
    		if( cn._len == 1 && cn._data[0]	== 0 ) return false;
    		if( cn._len == 1 && cn._data[0]	== 1 ) 
    		{
    			Copy(kan,d);
    
    			CLargeInt temp,temp1,temp2;
    			Mul(d,e,temp);
    			Div(temp,b,temp1,temp2);
    			Add(temp2,1,temp2);
    			if( Equal(temp2,b) )
    			{
    				Mul(d,d,temp1);
    				Div(temp1,b,temp2,d);
    				Mul(d,e,temp1);
    				Div(temp1,b,temp2,d);
    			}
    			return true; 
    		}
    		Copy(kn_1,kn_2);
    		Copy(cn_1,cn_2);
    		Copy(ka_1,ka_2);
    
    		Copy(kn,kn_1);
    		Copy(cn,cn_1);
    		Copy(kan,ka_1);
    	} while (true);
    }
    
    void CLargeInt::RSAEncode( const CLargeInt &n,const CLargeInt &d,const CLargeInt &m,CLargeInt &c)
    {
    	ExpMod(m,d,n,c);
    }
    
    void CLargeInt::RSADecode( const CLargeInt &n,const CLargeInt &e,const CLargeInt &c,CLargeInt &m)
    {
    	ExpMod(c,e,n,m);
    }
    
    string CLargeInt::RSADecode(const char* N, const char* E, const char* I)
    {
    	CLargeInt o;
    	CLargeInt n(N);
    	CLargeInt i(I);
    	CLargeInt e(E);
    	RSADecode(n, e, i, o);
    	return o.GetHexStr();
    }
    
    string CLargeInt::GetHexStr()
    {
    	char buffer[128];
    	memset(buffer, 0x00, sizeof(buffer));
    	string strHex;
    
    	for (int i = _len - 1 ; i >= 0; i--)
    	{
    		char str[] = "%08X";
    		str[2] = '0' + sizeof(T_DWORD) * 2;
    		if ( i != (int)(_len - 1) )
    		{
    			sprintf(buffer, str, _data[i]);
    		}
    		else 
    		{
    			sprintf(buffer, str, _data[i]);
    		}
    
    		strHex += buffer;
    	}
    	return strHex;
    }
    
    typedef	unsigned __int64 ULONGLONG;
    inline ULONGLONG GetCycleCount()
    {
    	__asm RDTSC
    }
    
    //#include <windows.h>
    void CLargeInt::CreateRandom(CLargeInt &n, T_DWORD bitcount)
    {
    	n._len = (bitcount + 31) / 32;
    	for (T_DWORD i = 0; i <	n._len;	i++)
    	{
    		T_DWORD	byte[4];
    
    		for (int b = 0;	b < 4; b++)
    		{
    			for (int j = 0;	j < 4;	j++)
    			{
    				//Sleep(0);
    				j = j + 1 - 1;
    			}
    			byte[b]	= GetCycleCount() & 0xFF;
    		}
    
    		n._data[i] = (byte[0] << 24)
    			| (byte[1] << 16)
    			| (byte[2] << 8)
    			| (byte[3] << 0);
    	}
    	n._data[0] |= 1;
    	n._data[n._len - 1] |= 0x80000000;
    	if ((bitcount &	31))
    	{
    		n._data[n._len - 1] >>=	32 - (bitcount & 31);
    	}	
    }
    
    bool CLargeInt::Coprime(const CLargeInt	&source,const CLargeInt	&target)
    {
    	CLargeInt m(source),n(target),temp;
    	while(true)
    	{
    		Div(m,n,temp,m);
    		if( m._len == 1	&& m._data[0] == 1 ) return true;
    		else if( m._len	== 1 &&	m._data[0] == 0	) return false;
    
    		Div(n,m,temp,n);
    		if( n._len == 1	&& n._data[0] == 1 ) return true;
    		else if( n._len	== 1 &&	n._data[0] == 0	) return false;
    	}
    
    }


    3.RSA工具


    RSA工具(来源于网络),此工具用着比较顺手。软件下载地址:http://download.csdn.net/detail/yxstars/7734567





    文/闫鑫原创   转载请注明出处http://blog.csdn.net/yxstars/article/details/38443937


    Meet so Meet. C plusplus I-PLUS....
  • 相关阅读:
    百度编辑器 Ueditor使用记录
    JS实现继承的几种方式
    IOS 浏览器上设置overflow: auto 不可滚动
    throw new Error('Cyclic dependency' + nodeRep)
    如何理解springaop
    SQL连接的分类
    Eclipse创建Maven-Web项目及解决 jre版本和web.xml版本问题
    SQL的几种连接:内连接、左联接、右连接、全连接、交叉连接
    Centos7下面安装eclipse
    Centos7 下编译 Openjdk8
  • 原文地址:https://www.cnblogs.com/iplus/p/4467126.html
Copyright © 2020-2023  润新知