[C#.NET][XML] 忽略XML宣告及XML命名空間
最近在使用非對稱加密RSACryptoServiceProvider 時,發現RSACryptoServiceProvider類別所產生的Xml字串沒有宣告,也沒有命名空間,要用序列化保存這些資訊時,必需要要動手處理掉它們,否則下次載入給RSACryptoServiceProvider用時會出錯
RSACryptoServiceProvider provider = new RSACryptoServiceProvider();
string publicKey = provider.ToXmlString(false);
string privateKey = provider.ToXmlString(true);
publicKey
<RSAKeyValue> <Modulus>8uifIYGkqxnHpnvY3ltjFp/uNVgtFZ6abv3F2BZIseInWKhYBD9PiIialKypT+HYyb44uWBS0QuVZAKu4eeWHjW3OmLvvfswj3l/xq3qMrWqNWpZfsFPXf9/E3N9GOrpoktW1E73fYakrBngyAj8aaJxi6p00G4Uztf9XDijMUc=</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>
privateKey
<RSAKeyValue>
<Modulus>8uifIYGkqxnHpnvY3ltjFp/uNVgtFZ6abv3F2BZIseInWKhYBD9PiIialKypT+HYyb44uWBS0QuVZAKu4eeWHjW3OmLvvfswj3l/xq3qMrWqNWpZfsFPXf9/E3N9GOrpoktW1E73fYakrBngyAj8aaJxi6p00G4Uztf9XDijMUc=</Modulus>
<Exponent>AQAB</Exponent>
<P>/jBebYAlBZihNwl8Mj1gkIawGerXWbokEUYmWUZ/Hl93rUfiZeBBFwt0tmx3VvjrWTOkJBCvHIf3tdsKEZbUiQ==</P>
<Q>9KOtj1eSpOvB06ULUZy/ncX2VyYJW+cN47hpmcGu4X5Y9bnIKkGS7PRkYhAqZFEXtIsMh15kfPpHxb6GH0oDTw==</Q>
<DP>OX3pLa0pMn3WIOOlUputRqMgG4yRTrsaQ0nxjIm0YMNJB0lV/KLfNf4iVMxtpZ9BY/iZLIsVgEeEkH5NZbMOuQ==</DP>
<DQ>CYPYO0wHqxx0VHwF3a3AEi3h7+/Ny2JIOwQwL0fGOoUEhsIsE+CrC0ZSJTJFw9MXnfEOkrFMLUQ6yGkppEvnpw==</DQ>
<InverseQ>sF086K3Tk1ypJ7Inz+OHHrAbVi3i7AvYrGBAmS1HQbBf2vi9YlD0of7osYesOPsTHeNjsvz+3Bx7LdNk54hMpg==</InverseQ>
<D>cvsmCEhD2DIVzXqmR2re1qDRszKP9MHkvFEny4eQ1ZMFqPPW5fvJ/Akdku7AHm37nlOKqkUsLoPbLUIP4iMrbSlEPHxbNvIM7eNu7QVOIlctNDZP53AD/BjkiFrP/TtRlualmWMZ5GlQ+AiajX8NyXztxLge0SmWToIfQz8RAiE=</D>
</RSAKeyValue>
Xml我還是相當習慣用序列化來處理,老樣子,先建立要序列化的Entity類別
[Serializable, XmlRoot("RSAKeyValue")]
public class PrivateKeyEntity
{
[XmlElement("Modulus")]
public string Modulus { get; set; }
[XmlElement("Exponent")]
public string Exponent { get; set; }
[XmlElement("P")]
public string P { get; set; }
[XmlElement("Q")]
public string Q { get; set; }
[XmlElement("DP")]
public string DP { get; set; }
[XmlElement("DQ")]
public string DQ { get; set; }
[XmlElement("InverseQ")]
public string InverseQ { get; set; }
[XmlElement("D")]
public string D { get; set; }
}
[Serializable, XmlRoot("RSAKeyValue")]
public class PublicKeyEntity
{
[XmlElement("Modulus")]
public string Modulus { get; set; }
[XmlElement("Exponent")]
public string Exponent { get; set; }
}
因為這次所取得的資料是Xml 字串,反序列化時使用MemoryStream 類別處理,
public static T DeserializeFromXml<T>(string XmlString)
{
Encoding encode = Encoding.UTF8;
using (MemoryStream ms = new MemoryStream(encode.GetBytes(XmlString)))
{
XmlSerializer xs = new XmlSerializer(typeof(T));
object obj = xs.Deserialize(ms);
if (obj == null)
return default(T);
else
return (T)obj;
}
}
序列化時要清掉Xml宣告跟命名空間,這跟上篇的實作方式不一樣 [C#.NET] 利用 泛型方法 重構 反序列化
public static void SerializeToXml(string FileName, object Entity)
{
Encoding encode = Encoding.UTF8;
//清掉命名空間
XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
namespaces.Add("", "");
using (FileStream fileStream = new FileStream(FileName, FileMode.Create, FileAccess.Write, FileShare.Read))
using (StreamWriter streamWriter = new StreamWriter(fileStream, encode))
{
//忽略Xml宣告
XmlWriterSettings xmlSetting = new XmlWriterSettings();
xmlSetting.OmitXmlDeclaration = true;
XmlWriter xmlWriter = XmlWriter.Create(streamWriter, xmlSetting);
XmlSerializer xml = new XmlSerializer(Entity.GetType());
xml.Serialize(xmlWriter, Entity, namespaces);
}
}
單元測試裡的呼叫
[TestMethod()]
public void WriteFileTest()
{
XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
namespaces.Add("", "");
RSACryptoServiceProvider provider = new RSACryptoServiceProvider(1024);
string publicKey = provider.ToXmlString(false);
string privateKey = provider.ToXmlString(true);
PrivateKeyEntity privateEntity = DeserializeFromXml<PrivateKeyEntity>(privateKey);
PublicKeyEntity publicEntity = DeserializeFromXml<PublicKeyEntity>(publicKey);
SerializeToXml(@"C:\privateKey.key", privateEntity);
SerializeToXml(@"C:\publicKey.key", publicEntity);
}
測試存放在電腦裡的檔案能不能給 RSACryptoServiceProvider 用
[TestMethod()]
public void ReadFileTest()
{
string publicKey = "";
string privateKey = "";
string source = "我是章魚";
using (StreamReader reader = new StreamReader(@"C:\publicKey.key", Encoding.UTF8))
{
publicKey = reader.ReadToEnd();
}
using (StreamReader reader = new StreamReader(@"C:\privateKey.key", Encoding.UTF8))
{
privateKey = reader.ReadToEnd();
}
RSACryptoServiceProvider provider = new RSACryptoServiceProvider();
//加密用公開金鑰
provider.FromXmlString(publicKey);
byte[] encryptBytes = provider.Encrypt(Encoding.UTF8.GetBytes(source), false);
string encryptString = BitConverter.ToString(encryptBytes).Replace("-", "");
//解密用私鑰
byte[] decryptBytes = new byte[encryptString.Length / 2];
int j = 0;
for (int i = 0; i < encryptString.Length / 2; i++)
{
decryptBytes[i] = Byte.Parse(encryptString[j].ToString() + encryptString[j + 1].ToString(), System.Globalization.NumberStyles.HexNumber);
j += 2;
}
provider.FromXmlString(privateKey);
string decryptString = Encoding.UTF8.GetString(provider.Decrypt(decryptBytes, false));
Assert.AreEqual(source, decryptString);
}
套句91哥的話,看著自己寫的程式,通過測試的程式真爽
後記:
加密後的字串 encryptString 每次都會不一樣,想要比對加密後的資料都會失敗,我在這裡卡到點時間。
若是要完整的處理必需要再把 encryptString 存起來,然後丟給解密方法,再這裡就不多說了,下次再詳細分享非對稱加密的用法。
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET