前言牢騷
我是要在uwp下使用加密演算法並傳送到server去,由Server進行解密。於是在google中下關鍵字”.net rsa encrypt”,所以就找到了最舊也最多人討論的RSACryptoServiceProvider。我使用Visual studio 2015開command line,想在弄到uwp前先行測試,結果發現了許多與伺服器(python)的解密不相容之處,不管是金鑰格式,還是填補方法。等到把這些問題都搞定了,又發現不能在UWP中使用,真是想死的心都有了…。再次google了”uwp encrpyt”後找到了CryptographicEngine + AsymmetricKeyAlgorithmProvider這組library,才終於搞定加解密問題。
這篇文章會大概描述下列兩種加解密方法與金鑰格式。
- RSACryptoServiceProvider/RSACng
- CryptographicEngine + AsymmetricKeyAlgorithmProvider
.net RSA API概觀
在.net中RSA的加解密有三組。分別是
- RSACryptoServiceProvider
- RSACng
- CryptographicEngine + AsymmetricKeyAlgorithmProvider
以格式來說,1,2是同一組介面實作,有著類似的操作方式,AsymmetricKeyAlgorithmProvider則是另外的class.
以新舊來說,RSACryptoServiceProvider是最舊的,提供的填補格式極少。RSACng是後繼更加完善的class,但是要在.net4.6後才能用,選擇版本時要注意。至於CryptographicEngine,只能在windows app(含uwp)當中使用。
RSACryptoServiceProvider與RSACng
加密
步驟如下
- 先將字串轉換成Byte (可以用ASCIIEncoding或UTF8Encdoing,記得怎麼轉回來就好)
- 初始化RSACryptoServiceProvider (可用來加解密,在此只用來加密)
- 進行加密
- 對加密結果進行Base64(理由說明如下)
- RSA對byte進行加密,結果也是byte。
- 將加密結果直接轉回string,看起來是亂碼,會涵蓋許多無法顯示的字元。
- 因此如果要進行傳輸,最好以Base64重新編碼過,出來的字串才會全部都是可顯示的文字。
- Base64介紹
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
static String Encrypt(String _content)
{
// 先轉成Byte再加密
Encoding ByteConverter = new ASCIIEncoding();
var content = ByteConverter.GetBytes(_content);
// 初始化RSACryptoServiceProvider
RSACryptoServiceProvider rsa = getRSACrypto();
// 加密
var encrypt = rsa.Encrypt(content, false);
// 轉成base64
var encryptStr = System.Convert.ToBase64String(encrypt);
return encryptStr;
}
|
在步驟2的時候,我使用了自己撰寫的getRSACrypto,透過匯入金鑰來產生實體,在下一小節會說明。
不過從來沒有產生過金鑰的人,可能會需要自己先行產生。程式碼如下。
1
2
3
4
|
// 指定金鑰長度在2048以上比較安全
RSACryptoServiceProvider rsa = RSACryptoServiceProvider (2048);
String publickey = rsa.ToXmlString(false)
String privatekey = rsa.ToXmlString(true)
|
再將這兩個key儲存起來即可。
匯入金鑰
那來看看要如何匯入金鑰,其實只要引用FromXmlString即可。
1
2
3
4
5
6
7
|
static RSACryptoServiceProvider getRSACrypto()
{
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
var pubkey = "<RSAKeyValue><Modulus>w3XOM8su/sXvgAIhwlNhpihgVIjWQUhW9zoocwCNWGI8b4YtU/qfOlX3bsWzqw6IqRF/Ncmkbs6Rr5uiDzhHwt7cz/+vgZ9Hm346OM6K2N70a7Lbzr/LlDDb4Yt/PzBEOHEffCkKnpy31KL6Xkfg2a0OkZiF8dny9PbKQaYbXQUJIhCx9YcZ4JZ5cRGMvpNKpS+Wx2q22GV5akKVZ6jVcIgotAgp+MtMJoSr8av61eX6sOl0JborsuuX8Cao278MGKCkUldU1hUDTJtH6IOQfeeJhWolEUS6KXj0FAReWR76aYXBDQ3xJAQHvB/mjMLSiB4mpfk0JETz9Bh0o9FvXw==</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
rsa.FromXmlString(pubkey);
return rsa;
}
|
特別將金鑰的地方提出來看看,是一個XML
publickey.xml
1
2
3
4
5
6
|
<RSAKeyValue>
<Modulus>
w3XOM8su/sXvgAIhwlNhpihgVIjWQUhW9zoocwCNWGI8b4YtU/qfOlX3bsWzqw6IqRF/Ncmkbs6Rr5uiDzhHwt7cz/+vgZ9Hm346OM6K2N70a7Lbzr/LlDDb4Yt/PzBEOHEffCkKnpy31KL6Xkfg2a0OkZiF8dny9PbKQaYbXQUJIhCx9YcZ4JZ5cRGMvpNKpS+Wx2q22GV5akKVZ6jVcIgotAgp+MtMJoSr8av61eX6sOl0JborsuuX8Cao278MGKCkUldU1hUDTJtH6IOQfeeJhWolEUS6KXj0FAReWR76aYXBDQ3xJAQHvB/mjMLSiB4mpfk0JETz9Bh0o9FvXw==
</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>
|
PEM to XML
上述的金鑰有一個問題,目前大多數的linux server產出的金鑰都是PEM格式,頂多是base64後的PEM格式,根本沒辦法匯入RSACryptoServiceProvider。
幸好網路上有人幫忙解決這個問題RSA Key Converter,不過美中不足的是XML to PEM似乎有BUG。以server產key來說,應該還堪用就是。
RSACng與填補
RSACng的使用方法與RSACryptoServiceProvider幾乎一模一樣,只在加密的時候,可以選擇更多種類的填補。
關於填補的種類可以參閱RSAEncryptionPadding此一class。注意,只有.net4.6以上才支援此class。
RSACryptoServiceProvider的Encrypt只有
RASCng除了上述兩種外還有
- OaepSHA256
- OaepSHA384
- OaepSHA512
CryptographicEngine + AsymmetricKeyAlgorithmProvider
CryptographicEngine和AsymmetricKeyAlgorithmProvider是用在Windows app(UWP)上的Library,在一般的.net中似乎無法使用。
加密
大致流程如下
- 先將字串轉換成IBuffer,可藉由先轉成Byte,再使用AsBuffer來轉換
- 初始化CryptographicKey,此key包含指定的加密之演算法與金鑰
- 利用CryptographicEngine進行加密,需匯入步驟2的key與想加密的內容
- 對加密結果進行Base64(理由剛剛已經說明過了)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public String Encrypt(string _content)
{
// 將字串轉換成IBuffer
Encoding ByteConverter = new ASCIIEncoding();
byte[] contentByte = ByteConverter.GetBytes(_content);
IBuffer content = contentByte.AsBuffer();
CryptographicKey key = getCryptographicKey();
// 加密
IBuffer encrypt = CryptographicEngine.Encrypt(key, content, null);
// 轉換成Base64
String encryptStr = CryptographicBuffer.EncodeToBase64String(encrypt);
return encryptStr;
}
|
在步驟2的時候,我使用了自己撰寫的getCryptographicKey,透過選擇演算法和匯入金鑰來產生實體,在下一小節會說明。
CryptographicKey的初始化
- 使用AsymmetricKeyAlgorithmProvider.OpenAlgorithm選擇加密演算法,返回一個實體
- 使用此實體指定金鑰(ImportKeyPair / ImportPublicKey),會產生一個CryptographicKey實體
- 值得注意的是,這次的金鑰不再是XML格式,而是大部分通用之pem格式
- ImportPublicKey這個method似乎還可以讀取更多的格式,詳請請參閱MSDN
1
2
3
4
5
6
7
|
public Encrypter getCryptographicKey()
{
String publickey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw3XOM8su/sXvgAIhwlNhpihgVIjWQUhW9zoocwCNWGI8b4YtU/qfOlX3bsWzqw6IqRF/Ncmkbs6Rr5uiDzhHwt7cz/+vgZ9Hm346OM6K2N70a7Lbzr/LlDDb4Yt/PzBEOHEffCkKnpy31KL6Xkfg2a0OkZiF8dny9PbKQaYbXQUJIhCx9YcZ4JZ5cRGMvpNKpS+Wx2q22GV5akKVZ6jVcIgotAgp+MtMJoSr8av61eX6sOl0JborsuuX8Cao278MGKCkUldU1hUDTJtH6IOQfeeJhWolEUS6KXj0FAReWR76aYXBDQ3xJAQHvB/mjMLSiB4mpfk0JETz9Bh0o9FvXwIDAQAB";
AsymmetricKeyAlgorithmProvider asymmAlg = AsymmetricKeyAlgorithmProvider.OpenAlgorithm(AsymmetricAlgorithmNames.RsaPkcs1);
CryptographicKey key = asymmAlg.ImportPublicKey(CryptographicBuffer.DecodeFromBase64String(publickey), CryptographicPublicKeyBlobType.X509SubjectPublicKeyInfo);
return key;
}
|
單獨把pulbic key拿出來看,是一個pem格式的金鑰。
publickey
1
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw3XOM8su/sXvgAIhwlNhpihgVIjWQUhW9zoocwCNWGI8b4YtU/qfOlX3bsWzqw6IqRF/Ncmkbs6Rr5uiDzhHwt7cz/+vgZ9Hm346OM6K2N70a7Lbzr/LlDDb4Yt/PzBEOHEffCkKnpy31KL6Xkfg2a0OkZiF8dny9PbKQaYbXQUJIhCx9YcZ4JZ5cRGMvpNKpS+Wx2q22GV5akKVZ6jVcIgotAgp+MtMJoSr8av61eX6sOl0JborsuuX8Cao278MGKCkUldU1hUDTJtH6IOQfeeJhWolEUS6KXj0FAReWR76aYXBDQ3xJAQHvB/mjMLSiB4mpfk0JETz9Bh0o9FvXwIDAQAB
|
與python共舞
如果安裝pycrypto並使用
1
2
|
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
|
Crypto.Cipher裡似乎有很多不同Module,我目前測試只有PKCS1_v1_5可以和.net的三個library互通。
- RSACryptoServiceProvider:Encrypt時選Pkcs1(或false)
- RSACng:Encrypt時選Pkcs1
- AsymmetricKeyAlgorithmProvider:OpenAlgorithm時選RsaPkcs1
如果有其他種類可以互通請務必留言給我知道XD