前言牢騷

我是要在uwp下使用加密演算法並傳送到server去,由Server進行解密。於是在google中下關鍵字”.net rsa encrypt”,所以就找到了最舊也最多人討論的RSACryptoServiceProvider。我使用Visual studio 2015開command line,想在弄到uwp前先行測試,結果發現了許多與伺服器(python)的解密不相容之處,不管是金鑰格式,還是填補方法。等到把這些問題都搞定了,又發現不能在UWP中使用,真是想死的心都有了…。再次google了”uwp encrpyt”後找到了CryptographicEngine + AsymmetricKeyAlgorithmProvider這組library,才終於搞定加解密問題。

這篇文章會大概描述下列兩種加解密方法與金鑰格式。

  1. RSACryptoServiceProvider/RSACng
  2. CryptographicEngine + AsymmetricKeyAlgorithmProvider

.net RSA API概觀

在.net中RSA的加解密有三組。分別是

  1. RSACryptoServiceProvider
  2. RSACng
  3. CryptographicEngine + AsymmetricKeyAlgorithmProvider

以格式來說,1,2是同一組介面實作,有著類似的操作方式,AsymmetricKeyAlgorithmProvider則是另外的class.
以新舊來說,RSACryptoServiceProvider是最舊的,提供的填補格式極少。RSACng是後繼更加完善的class,但是要在.net4.6後才能用,選擇版本時要注意。至於CryptographicEngine,只能在windows app(含uwp)當中使用。

RSACryptoServiceProvider與RSACng

加密

步驟如下

  1. 先將字串轉換成Byte (可以用ASCIIEncoding或UTF8Encdoing,記得怎麼轉回來就好)
  2. 初始化RSACryptoServiceProvider (可用來加解密,在此只用來加密)
  3. 進行加密
  4. 對加密結果進行Base64(理由說明如下)
    • RSA對byte進行加密,結果也是byte。
    • 將加密結果直接轉回string,看起來是亂碼,會涵蓋許多無法顯示的字元。
    • 因此如果要進行傳輸,最好以Base64重新編碼過,出來的字串才會全部都是可顯示的文字。
    • Base64介紹
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,透過匯入金鑰來產生實體,在下一小節會說明。
不過從來沒有產生過金鑰的人,可能會需要自己先行產生。程式碼如下。

// 指定金鑰長度在2048以上比較安全
RSACryptoServiceProvider rsa = RSACryptoServiceProvider (2048);
String publickey = rsa.ToXmlString(false)
String privatekey = rsa.ToXmlString(true)

再將這兩個key儲存起來即可。

匯入金鑰

那來看看要如何匯入金鑰,其實只要引用FromXmlString即可。

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

<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只有

  • OaepSHA1
  • Pkcs1

RASCng除了上述兩種外還有

  • OaepSHA256
  • OaepSHA384
  • OaepSHA512

CryptographicEngine + AsymmetricKeyAlgorithmProvider

CryptographicEngine和AsymmetricKeyAlgorithmProvider是用在Windows app(UWP)上的Library,在一般的.net中似乎無法使用

加密

大致流程如下

  1. 先將字串轉換成IBuffer,可藉由先轉成Byte,再使用AsBuffer來轉換
  2. 初始化CryptographicKey,此key包含指定的加密之演算法與金鑰
  3. 利用CryptographicEngine進行加密,需匯入步驟2的key與想加密的內容
  4. 對加密結果進行Base64(理由剛剛已經說明過了)
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的初始化

  1. 使用AsymmetricKeyAlgorithmProvider.OpenAlgorithm選擇加密演算法,返回一個實體
    • 演算法種類可以上MSDN查詢
  2. 使用此實體指定金鑰(ImportKeyPair / ImportPublicKey),會產生一個CryptographicKey實體
    • 值得注意的是,這次的金鑰不再是XML格式,而是大部分通用之pem格式
    • ImportPublicKey這個method似乎還可以讀取更多的格式,詳請請參閱MSDN
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

MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw3XOM8su/sXvgAIhwlNhpihgVIjWQUhW9zoocwCNWGI8b4YtU/qfOlX3bsWzqw6IqRF/Ncmkbs6Rr5uiDzhHwt7cz/+vgZ9Hm346OM6K2N70a7Lbzr/LlDDb4Yt/PzBEOHEffCkKnpy31KL6Xkfg2a0OkZiF8dny9PbKQaYbXQUJIhCx9YcZ4JZ5cRGMvpNKpS+Wx2q22GV5akKVZ6jVcIgotAgp+MtMJoSr8av61eX6sOl0JborsuuX8Cao278MGKCkUldU1hUDTJtH6IOQfeeJhWolEUS6KXj0FAReWR76aYXBDQ3xJAQHvB/mjMLSiB4mpfk0JETz9Bh0o9FvXwIDAQAB

與python共舞

如果安裝pycrypto並使用

from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5

Crypto.Cipher裡似乎有很多不同Module,我目前測試只有PKCS1_v1_5可以和.net的三個library互通。

  1. RSACryptoServiceProvider:Encrypt時選Pkcs1(或false)
  2. RSACng:Encrypt時選Pkcs1
  3. AsymmetricKeyAlgorithmProvider:OpenAlgorithm時選RsaPkcs1

如果有其他種類可以互通請務必留言給我知道XD