加密

最近在做的项目,需要账户密码登录,采用 AES 加密算法,从认识到了解,以及后续的扩展学习。

首先,明确几个加解密的重要概念

密钥

在明文转换为密文或将密文转换为明文的算法中输入的参数。密钥分为对称密钥(私用密钥)与非对称密钥(公共密钥)。

密钥加密

发送和接收数据的双方,使用相同的或对称的密钥对明文进行加密解密运算的加密方法。

加密向量

通常采用密钥长度的随机文本块对纯文本进行异或运算,然后再对其进行加密,产生一个加密文本块。然后,将前面产生的密文块作为一个初始化向量对下一个纯文本块进行异或运算。

对称加密算法中,如果只有一个密钥来加密数据的话,明文中的相同文字就会也会被加密成相同的密文,这样密文和明文就有完全相同的结构,容易破解。如果给一个初始化向量,第一个明文使用初始化向量混合并加密,第二个明文用第一个明文的加密后的密文与第二个明文混合加密,这样加密出来的密文的结构则完全与明文不同,更加安全可靠。

综上,加密之所以安全,并非不知道加解密算法,而是加密的密钥和向量是绝对的隐藏,加密向量可以增加加密算法的强度(区块加密)。

AES

那么,什么是 AES 加密呢?

Advanced Encryption Standard(高级加密标准),在密码学中又叫 Rijndael 加密法,是美国联邦政府采用的一种区块加密标准。

  • 替代 DES
  • 对称密钥加密(迭代对称分组密码体制)
  • 代换-置换网络(基于排列和置换运算),非Feistel架构(DES)
  • AES 加密数据块分组长度必须为128比特,密钥长度可以是128比特、192比特、256比特中的任意一个

Rijndael 密码的设计力求满足以下3条标准:

  抵抗所有已知的攻击
  在多个平台上速度快,编码紧凑
  设计简单

当前的大多数分组密码,其轮函数是Feistel结构。Rijndael 轮函数是由3个不同的可逆均匀变换组成。

具体信息参见:AES是个什么鬼?

本文后续代码基础:AES加密,密钥位数不足转换成byte[]后填充(byte)0,加密向量16位,加密模式CBC,填充模式PKCS5Padding,字符集是UTF-8,输出是HEX格式

其中,PKCS5Padding(Java)与 PKCS7(C#)是可以互相加解密的,这是正好匹配的情况。

但是,经常会存在合作双方平台不一致、跨语言接口对接导致数据不一致的问题,可以采用ikvm工具将.jar包转换为.dll,则在.Net平台直接引用并调用方法即可

具体方法:在 jre1.8版本下,使用 ikvm 将jar转换为dll,以供c#调用

/// <summary>
/// AES加密
/// </summary>
/// <param name="_pwd">明文密码</param>
/// <param name="_key">加密密钥</param>
/// <param name="_iv">加密向量</param>
/// <returns></returns>
public static string AESEncrypt(string _pwd, string _key, string _iv, int _keyLen, int _ivLen)
{
	byte[] toEncryptArray = UTF8Encoding.UTF8.GetBytes(_pwd);
	using (AesCryptoServiceProvider aesProvider = new AesCryptoServiceProvider())
	{
		aesProvider.Mode = System.Security.Cryptography.CipherMode.CBC;
		aesProvider.Padding = System.Security.Cryptography.PaddingMode.PKCS7;
		aesProvider.Key = GetAesKey(_key, _keyLen);
		aesProvider.IV = GetAesVector(_iv, _ivLen);
		using (MemoryStream ms = new MemoryStream())
		{
			using (CryptoStream cs = new CryptoStream(
				ms, aesProvider.CreateEncryptor(), CryptoStreamMode.Write))
			{
				cs.Write(toEncryptArray, 0, toEncryptArray.Length);
				cs.FlushFinalBlock();
				cs.Close();
			}

			string resultStr = ByteArrayToHexString(ms.ToArray()); // Convert.ToBase64String
			ms.Close();
			return resultStr;
		}
	}
}

/// <summary>
/// AES解密
/// </summary>
/// <param name="_pwd">暗文密码</param>
/// <param name="_key">密钥</param>
/// <param name="_iv">向量</param>
/// <returns></returns>
public static string AESDecrypt(string _pwd, string _key, string _iv, int _keyLen, int _ivLen)
{
	byte[] toDecryptArray = HexStringToByteArray(_pwd);
	using (AesCryptoServiceProvider aesProvider = new AesCryptoServiceProvider())
	{
		aesProvider.Mode = System.Security.Cryptography.CipherMode.CBC;
		aesProvider.Padding = System.Security.Cryptography.PaddingMode.PKCS7;
		aesProvider.Key = GetAesKey(_key, _keyLen);
		aesProvider.IV = GetAesVector(_iv, _ivLen);

		using (MemoryStream ms = new MemoryStream(toDecryptArray))
		{
			byte[] decryptBytes = new byte[toDecryptArray.Length];
			string resultStr = string.Empty;
			using (CryptoStream cs = new CryptoStream(
				ms, aesProvider.CreateDecryptor(), CryptoStreamMode.Read))
			{
				if (cs.Read(decryptBytes, 0, decryptBytes.Length) > 0)
				{
					resultStr = Encoding.UTF8.GetString(decryptBytes);
				}
				cs.Close();
			}

			ms.Close();
			return resultStr;
		}
	}
}

/// </summary>
/// 获取AES密钥
/// </summary>
/// <param name="_key">Aes密钥字符串</param>
/// <param name="_length">默认长度16字节</param>
/// <returns>Aes密钥</returns>
public static byte[] GetAesKey(string _key, int _length)
{
	byte[] resBytes = new byte[_length];
	byte[] keyBytes = UTF8Encoding.UTF8.GetBytes(_key);
	int lenTmp = keyBytes.Length;

	if (lenTmp <= _length)
	{
		Array.Copy(keyBytes, resBytes, lenTmp);
	}
	else
	{
		Array.Copy(keyBytes, resBytes, _length);
	}

	return resBytes;
}

/// <summary>
/// 获取AES向量
/// </summary>
/// <param name="_iv">Aes向量字符串</param>
/// <param name="_length">默认长度16字节</param>
/// <returns>Aes向量</returns>
public static byte[] GetAesVector(string _iv, int _length)
{
	byte[] resBytes = new byte[_length];
	if (string.IsNullOrWhiteSpace(_iv))
	{
		// _iv为空,返回全0字节数组
		return resBytes;
	}
	else
	{
		byte[] ivBytes = UTF8Encoding.UTF8.GetBytes(_iv);
		int lenTmp = ivBytes.Length;
		if (lenTmp <= _length)
		{
			Array.Copy(ivBytes, resBytes, lenTmp);
		}
		else
		{
			Array.Copy(ivBytes, resBytes, _length);
		}
		return resBytes;
	}
}

/// <summary>
/// 将一个byte数组转换成一个格式化的16进制字符串
/// </summary>
/// <param name="data">byte数组</param>
/// <returns>格式化的16进制字符串</returns>
public static string ByteArrayToHexString(byte[] data)
{
	StringBuilder sb = new StringBuilder(data.Length * 3);
	foreach (byte b in data)
	{
		//16进制数字
		sb.Append(Convert.ToString(b, 16).PadLeft(2, '0'));
	}
	return sb.ToString().ToUpper();
}

/// <summary>
/// 将一个格式化的16进制字符串转换成一个byte数组
/// </summary>
/// <param name="?"></param>
/// <returns></returns>
public static byte[] HexStringToByteArray(string str)
{
	str = str.Replace(" ", "");
	byte[] buffer = new byte[str.Length / 2];
	for (int i = 0; i < str.Length; i += 2)
	{
		buffer[i / 2] = (byte)Convert.ToByte(str.Substring(i, 2), 16);
	}
	return buffer;
}

测试发现一个解密的bug,会出现解密结果 "288008" 的情况,遂推荐如下方法

using (ICryptoTransform transform = aesProvider.CreateDecryptor())
{
	byte[] plainText = transform.TransformFinalBlock(toDecryptArray, 0, toDecryptArray.Length);
	return Encoding.UTF8.GetString(plainText); 
} 

或直接在返回时进行替换处理: return resultStr.Replace("", ""); 

注意,上述代码加密结果是 HEX格式。如果采用 Base64格式,需分别如下改动:

加密-AESEncrypt:Convert.ToBase64String()
解密-AESDecrypt:Convert.FromBase64String()

除上述加解密方法外,还可以采用如下方式:RijndaelManaged

public static string AesEnc(string key, string content)
{
    byte[] keyBytes = Encoding.UTF8.GetBytes(key);
    byte[] contentBytes = Encoding.UTF8.GetBytes(content);
    RijndaelManaged rDel = new RijndaelManaged();
    rDel.Key = keyBytes;
    rDel.Mode = CipherMode.ECB;
    rDel.Padding = PaddingMode.PKCS7;
    ICryptoTransform cTransForm = rDel.CreateEncryptor();
    byte[] result = cTransForm.TransformFinalBlock(contentBytes, 0, contentBytes.Length);
    return Convert.ToBase64String(result, 0, result.Length);
}

public static string AesDecr(string key, string content)
{
    byte[] keyBytes = Encoding.UTF8.GetBytes(key);
    byte[] contentBytes = Encoding.UTF8.GetBytes(content);
    RijndaelManaged rDel = new RijndaelManaged();
    rDel.Key = keyBytes;
    rDel.Mode = CipherMode.ECB;
    rDel.Padding = PaddingMode.PKCS7;
    ICryptoTransform cTransForm = rDel.CreateDecryptor();
    byte[] result = cTransForm.TransformFinalBlock(contentBytes, 0, contentBytes.Length);
    return Encoding.UTF8.GetString(result);
}

具体参见:AES 加解密


学习 MD5 之前,再明确单向加密的概念。

单向加密

单向散列算法,通过散列生成固定长度,不可逆且防冲突,常用于消息摘要、密钥加密等确保数据的完整性。

  • MD5:标准密钥长度128位
  • SHA:可以对任意长度的数据运算生成一个160位的数值

MD5

Message-Digest Algorithm 5(消息摘要算法),加密单向不可逆,即无法根据密文推导出明文。计算机安全领域广泛使用的一种散列函数,用以提供消息的完整性保护。

特点:

  • 容易计算
  • 压缩性:MD5长度固定,把一个任意长度的字节串变换成一定长的16进制数字串(标准密钥长度128位)
  • 抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别
  • 强抗碰撞:已知原数据和其MD5值,想找到一个具有相同MD5值的数据(即伪造数据)非常困难

主要用途:

  • 密码加密
  • 一致性验证文件的有效性(正确、重复、缺失、损坏)
  • 对一段信息生成信息摘要,唯一性 ---> 数字签名
  • 哈希函数中计算散列值

MD5 以512位分组来处理输入的信息,且每一分组又被划分为16个32位子分组,经过了一系列的处理后,算法的输出由四个32位分组组成,将这四个32位分组级联后将生成一个128位散列值。

  • 填充,使其位长对512求余的结果等于448
  • 后64位二进制表示填充前的信息长度
  • 信息的位长 = N*512+448+64 = (N+1)*512
using System.Security.Cryptography;
using System.IO;
using System.Web;
using System.Web.Security;

/// <summary>
/// MD5加密
/// 信息摘要算法,不可逆
/// </summary>
/// <param name="myStr">待加密字符串</param>
public static string MD5Encrypt(string myStr)
{
	MD5 md5 = new MD5CryptoServiceProvider();
	byte[] myBytes = System.Text.Encoding.UTF8.GetBytes(myStr);
	byte[] hashBytes = md5.ComputeHash(myBytes);
        md5.Clear();//资源释放

	StringBuilder _sb_ = new StringBuilder();
	for (int i = 0; i < hashBytes.Length; i++)
	{
		// "x2"表示加密结果为32位,"x3"为48位,"x4"为64位
		// X/x 分别表示结果大小写
		_sb_.Append(hashBytes[i].ToString("x2"));
	}
	return _sb_.ToString();
}

/// <summary>
/// MD5加密方法
/// 默认结果32位,若要结果16位则.SubString()截取即可
/// </summary>
public static string Get32MD5(string myStr)
{
	string md5Res = "";
	md5Res = FormsAuthentication.HashPasswordForStoringInConfigFile(myStr, "MD5");
	return (md5Res);
}

/// <summary>
/// MD5加密
/// </summary>
public static string MD5(string stringToHash)
{
    MD5 md5csp = new System.Security.Cryptography.MD5CryptoServiceProvider();
    byte[] emailBytes = Encoding.UTF8.GetBytes(stringToHash);
    byte[] hashedEmailBytes = md5csp.ComputeHash(emailBytes);
    md5csp.Clear();
    string md5 = BitConverter.ToString(hashedEmailBytes).Replace("-", "");
    return md5.ToLower();
}

在使用 MD5 加密中可能遇到的问题,参见:http://blog.csdn.net/zhao1468465728/article/details/47025645

  • Encoding.Default默认编码 gb2312,需使用 utf-8
  • X2/x2 表示不省略首位为0的十六进制数字

SHA

SHA-0和SHA-1已被攻破,SHA-3应用较少。主要使用SHA-2,多指:SHA256

SHA-1

/// <summary>
/// SHA1加密
/// </summary>
public static string SHA1(string data)
{
    SHA1 sha = new SHA1CryptoServiceProvider();
    byte[] temp1 = Encoding.UTF8.GetBytes(data);
    byte[] temp2 = sha.ComputeHash(temp1);
    sha.Clear(); ;
    string sig = BitConverter.ToString(temp2).Replace("-", "");
    return sig.ToLower();
}

SHA-256

/// <summary>
/// SHA256加密
/// </summary>
public static string SHA256Encrypt(string data)
{
    try
    {
        byte[] bytes = Encoding.UTF8.GetBytes(data);
        byte[] hash = SHA256Managed.Create().ComputeHash(bytes);

        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < hash.Length; i++)
        {
            builder.Append(hash[i].ToString("X2"));
        }

        return builder.ToString();
    }
    catch (Exception ex)
    {
        return "";
    }
}

/// <summary>
/// SHA256加密
/// </summary>
public static string SHA256(string data)
{
    byte[] SHA256Data = Encoding.UTF8.GetBytes(data);
    SHA256Managed Sha256 = new SHA256Managed();
    byte[] bytes = Sha256.ComputeHash(SHA256Data);
    Sha256.Clear();

    //Replace掉“-”后长度为64,视需求而定
    return BitConverter.ToString(bytes).Replace("-", "");
}

DES

使用一个 56 位的密钥以及附加的 8 位奇偶校验位,产生最大 64 位的分组大小。

  • 迭代的对称分组密码
  • 16 个循环,使用异或,置换,代换,移位操作四种基本运算
  • 使用 Feistel 技术

待加密文本块分成两半,使用子密钥对其中一半应用循环功能,然后将输出与另一半进行“异或”运算;接着交换这两半、迭代操作,但最后一个循环不交换。

该加密方法了解即可。


学习 RSA 之前,再明确 2 个概念。前面的 MD5 是不可逆,而 可逆加密分为对称加密和非对称加密

对称加密

发送方和接收方采用相同的密钥进行加密和解密。

  • 算法公开、计算量小、加密速度快、效率高
  • 加解密密钥相同、安全性略低,密钥管理开销大

非对称加密

加密和解密使用不同的密钥的一类加密算法。通常有两个密钥A和B,用密钥A加密数据得到的密文,只有密钥B可以进行解密操作(即使密钥A也无法解密),相反,使用了密钥B加密数据得到的密文,只有密钥A可以解密。即存在一对公钥和私钥(完全不同但完全匹配),用公钥加密的信息只能用对应的私钥进行解密,反之亦然(通常加密是用公钥,解密是用私钥)。

  • 收发信双方在通信之前,收信方必须将自己早已随机生成的公钥送给发信方,而自己保留私钥
  • 不对称算法拥有两个密钥,特别适用于分布式系统中的数据加密

在实际情况中,通常采用方式:非对称加密算法管理对称算法的密钥,然后用对称加密算法加密数据,集成两类加密算法的优点

  • 既实现加密速度快的优点
  • 又实现安全方便管理密钥的优点

前面的 DES 和 AES 是对称加密方法,下面要介绍的 RSA 则是非对称加密方法。除RSA外:

  • DSA(Digital Signature Algorithm):数字签名算法,一种标准的DSS(数字签名标准)
  • ECC(Elliptic Curves Cryptography):椭圆曲线密码编码学

其中,ECC计算量小、处理速度快,存储空间占用小,抗攻击性强,带宽要求低(IC卡、无线网领域)

RSA

非对称加密,目前最有影响力的支持变长密钥的公共密钥算法,第一个可以同时用于加密和数字签名的算法。

主要用于:(1)数据加密 (2)数字签名(只有非对称加密算法可以)

  • 安全性依赖于大数分解:将两个大质数相乘十分容易,但是对其乘积进行因式分解却极其困难
  • 公开密钥加密体制:一种“由已知加密密钥推导出解密密钥在计算上是不可行的”密码体制
  • 加密密钥、加密算法、解密算法均公开,解密密钥保留

缺点:大数计算速度慢,密钥生成麻烦。

务必明确,RSA 的加密体制和签名体制是不同的算法:

  • 加密公钥加密,私钥解密:KeyGen(密钥生成算法),Encrypt(加密算法),Decrypt(解密算法)
  • 签名私钥签名,公钥验证签名:KeyGen(密钥生成算法),Sign(签名算法),Verify(验证算法)

具体算法细节参见:RSA的公钥和私钥到底哪个才是用来加密和哪个用来解密?@刘巍然-学酥 的讲解。

下面是RSA加解密的简单实现,学习之。

/// <summary>
/// RSA加密的密钥结构
/// </summary>
public class RSAKey
{
	// 公钥
	public string PublicKey { get; set; }
	// 私钥
	public string PrivateKey { get; set; }
}

/// <summary>
/// 全局静态变量,保存密钥对
/// </summary>
private static RSAKey RSA_KEY = new RSAKey();

/// <summary>
/// 生成RSA密钥对
/// </summary>
public static void GenerateRASKey()
{
	RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider();
	RSA_KEY = new RSAKey()
		{
			PublicKey = rsaProvider.ToXmlString(false),
			PrivateKey = rsaProvider.ToXmlString(true)
		};
}
/// <summary>
/// 获取加密公钥
/// </summary>
private static string GetPublicKey()
{
	string publicKey = RSA_KEY.PublicKey;
	return publicKey;
}
/// <summary>
/// 获取解密私钥
/// </summary>
private static string GetPrivateKey()
{
	string privateKey = RSA_KEY.PrivateKey;
	return privateKey;
}

/// <summary>
/// RSA加密
/// </summary>
/// <param name="data">待加密数据</param>
public static string RSAEncrypt(string data)
{
	try
	{
		//获取公钥
		string publicKey = GetPublicKey();

		//创建RSA对象并载入公钥
		RSACryptoServiceProvider rsaPublic = new RSACryptoServiceProvider();
		rsaPublic.FromXmlString(publicKey);

		//数据加密
		byte[] bytesPublic = rsaPublic.Encrypt(Encoding.UTF8.GetBytes(data), false);
		return Convert.ToBase64String(bytesPublic); //使用Base64将byte转换为string
	}
	catch (Exception ex)
	{
		return "";
	}
}

/// <summary>
/// RSA解密
/// </summary>
/// <param name="data">待解密数据</param>
/// <returns></returns>
public static string RSADecrypt(string data)
{
	try
	{
		//获取公钥
		string privateKey = GetPrivateKey();

		//创建RSA对象并载入私钥
		RSACryptoServiceProvider rsaPrivate = new RSACryptoServiceProvider();
		rsaPrivate.FromXmlString(privateKey);

		//数据解密
		byte[] bytesPrivate = rsaPrivate.Decrypt(
			Convert.FromBase64String(data), false);//使用Base64将string转换为byte
		return Encoding.UTF8.GetString(bytesPrivate);
	}
	catch (Exception ex)
	{
		return "";
	}
}

主调用方式

// RSA
EncryptOperation.GenerateRASKey();
string 密文 = EncryptOperation.RSAEncrypt(明文);
string 明文De = EncryptOperation.RSADecrypt(密文);

也可以将密钥对保存在 PublicKey.xml 和 PrivateKey.xml 文件中,PublicKey.xml 中只包含公钥,PrivateKey.xml 中包含公钥和密钥。密钥的获取通过读取xml文件实现。

//将密钥写入指定路径
File.WriteAllText(privateKeyPath, privateKey);
File.WriteAllText(publicKeyPath, publicKey);

//获取密钥对
string publicKey = File.ReadAllText(publicKeyPath);
string privateKey = File.ReadAllText(privateKeyPath);

在实际中,可能会遇到以下几个比较特殊的问题:

(1)私钥加密、公钥解密的要求

在C#中,支持公钥加密私钥解密,但不能逆向使用(Java是可以的)。

如果有该特殊需求,可通过第三方的加解密组件 BouncyCastle 来实现:

具体参见:利用第三方dll实现私钥作RSA加密C#使用BouncyCastle来实现私钥加密,公钥解密的方法

(2)C# 的RSA公钥是XML,如何与 Java 的RSA公钥匹配

涉及公钥和私钥在C#和Java之间的相互转换,共 4 个方法:

using System;
using System.Collections.Generic;
using System.Text;
using System;
using System.Xml;
using Org.BouncyCastle.Asn1.Pkcs;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.X509;

/// <summary>
/// RSA密钥格式转换
/// </summary>
public class RSAKeyConvert
{
	/// <summary>    	  
	/// RSA私钥格式转换,java->.net    	  
	/// </summary>    	  
	/// <param name="privateKey">java生成的RSA私钥</param>    	  
	/// <returns></returns>   	  
	public static string RSAPrivateKeyJava2DotNet(string privateKey)	  
	{	  
	   var privateKeyParam =		
		   (RsaPrivateCrtKeyParameters)PrivateKeyFactory.CreateKey(Convert.FromBase64String(privateKey));		
	   return		
		  string.Format(		
			  "<RSAKeyValue><Modulus>{0}</Modulus><Exponent>{1}</Exponent><P>{2}</P><Q>{3}</Q><DP>{4}</DP><DQ>{5}</DQ><InverseQ>{6}</InverseQ><D>{7}</D></RSAKeyValue>",		
			  Convert.ToBase64String(privateKeyParam.Modulus.ToByteArrayUnsigned()),		
			  Convert.ToBase64String(privateKeyParam.PublicExponent.ToByteArrayUnsigned()),		
			  Convert.ToBase64String(privateKeyParam.P.ToByteArrayUnsigned()),		
			  Convert.ToBase64String(privateKeyParam.Q.ToByteArrayUnsigned()),		
			  Convert.ToBase64String(privateKeyParam.DP.ToByteArrayUnsigned()),		
			  Convert.ToBase64String(privateKeyParam.DQ.ToByteArrayUnsigned()),		
			  Convert.ToBase64String(privateKeyParam.QInv.ToByteArrayUnsigned()),		
			  Convert.ToBase64String(privateKeyParam.Exponent.ToByteArrayUnsigned()));		
	}	  
	  
	/// <summary>    	  
	/// RSA私钥格式转换,.net->java    	  
	/// </summary>    	  
	/// <param name="privateKey">.net生成的私钥</param>    	  
	/// <returns></returns>   	  
	public static string RSAPrivateKeyDotNet2Java(string privateKey)	  
	{	  
	   XmlDocument doc = new XmlDocument();		
	   doc.LoadXml(privateKey);		
	   BigInteger m = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("Modulus")[0].InnerText));		
	   BigInteger exp = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("Exponent")[0].InnerText));		
	   BigInteger d = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("D")[0].InnerText));		
	   BigInteger p = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("P")[0].InnerText));		
	   BigInteger q = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("Q")[0].InnerText));		
	   BigInteger dp = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("DP")[0].InnerText));		
	   BigInteger dq = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("DQ")[0].InnerText));		
	   BigInteger qinv = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("InverseQ")[0].InnerText));		
	   RsaPrivateCrtKeyParameters privateKeyParam = new RsaPrivateCrtKeyParameters(m, exp, d, p, q, dp, dq, qinv);		
	   PrivateKeyInfo privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(privateKeyParam);		
	   byte[] serializedPrivateBytes = privateKeyInfo.ToAsn1Object().GetEncoded();		
	   return Convert.ToBase64String(serializedPrivateBytes);		
	}	  

	/// <summary>    	  
	/// RSA公钥格式转换,java->.net    	  
	/// </summary>    	  
	/// <param name="publicKey">java生成的公钥</param>    	  
	/// <returns></returns>    	  
	public static string RSAPublicKeyJava2DotNet(string publicKey)	  
	{	  
	   RsaKeyParameters publicKeyParam = (RsaKeyParameters)PublicKeyFactory.CreateKey(Convert.FromBase64String(publicKey));		
	   return string.Format("<RSAKeyValue><Modulus>{0}</Modulus><Exponent>{1}</Exponent></RSAKeyValue>",		
		   Convert.ToBase64String(publicKeyParam.Modulus.ToByteArrayUnsigned()),		
		   Convert.ToBase64String(publicKeyParam.Exponent.ToByteArrayUnsigned()));		
	}	  
	  
	/// <summary>    	  
	/// RSA公钥格式转换,.net->java    	  
	/// </summary>    	  
	/// <param name="publicKey">.net生成的公钥</param>    	  
	/// <returns></returns>   	  
	public static string RSAPublicKeyDotNet2Java(string publicKey)	  
	{	  
	   XmlDocument doc = new XmlDocument(); doc.LoadXml(publicKey);		
	   BigInteger m = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("Modulus")[0].InnerText));		
	   BigInteger p = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("Exponent")[0].InnerText));		
	   RsaKeyParameters pub = new RsaKeyParameters(false, m, p);		
	   SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(pub);		
	   byte[] serializedPublicBytes = publicKeyInfo.ToAsn1Object().GetDerEncoded();		
	   return Convert.ToBase64String(serializedPublicBytes);		
	}	  
}

具体参见:C#的RSA密钥XML格式与Java的其密钥格式匹配

扩展应用参见:SHA256WithRSA


下面扩展学习数字签名和数字证书、以及与上述加密算法之间的关系

数字签名

Digital Signature,公钥数字签名/电子签章,私钥签名、公钥验证签名。

  • 信息完整性
  • 发送者身份验证
  • 不可抵赖性

数字证书

Certificate Authority,数字证书是一个经证书授权中心数字签名的包含公开密钥拥有者信息以及公开密钥的文件。

最简单的证书包含一个公开密钥、名称以及证书授权中心的数字签名。

  • 一段含有证书持有者身份信息并经过认证中心审核签发的电子数据
  • 方便:即时申请、即时开通、即时使用
  • 一个重要的特征:只在特定的时间段内有效
  • 证明站点、文件有效合法安全

关系理解

  • 两者都是非对称加密技术的应用
  • 把数字证书的hash值用私钥加密得到数字签名
  • 信息 + HASH = 摘要,摘要 + 私钥 = 数字签名(给收方做对比用的,验证收发内容是否一致
  • 公钥 + 相关信息 + CA私钥 = 数字证书(验证发送者是否正确,是可信任的公钥

具体参考:数字签名和数字证书数字签名、数字证书、SSL、https是什么关系?-知乎

原文地址:https://www.cnblogs.com/wjcx-sqh/p/8341749.html