在這個系列的第十六章節中Windows phone應用開發[16]-數據加密 中曾詳細講解過windows phone 經常使用的MD5,HMAC_MD5,DES,TripleDES[3DES] 數據加密的解決方案.本篇做爲windows phone 數據加密一個彌補篇幅.將專門來說解windows phone rsa數據加密存在問題解決方案以及和其餘平臺[Java]互通存在的問題.php
若是你關注過近現代密碼學的發展.你必定不會否定RSA的出現的重要意義.css
[上圖:德國的洛倫茲密碼機,所使用的二次世界大戰加密機密郵件]html
RSA 做爲計算機安全通訊的基石.保證數據在傳輸過程不被第三方破解.在RSA[非對稱加密算法]出現以前 也就是1976年以前.在一般使用數據通訊傳輸過程大多會使用[對稱算法].簡單用以下一個使用場景來講明這個原理:java
對稱算法加密最大特色是:若是咱們要從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原理圖以下:算法
在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加密安全性受到了必定挑戰.
再回到本篇正題.在.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結構包含的參數屬性:
其實如上能夠發現這些參數正式生成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的異常 以下:
Bad Length Exception.剛開始調試時特別頻繁.這個異常主要問題是由於加密的數據長度超過公鑰可以加密數據的範圍致使的. 在RSA中咱們可以加密數據的大小取決於公鑰的長度大小.公鑰長度和保密之間區別以下:
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]
參考資料:
Why does my Java RSA encryption give me an Arithmetic Exception?