在這個系列的第十六章節中Windows phone應用開發[16]-數據加密 中曾詳細講解過windows phone 常用的MD5,HMAC_MD5,DES,TripleDES[3DES] 數據加密的解決方案.本篇作為windows phone 數據加密一個彌補篇幅.將專門來講解windows phone rsa數據加密存在問題解決方案以及和其他平台[Java]互通存在的問題.
RSA算法起源與現狀
如果你關注過近現代密碼學的發展.你一定不會否認RSA的出現的重要意義.
[上圖:德國的洛倫茲密碼機,所使用的二次世界大戰加密機密郵件]
RSA 作為計算機安全通信的基石.保證數據在傳輸過程不被第三方破解.在RSA[非對稱加密算法]出現之前 也就是1976年之前.在通常使用數據通信傳輸過程大多會使用[對稱算法].簡單用如下一個使用場景來說明這個原理:
對稱算法加密最大特點是:如果我們要從A點要向B點傳輸加密數據. 首先我們在A點采用加密算法進行加密.加密數據傳輸給B點后. B點必須采用同樣規則才能解密.而A作為傳輸方必須告訴接收方加密規則.否則B點則無法解密. 而這個過程難以避免要傳輸和保存密文數據的密鑰. 而一旦涉及到密鑰傳遞就存在被第三方破解和攔截的風險.
至此之后.在1977年由羅納德·李維斯特(Ron Rivest)、阿迪·薩莫爾(Adi Shamir)和倫納德·阿德曼(Leonard Adleman)三人共同提出新的加密算法也就是現在的[非對稱加密算法]. 這個密鑰傳輸問題才得以避免. 而RSA加密算也正是采用三人首字母命名而成的. 而其實RSA最早是在1976年,已經由Diffie和Hellman 在“New Directions in Cryptography [密碼學新方向]”一文中就已經提出. 二人在文章中提出在不傳遞密鑰的就可以實現數據的解密新構思. 而這也正是“非對稱加密算法”最早的雛形. 正是因為二人新的構思才促使RSA基於該理論上出現.
RSA原理圖如下:
在B點可以生成兩種密鑰分別公鑰和私鑰. 公鑰是公開.任何人都可以獲得.但私鑰卻是保密的. 這樣一來數據傳輸流程就發生了變化。A點只需要獲取B點頒發的公鑰.然后對傳輸的數據進行加密. B點獲取加密數據后在采用私鑰進行解密.這樣一來采用公鑰加密數據只有采用私鑰才能解密成功.那么只需確保私鑰的安全不被泄露.同時即使知道了算法和若干密文不足以確定密鑰的情況.整個通信過程都是安全的.
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結構包含的參數屬性:
其實如上可以發現這些參數正式生成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?