Windows phone应用开发[19]-RSA数据加密

在这个系列的第十六章节中Windows phone应用开发[16]-数据加密 中曾详细讲解过windows phone 经常使用的MD5,HMAC_MD5,DES,TripleDES[3DES] 数据加密的解决方案.本篇做为windows phone 数据加密一个弥补篇幅.将专门来说解windows phone rsa数据加密存在问题解决方案以及和其余平台[Java]互通存在的问题.php

RSA算法起源与现状

若是你关注过近现代密码学的发展.你必定不会否定RSA的出现的重要意义.css

1263035386e4QfBRjD

[上图:德国的洛伦兹密码机,所使用的二次世界大战加密机密邮件]html

RSA 做为计算机安全通讯的基石.保证数据在传输过程不被第三方破解.在RSA[非对称加密算法]出现以前 也就是1976年以前.在一般使用数据通讯传输过程大多会使用[对称算法].简单用以下一个使用场景来讲明这个原理:java

IC168364

对称算法加密最大特色是:若是咱们要从A点要向B点传输加密数据. 首先咱们在A点采用加密算法进行加密.加密数据传输给B点后. B点必须采用一样规则才能解密.而A做为传输方必须告诉接收方加密规则.不然B点则没法解密. 而这个过程难以免要传输和保存密文数据的密钥. 而一旦涉及到密钥传递就存在被第三方破解和拦截的风险.git

至此以后.在1977年由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)三人共同提出新的加密算法也就是如今的[非对称加密算法]. 这个密钥传输问题才得以免. 而RSA加密算也正是采用三人首字母命名而成的. 而其实RSA最先是在1976年,已经由Diffie和Hellman 在“New Directions in Cryptography [密码学新方向]”一文中就已经提出. 二人在文章中提出在不传递密钥的就能够实现数据的解密新构思. 而这也正是“非对称加密算法”最先的雏形. 正是由于二人新的构思才促使RSA基于该理论上出现.github

RSA原理图以下:算法

IC21919

在B点能够生成两种密钥分别公钥和私钥. 公钥是公开.任何人均可以得到.但私钥倒是保密的. 这样一来数据传输流程就发生了变化。A点只须要获取B点颁发的公钥.而后对传输的数据进行加密. B点获取加密数据后在采用私钥进行解密.这样一来采用公钥加密数据只有采用私钥才能解密成功.那么只需确保私钥的安全不被泄露.同时即便知道了算法和若干密文不足以肯定密钥的状况.整个通讯过程都是安全的.windows

RSA的明文、密文是0到n-1之间的整数,一般n的大小为1024位或309位十进制数.也就是说密钥越长.它整个加密过程就越难以被破解.而目前被破解的最长RSA密钥是768个二进制位. 而基于1024位的RSA密钥也是相对安全的[至少目前公开的数据显示 没有被破解的先例].RSA 密钥是经过以下过程产生的:数组

A:随机选择两个大素数 p, q安全

B:计算 N=p.q [注意 ø(N)=(p-1)(q-1)]

C:选择 e使得1<e<ø(N),且gcd(e,ø(N))=1

D:解下列方程求出 d  [e.d=1 mod ø(N) 且 0≤d≤N]

E:公布公钥: KU={e,N}

F:保存私钥: KR={d,p,q}

其实只须要知道一些数论知识基本能够理解. 当咱们知道RSA 原理后. 理论上能够经过以下三种方式来破解攻击RSA算法体系:

三种攻击 RSA的方法:

A:强力穷举密钥

B:数学攻击:实质上是对两个素数乘积的分解

C:时间攻击:依赖解密算法的运行时间

而相对可行是采用数学方式,主要是基于因数分解的问题:

三种数学攻击方法

A:分解 N=p.q, 所以可计算出 ø(N),从而肯定d

B:直接肯定ø(N),而后找到d

C:直接肯定d

其实说到本质. 正式由于对极大整数作因数分解的难度决定了RSA算法的可靠性。换言之,对一极大整数作因数分解愈困难,RSA算法愈可靠。尽管如此,只有一些RSA算法的变种[来源请求]被证实为其安全性依赖于因数分解。假若有人找到一种快速因数分解的算法的话,那么用RSA加密的信息的可靠性就确定会极度降低。但找到这样的算法的可能性是很是小的。今天只有短的RSA钥匙才可能被强力方式解破。到2008年为止,世界上尚未任何可靠的攻击RSA算法的方式。只要其钥匙的长度足够长,用RSA加密的信息其实是不能被解破的。但在分布式计算量子计算机理论日趋成熟的今天,RSA加密安全性受到了必定挑战.

wp rsa数据加密

再回到本篇正题.在.NET 平台其实早在FrameWork 2.0 时就已经提供了RSACryptoServiceProvider 来实现基于RSA算法加密.并一直延续到windows phone 版本中.首先来看看基于windows phone 平台作一个最简单加密“Hello World”. 构建一个空白的Project .实现起来很是简单:

   1:   RSACryptoServiceProvider rsa = new System.Security.Cryptography.RSACryptoServiceProvider();
   2:   byte[] contentBytes = System.Text.Encoding.UTF8.GetBytes(“Hello World”);
   3:   return Convert.ToBase64String(rsa.Encrypt(contentBytes, false));

首先声明一个RSACryptoServiceProvider 对象,而后对须要加密的数据采用UTF8格式编码成字节数组.在采用RSACryptoServiceProvider对象的Encrypt()方法执行加密.加密数据由于返回时字节数组转换成Base64字符串可见.

作到这确定有人会问RSA加密结果成不是采用公钥和私钥吗? 那RSACryptoServiceProvider对象公钥和私钥在那?咱们也能够采用以下代码来查看:

   1:   RSACryptoServiceProvider rsa= new System.Security.Cryptography.RSACryptoServiceProvider();
   2:   rsa.ExportParameters(true);

ExportParameters()方法会返回一个RSAParameters结构的对象. 方法的参数True和False用来标识导出的RSAParameters对象是否包含私有参数. 其实当咱们采用默认的构造函数实例化一个RSACryptoServiceProvider对象时..net会帮咱们默认生成一对公钥和私钥.能够经过调用其ExportParameters()方法或ToXmlString()[WP中没有改方法]方法导出密钥.在看来看RSAParameters结构包含的参数属性:

2013-10-15_183151

其实如上能够发现这些参数正式生成RSA公钥和密钥须要的参数. P和Q表明两个大素数.D表明私钥指数.Exponent表明公钥指数.把导出的默认的RSAParameters对象能够看到公钥指数指数是65537. 这是微软选择默认构造方法时生成的公钥都是同样的.65537转换成字节数组就是Exponent的值. 而AQAB正是65337 Base64编码而来. 但当咱们把这个字节数组转换成Base64String看一下结果:

   1:  Console.WriteLine(Convert.ToBase64String(Encoding.Default.GetBytes("65537")));

输出倒是”NjU1Mzc=” 而并非咱们预想的AQAB. 其实65337 是一个大素数. 咱们不能把其当作一个普通的字符串来直接处理. 首先咱们须要把它转换成一个二进制字节数组,每8位一截取. 依次取6位每一个前面加上“00”. 转换十进制就是咱们对应数据. 固然更多细节能够参考以下这篇文章.

其实整个加密过程就像一个临时的回话Session.若是咱们加密数据对外使用是就须要进行传递. 咱们就须要对这个临时回话的RSA公钥和私钥进行保存.但更多的应用场景里咱们会发现实际项目中通常状况采用公钥证书也就是[.Cer]文件来保存和传递公钥. 而后应用程序中导入公钥文件中的数据.来进行加密数据. 而把一个文件的字节流数组转换咱们须要公钥对象RSAParameter对象,这时咱们须要用到X509PublicKeyParser这个类. 但遗憾的是目前Windows Phone 并无提供这个类的实现.但在C-Sharper上已经有人完整移植了该版本.完整的类以下:

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Security.Cryptography;
   5:  using System.Security.Cryptography.X509Certificates;
   6:  using System.Text;
   7:   
   8:  namespace System.Security.Cryptography
   9:  {   
  10:      internal abstract class AbstractAsn1Container
  11:      {
  12:          #region Property
  13:          private int offset;
  14:          private byte[] data;
  15:          private byte tag;
  16:          #endregion
  17:   
  18:          #region Action
  19:          internal protected AbstractAsn1Container(byte[] abyte, int i, byte tag)
  20:          {
  21:              this.tag = tag;
  22:              if (abyte[i] != tag)
  23:              {
  24:                  throw new Exception("Invalid data. The tag byte is not valid");
  25:              }
  26:              int length = DetermineLength(abyte, i + 1);
  27:              int bytesInLengthField = DetermineLengthLen(abyte, i + 1);
  28:              int start = i + bytesInLengthField + 1;
  29:              this.offset = start + length;
  30:              data = new byte[length];
  31:              Array.Copy(abyte, start, data, 0, length);
  32:          }
  33:   
  34:          internal int Offset
  35:          {
  36:              get
  37:              {
  38:                  return offset;
  39:              }
  40:          }
  41:   
  42:          internal byte[] Bytes
  43:          {
  44:              get
  45:              {
  46:                  return this.data;
  47:              }
  48:          }
  49:   
  50:          internal protected virtual int DetermineLengthLen(byte[] abyte0, int i)
  51:          {
  52:              int j = abyte0[i] & 0xff;
  53:              switch (j)
  54:              {
  55:                  case 129:
  56:                      return 2;
  57:   
  58:   
  59:                  case 130:
  60:                      return 3;
  61:   
  62:   
  63:                  case 131:
  64:                      return 4;
  65:   
  66:   
  67:                  case 132:
  68:                      return 5;
  69:   
  70:   
  71:                  case 128:
  72:                  default:
  73:                      return 1;
  74:              }
  75:          }
  76:   
  77:          internal protected virtual int DetermineLength(byte[] abyte0, int i)
  78:          {
  79:              int j = abyte0[i] & 0xff;
  80:              switch (j)
  81:              {
  82:                  case 128:
  83:                      return DetermineIndefiniteLength(abyte0, i);
  84:   
  85:   
  86:                  case 129:
  87:                      return abyte0[i + 1] & 0xff;
  88:   
  89:   
  90:                  case 130:
  91:                      int k = (abyte0[i + 1] & 0xff) << 8;
  92:                      k |= abyte0[i + 2] & 0xff;
  93:                      return k;
  94:   
  95:   
  96:                  case 131:
  97:                      int l = (abyte0[i + 1] & 0xff) << 16;
  98:                      l |= (abyte0[i + 2] & 0xff) << 8;
  99:                      l |= abyte0[i + 3] & 0xff;
 100:                      return l;
 101:              }
 102:              return j;
 103:          }
 104:   
 105:          internal protected virtual int DetermineIndefiniteLength(byte[] abyte0, int i)
 106:          {
 107:              if ((abyte0[i - 1] & 0xff & 0x20) == 0)
 108:                  throw new Exception("Invalid indefinite length.");
 109:              int j = 0;
 110:              int k;
 111:              int l;
 112:              for (i++; abyte0[i] != 0 && abyte0[i + 1] != 0; i += 1 + k + l)
 113:              {
 114:                  j++;
 115:                  k = DetermineLengthLen(abyte0, i + 1);
 116:                  j += k;
 117:                  l = DetermineLength(abyte0, i + 1);
 118:                  j += l;
 119:              }
 120:   
 121:   
 122:              return j;
 123:          }
 124:          #endregion
 125:      }
 126:   
 127:      #region Internal Object 
 128:      internal class IntegerContainer : AbstractAsn1Container
 129:      {
 130:          internal IntegerContainer(byte[] abyte, int i)
 131:              : base(abyte, i, 0x2)
 132:          {
 133:          }
 134:      }
 135:   
 136:      internal class SequenceContainer : AbstractAsn1Container
 137:      {
 138:          internal SequenceContainer(byte[] abyte, int i)
 139:              : base(abyte, i, 0x30)
 140:          {
 141:          }
 142:      }
 143:   
 144:      public class X509PublicKeyParser
 145:      {
 146:          public static RSAParameters GetRSAPublicKeyParameters(byte[] bytes)
 147:          {
 148:              return GetRSAPublicKeyParameters(bytes, 0);
 149:          }
 150:   
 151:   
 152:          public static RSAParameters GetRSAPublicKeyParameters(byte[] bytes, int i)
 153:          {
 154:              SequenceContainer seq = new SequenceContainer(bytes, i);
 155:              IntegerContainer modContainer = new IntegerContainer(seq.Bytes, 0);
 156:              IntegerContainer expContainer = new IntegerContainer(seq.Bytes, modContainer.Offset);
 157:              return LoadKeyData(modContainer.Bytes, 0, modContainer.Bytes.Length, expContainer.Bytes, 0, expContainer.Bytes.Length);
 158:          }
 159:   
 160:   
 161:          public static RSAParameters GetRSAPublicKeyParameters(X509Certificate cert)
 162:          {
 163:              return GetRSAPublicKeyParameters(cert.GetPublicKey(), 0);
 164:          }
 165:   
 166:   
 167:          private static RSAParameters LoadKeyData(byte[] abyte0, int i, int j, byte[] abyte1, int k, int l)
 168:          {
 169:              byte[] modulus = null;
 170:              byte[] publicExponent = null;
 171:              for (; abyte0[i] == 0; i++)
 172:                  j--;
 173:   
 174:   
 175:              modulus = new byte[j];
 176:              Array.Copy(abyte0, i, modulus, 0, j);
 177:              int i1 = modulus.Length * 8;
 178:              int j1 = modulus[0] & 0xff;
 179:              for (int k1 = j1 & 0x80; k1 == 0; k1 = j1 << 1 & 0xff)
 180:                  i1--;
 181:   
 182:   
 183:              if (i1 < 256 || i1 > 4096)
 184:                  throw new Exception("Invalid RSA modulus size.");
 185:              for (; abyte1[k] == 0; k++)
 186:                  l--;
 187:   
 188:   
 189:              publicExponent = new byte[l];
 190:              Array.Copy(abyte1, k, publicExponent, 0, l);
 191:              RSAParameters p = new RSAParameters();
 192:              p.Modulus = modulus;
 193:              p.Exponent = publicExponent;
 194:              return p;
 195:          }
 196:      }
 197:   
 198:      #endregion
 199:  }

那么拿到这个类的实现.咱们能够在windows phone 实现把公钥文件[.cer]字节流数据转换公钥信息RSAParameters对象导入到应用程序中.导出公钥PublicKey方法能够采用以下代码实现:

   1:  private System.Security.Cryptography.RSAParameters ConvertPublicKeyToRsaInfo()
   2:  {
   3:     System.Security.Cryptography.RSAParameters RSAKeyInfo;
   4:     using (var cerStream = Application.GetResourceStream(new Uri("/RSAEncryptDemo;component/Files/DemoPublicKey.cer", UriKind.RelativeOrAbsolute)).Stream)
   5:     {
   6:        byte[] cerBuffer = new byte[cerStream.Length];
   7:        cerStream.Read(cerBuffer, 0, cerBuffer.Length);
   8:        System.Security.Cryptography.X509Certificates.X509Certificate cer = new System.Security.Cryptography.X509Certificates.X509Certificate(cerBuffer);
   9:        RSAKeyInfo = X509PublicKeyParser.GetRSAPublicKeyParameters(cer.GetPublicKey());                    
  10:     }
  11:     return RSAKeyInfo;
  12:  }

在执行这段代码你须要保证添加进来引入的公钥.Cer文件是做为项目资源被访问的.Build Action 设置为Resource. 这样咱们就能够直接经过经过公钥文件的方式来获取公钥数据的信息.而后再默认构建的RSACryptoServiceProvider对象中经过ImportParameters()方法导入公钥信息:

   1:  RSACryptoServiceProvider rsa = new System.Security.Cryptography.RSACryptoServiceProvider();
   2:  rsa.FromXmlString(publickey);
   3:  RSAParameters publicKey = ConvertPublicKeyToRsaInfo();
   4:  rsa.ImportParameters(publicKey);

其实默认的RSACryptoServiceProvider对象也能够采用XMl格式来导入公钥数据. 但前提是你须要知道公钥的Modulus和Exponent对应的值.但大多状况咱们拿到公钥文件来实现跨平台传递信息的.若是须要这两个公钥字段数据. 咱们能够经过服务器端经过接口来传递公钥的信息. RSA特色就是公钥是能够公开的.只要保证私钥是保密便可实现加密. 因此这种传递彻底可行的. 拿到公钥数据后而后组合成对应XML 格式导入RSACryptoServiceProvider对象中.代码实现以下:

   1:  //导入文件方式代替 服务器接口的数据 原理是一至的
   2:  RSAParameters rsaDefineRap = ConvertPublicKeyToRsaInfo();
   3:  string modulus = Convert.ToBase64String(rsaDefineRap.Modulus);
   4:  string exponent = Convert.ToBase64String(rsaDefineRap.Exponent);
   5:   
   6:  string publickey = @"<RSAKeyValue><Modulus>" + modulus + "</Modulus><Exponent>" + exponent + "</Exponent></RSAKeyValue>";
   7:  RSACryptoServiceProvider rsa= new System.Security.Cryptography.RSACryptoServiceProvider();
   8:  rsa.FromXmlString(publickey);

当咱们拿到公钥的数据须要对拿到Byte字节数据进行Base64String编码.而后经过如上格式[不能错]拼接XML字符串.在经过FromXmlString()方法导入.也能够一样实现公钥信息的导入.在作Windows phone RSA 我也看到有人在Windows 8也遇到导入公钥的问题[Windows 8 系列(四):Win8 RSA加密相关问题]. 这个主要由于Windows 8导入公钥数据采用ASC编码.公钥信息头数据缺失致使的. 这个问题解决方案另外博文会说到.

如上咱们采用两种方式来导入公钥PublicKey数据.拿到公钥数据后咱们采用我么本身公钥进行数据加密 加密操做代码以下:

   1:  RSAParameters rsaDefineRap = ConvertPublicKeyToRsaInfo();
   2:  string modulus = Convert.ToBase64String(rsaDefineRap.Modulus);
   3:  string exponent = Convert.ToBase64String(rsaDefineRap.Exponent);
   4:   
   5:  //Import Public Key
   6:  string publickey = @"<RSAKeyValue><Modulus>" + modulus + "</Modulus><Exponent>" + exponent + "</Exponent></RSAKeyValue>";
   7:  RSACryptoServiceProvider rsaCrypt = new System.Security.Cryptography.RSACryptoServiceProvider();
   8:  rsaCrypt.FromXmlString(publickey); 
   9:   
  10:  //Data Encrypt
  11:  byte[] encryBytes=  rsaCrypt.Encrypt(System.Text.Encoding.UTF8.GetBytes("Hello World"), false);
  12:  return Convert.ToBase64String(encryBytes);

发现加密数据成功.在执行加密操做Encrypt()方法中不少人会碰到CryptographicException Message 为Bad Length的异常 以下:

2013-10-14_141104

Bad Length Exception.刚开始调试时特别频繁.这个异常主要问题是由于加密的数据长度超过公钥可以加密数据的范围致使的. 在RSA中咱们可以加密数据的大小取决于公钥的长度大小.公钥长度和保密之间区别以下:

2013-10-15_193451

RSA的数据加密大可能是针对数据量较小的. 首先须要确认当前公钥大小是多Bit.咱们目前演示的公钥文件.cer是1024 Bit的. Padding填充算法是[PKCS # 1 V1.5],ok 那么咱们为了不在加密操做出现这个异常咱们须要加密数据大小Size进行判断. 能够采用以下方法来判断:

   1:          private bool IsOverEncryptStrMaxSize(int encryptDataSize)
   2:          {
   3:              bool isOver = false;
   4:              if (!IsDoOAEPPadding)
   5:              {
   6:                  #region When Encrypt Is Does't Need Padding Operator
   7:                  int MaxBlockSize = ((KeySize - 384) / 8) + 37;
   8:                  if (encryptDataSize > MaxBlockSize)
   9:                      isOver = true;
  10:                  #endregion
  11:              }
  12:              else
  13:              {
  14:                  #region When Encrypt Is Need Padd Operator
  15:                  int MaxBlockSize = ((KeySize - 384) / 8) + 7;
  16:                  if (encryptDataSize > MaxBlockSize)
  17:                      isOver = true;
  18:                  #endregion
  19:              }
  20:              return isOver;
  21:          }

参数encryptDataSize 是当前把须要加密的数据转换成字节数组的长度. IsDoOAEPPanding是判断是否采用填充算法.这里须要说明的是:

   1:     byte[] encryBytes=  rsaCrypt.Encrypt(System.Text.Encoding.UTF8.GetBytes("Hello World"), false);

加密操做. 若是采用False则默认RSA加密采用填充算法是[PKCS#1 1.5]Ps:咱们证书一致. 若是为true.则对应加密填充算法是OAEP填充方式.目前.NET在RSA只支持[OAEP & PKCS#1 1.5]两种填充算法.固然还有更多.后买会讲到.咱们这里采用FAlse也就是[PKCS#1 1.5]和咱们证书保持一致. 若是这样咱们经过该方法计算.若是是1024 Bit大小的公钥可以加密的数据是117个字节. 同理能够推断2048Bit 可以加密 245字节. 也就是说咱们目前可以加密的不可以超过1024Bit对应的117个字节. 若是超过这个字节 就会抛出Bad Length异常.

有人说我要采用RSA加密一个整个Xml文件.这就涉及到另一个问题-RSA如何加密到大数据. 能够参考StackOverFllow 给出解决方案[RSA of large file using C# [duplicate] .其实核心的原理信息头采用RSA进行加密.主干内容也就是大数据内容采用的3DES配合进行加密. 解密是采用相同规则解密便可.并没有难度. 若是须要解决我会在另一个章节讲到.

若是是windows phone 我并不推荐你加密整个文件或是其余特别大的数据方式.若是你的数据小于1M 之内. 采用RSA加密. 并不推荐直接加密整个数据内容. 而是在加密前.把整个数据进行MD5加密.这样一来几百字节数据内容会被生成一个惟一的16位或32为大小的字符进行代替. 而后把MD5 对应的字符数据进行加密.就不会出现大数据加密时Bad Length异常.固然解密时也须要一样的规则. RSA加密前规则和解密必须保证一致.便可.

跨平台互通

RSA在客户端加密通常要涉及到服务器端的解密. 而服务器端大多采用其余平台构建的.相似Java. 在调试时大多状况会碰到客户端加密已经成功.而服务器端一值没法解密.存在互通的问题.

首先来看一个基本问题咱们在客户端采用如上加密方式来加密同一个MD5 字符串:

   1:  //MD5 String
   2:  7E9667A96C301BF79979E49956D189C7

加密两次查看查看同一个公钥对同一条数据的加密结果:

   1:  //First
   2:  tbn5ejK21uJxObBYRp1Bh8k9FrmMWDFKRuithTKU7OITeO8Wss+j6Q3FAcE7x7EA1KpPMhCgnIj6BbQlw+Xeat8Kj/s8SLH3Vel0UPS3+gvshDW8vm2qQsPlsbg3HQ7xD6P/OLdnRleOY9VWG31n3ZouYEKJp2G0FnK/w2VD9zs=
   3:   
   4:  //Second
   5:  lMw9QaU8MtXvyknEv2M9RNGcOR2UQKC44BD4i95seBjBnthcXosGh9O9DCYBEQuMNTTXX8pI5ipUwEBJNKbN4jmx7lPoxg4khxbmgaofq71sd1hFwY58Or29lxprm4dXPHkM2LsgifRazlpddHN3lKszH3i065fy8LJcrNmZmCU=

很明显咱们发如今同一公钥针对同一条数据加密结果竟然是不一致的.而一样的算法在Java针对同一条数据加密始终都是一致的.

这里须要说明的是在.net平台上为了保证RSA加密算法的安全性.在每次加密的时候都会生成必定的随机数和原始数据一块被加密.致使每次加密的结果由于添加随机数不一样加密结果也不一致.其实这些随机数也是遵循算法标准的.也就是上面提到的随机填充算法.好比NoPadding、ISO10126Padding、OAEPPadding、PKCS1Padding、PKCS5Padding、SSL3Padding,而最多见的应该是OAEPPadding【Optimal Asymmetric Encryption Padding】、PKCS1Padding;.Net支持PKCS1Padding或OAEPPadding,其中OAEPPadding仅在XP或更高版本的操做系统上可用.

Java的实现,主要经过Cipher.getInstance实现,传入的参数是描述为产生某种输出而在给定的输入上执行的操做(或一组操做)的字符串。必须包括加密算法的名称,后面可能跟有一个反馈模式和填充方案。这样的实现就比较灵活,咱们能够经过参数指定不一样的反馈模式和填充方案;好比Cipher.getInstance("RSA/ECB/PKCS1Padding"),或者Cipher.getInstance("RSA")都可,但用其加密的效果也会不同.

NET平台加入随机数的概念.当采用.NEt 平台加密后.在经过Java服务器端解密时因Java采用的是标准的RSA加密.不添加随机数的. 致使java平台服务器端解密失败.这个时候回致使.NET 加密数据没法与Java平台的互通.

解决这个问题有两种思路:

A:在.NET 客户端采用剔除掉加密时添加的随机数. 采用标准的RSA算法加密数据. Java服务器端采用标准RSA解密便可

B:.NET客户端和Java服务器端采用相同随机数填充标准. 实现一致的数据加密和解密操做.

思路A.其实如今。NEt FrameWork提供的RSACryptoServiceProvider对象由于在家随机数. 咱们须要本身实现一个标准的RSA算法. 而后对须要加密数据进行加密.针对windows phone 标准的RSA算法移植代码能够参考[C# BigInteger Class]: 这里移植版本.这样一来咱们加密操做就采用BitInteger类来进行 代码以下:

   1:   System.Security.Cryptography.RSAParameters RSAKeyInfo = ConvertPublicKeyToRsaInfo();
   2:   BigInteger bi_e = new BigInteger(RSAKeyInfo.Exponent);
   3:   BigInteger bi_n = new BigInteger(RSAKeyInfo.Modulus);
   4:   
   5:   BigInteger bi_data = new BigInteger(System.Text.Encoding.UTF8.GetBytes("Hello World"));//
   6:   BigInteger bi_encrypted = bi_data.modPow(bi_e, bi_n);
   7:   return bi_encrypted.getBytes();

.NET客户端构建一个公钥对应的BigInteger e、一个模对应的BigInteger n和一个明文对应的BigInteger m,而后执行语句BigInteger c=m.modPow(e,n),即可以实现加密操做,密文为c,这样的加密是标准加密,没有附加任何填充算法的加密. 然对加密数据进行Base64String编码. 在传递服务器端解密验证.经过.

思路B.若是.net和Java 平台在进行RSA加密时采用的填充保持标准一致. 那么Java和.net 平台数据加密和解密.即便每次加密数据结果都一致.也是能够互通的.而.NET 平台目前只是实现两种填充方式.[OAEP & PKCS#1 1.5]. 而Java平台刚好支持其中的PKCS#1 1.5. 这样一来咱们能够采用统一的填充标准加密便可.

   1:  rsa.Encrypt(contentBytes, false)

加密时设置是否填充为False.也就是采用默认的PKC#1 1.5填充标准.

但调试依然发现服务器端解密失败. 经历过很长一段时间都搞不清楚这个问题具体出在哪.直到我看到MSDN上关于RSACryptoServiceProvider对象Remark里卖弄描述.瞬间明白这个问题出在那以下:

Remark:

This is the default implementation of RSA.

The RSACryptoServiceProvider supports key lengths from 384 bits to 16384 bits in increments of 8 bits if you have the Microsoft Enhanced Cryptographic Provider installed. It supports key lengths from 384 bits to 512 bits in increments of 8 bits if you have the Microsoft Base Cryptographic Provider installed.

Interoperation with the Microsoft Cryptographic API (CAPI)

Unlike the RSA implementation in unmanaged CAPI, the RSACryptoServiceProvider class reverses the order of an encrypted array of bytes after encryption and before decryption. By default, data encrypted by the RSACryptoServiceProvider class cannot be decrypted by the CAPI CryptDecrypt function and data encrypted by the CAPI CryptEncrypt method cannot be decrypted by the RSACryptoServiceProvider class.

If you do not compensate for the reverse ordering when interoperating between APIs, the RSACryptoServiceProvider class throws a CryptographicException.

To interoperate with CAPI, you must manually reverse the order of encrypted bytes before the encrypted data interoperates with another API. You can easily reverse the order of a managed byte array by calling the Array.Reverse method.

你看到了吧.最后两段译文:

默认状况下,CAPI CryptDecrypt 函数没法解密由 RSACryptoServiceProvider 类加密的数据,RSACryptoServiceProvider 类没法解密由 CAPI CryptEncrypt 方法加密的数据。

若是在 API 之间互相操做时没有对颠倒的顺序进行补偿,RSACryptoServiceProvider 类会引起 CryptographicException

要同 CAPI 相互操做,必须在加密数据与其余 API 相互操做以前,手动颠倒加密字节的顺序。 经过调用 Array.Reverse 方法可轻松颠倒托管字节数组的顺序

也就是说。net平台对标准的RSA算法字节数组采用自动翻转.你要是还原到标准的加密结果须要采用Array.Reverse()方法翻转过来/.[汗啊].我采用系统对象加密采用Reverse()操做发现Java服务器端解密成功了 完成代码以下:

   1:             #region Get Data Encrypt Public Key
   2:              RSAParameters rsaDefineRap = ConvertPublicKeyToRsaInfo();
   3:              string modulus = Convert.ToBase64String(rsaDefineRap.Modulus);
   4:              string exponent = Convert.ToBase64String(rsaDefineRap.Exponent);
   5:              string publickey = @"<RSAKeyValue><Modulus>" + modulus + "</Modulus><Exponent>" + exponent + "</Exponent></RSAKeyValue>";
   6:   
   7:              RSACryptoServiceProvider rsa = new System.Security.Cryptography.RSACryptoServiceProvider();
   8:              rsa.ImportParameters(rsaDefineRap);
   9:              //rsa.ExportParameters(false);
  10:   
  11:              #region Control Encrypt To Md5 Format String
  12:              string encryptWithMd5Str = MD5Core.GetHashString(needEncryptStr);
  13:              #endregion
  14:   
  15:              #region Encrypt Not Over The Max Size
  16:              byte[] needEncryptBytes = System.Text.UTF8Encoding.UTF8.GetBytes(encryptWithMd5Str);
  17:              byte[] encryptBytes = null;
  18:              if (!IsOverEncryptStrMaxSize(needEncryptBytes.Length))
  19:              {
  20:                  encryptBytes = rsa.Encrypt(needEncryptBytes, IsDoOAEPPadding);
  21:                  if (encryptBytes != null)
  22:                      encryptBytes.Reverse();
  23:              }
  24:              #endregion
  25:   
  26:              return encryptBytes == null ? "" : Convert.ToBase64String(encryptBytes);
  27:              #endregion

若是你在最后数据加密成功后忘了这段代码:

   1:    if (encryptBytes != null)
   2:        encryptBytes.Reverse();

我只能对你说God Bless you.

如上两种思路分别都能实现.NET 和Java平台的互通.其中思路B的方式最为廉价.只须要确保两个平台之间填充算法一致. 而后对加密结果进行Reverse()翻转操做便可.这个问题在我看到MSDN文档REmark文档忽然就释然了.

核心代码都在上面.须要源码在直接@我把. 等我整理上传到Github上[https://github.com/chenkai]

Contact [@chenkaihome]

参考资料:

C# BigInteger Class

RSACryptoServiceProvider   

Why does my Java RSA encryption give me an Arithmetic Exception?

Java RSA Encrypt - Decrypt .NET - need help