在现代软件开发中,不同平台的应用之间需要安全地交换消息。为了实现这一点,采用了消息队列服务,它允许设计应用程序时,消息的发送者和接收者不需要同时与消息队列交互。消息被放置在队列上后会被存储,直到接收者检索它们。
为了熟悉消息队列,使用了两种技术:Microsoft Azure IoT和RabbitMQ。本文将重点介绍通过这些队列提交和检索的实际消息。
希望通过这些队列提交对象,因为这将提供在共享不同类型的消息和答案(如执行命令、发送答案、更新配置等)方面的灵活性。通常,消息以字符串的形式使用,因此需要将对象转换为字符串(有时也称为序列化)。
在开发使用OData和Entity Framework的应用程序时,注意到其中一个使用的库是Newtonsoft.json,这是一个流行的高性能.NET JSON框架。在浏览了他们的文档和API之后,决定尝试使用它。
在Visual Studio 2015中通过Nuget包管理器安装了该包,并准备在代码中使用它。使用了一个非常简单的基类,包含一个Token(int)和Data(string)。示例代码如下:
public class InfoBlock
{
public int Token { get; set; }
public string Data { get; set; }
}
现在,可以这样使用Newtonsoft.json序列化它:
string myString = JsonConvert.SerializeObject(anInfoBlock);
为了在字符串中包含序列化对象的“Infoblock”标识,在生产代码中使用了以下方法:
JsonSerializerSettings theJsonSerializerSettings = new JsonSerializerSettings();
theJsonSerializerSettings.TypeNameHandling = TypeNameHandling.None;
myString = JsonConvert.SerializeObject(anInfoBlock, theJsonSerializerSettings);
要反序列化,可以将其强制转换为Infoblock类。
InfoBlock myInfoBlock = JsonConvert.DeserializeObject<InfoBlock>(myString, theJsonSerializerSettings);
当使用Microsoft Message Analyzer嗅探发送的流量时,注意到所有内容都很容易阅读,因此决定通过加密使其变得更难一些。选择了AES作为加密算法,这是一个基于Rijndael密码的对称密钥算法,由两位比利时密码学家Joan Daemen和Vincent Rijmen开发,他们向NIST提交了AES选择过程的提案。
AES加密使用相同的密钥进行加密和解密。使用的密钥大小为256位,块大小为128位。初始化向量是一个128位的随机起始值。为了能够成功解密字符串,需要知道密钥和初始化向量。密钥嵌入在代码中,这是可以的。初始化向量是随机的,因此为了共享它,将其嵌入到加密的字符串中。更喜欢将其存储在加密字符串的某个随机位置,以感觉更安全。
为了使描述更困难,必须确保每次加密相同的字符串时,加密版本都是不同的。将在测试单元中使用适当的断言来测试这一点。
在.NET中,可以使用一个加密框架。不幸的是,较新的WinRT使用不同的类来完成这项工作,但通过使用条件编译,可以实现这一点。
#if WINDOWS_UWP
using Windows.Security.Cryptography;
using Windows.Security.Cryptography.Core;
using Windows.Storage.Streams;
#else
using System.Security.Cryptography;
#endif
在WinRT(UAP)中,使用:
myCryptoAlgorithm = SymmetricKeyAlgorithmProvider.OpenAlgorithm("AES_CBC_PKCS7");
在传统的Windows应用程序中,这是:
myCryptoAlgorithm = new AesCryptoServiceProvider();
myCryptoAlgorithm.Mode = CipherMode.CBC;
myCryptoAlgorithm.Padding = PaddingMode.PKCS7;
myCryptoAlgorithm.KeySize = 256;
myCryptoAlgorithm.BlockSize = 128;
Cipher Block Chaining(CBC)模式引入了反馈。在加密每个明文块之前,它通过位异或操作与前一个块的密文结合。这确保了即使明文包含许多相同的块,它们也会被加密成不同的密文块。初始化向量通过位异或操作与第一个明文块结合,然后才加密该块。如果密文块的单个位被篡改,相应的明文块也会被篡改。此外,如果原始被篡改的位的位置在随后的块中,该位也会被篡改。
PKCS7是一种向文本添加字节的方法,以便完整的缓冲区被填充。PKCS #7填充字符串由一系列字节组成,每个字节都等于添加的填充字节总数。大多数明文消息不包含完全填充块的字节数。通常,没有足够的字节来填充最后一个块。当这种情况发生时,会向文本添加填充字符串。例如,如果块长度为64位,数据长度为9,填充八位等于7,数据等于FF FF FF FF FF FF FF FF FF,则使用PKCS7填充后,它变为:FF FF FF FF FF FF FF FF FF 07 07 07 07 07 07 07。
字符串加密器类的示例代码如下:
public class StringEncryptor
{
// Encryption and Decryption methods...
}
现在,有一个类来加密和解密字符串,创建了一个帮助类来序列化和加密InfoBlock,并执行反向操作(解密字符串并将其反序列化回Infoblock)。
public class InfoBlockConverter
{
// Serialize and Encrypt methods...
// Decrypt and Deserialize methods...
}
这是在测试单元中的使用方式:
[TestMethod]
public void EncryptDecryptInfoBlock()
{
// Arrange
InfoBlock InfoBlock01 = new InfoBlock();
// Act
InfoBlock01.Token = InfoBlock.SOMETHINGELSE;
InfoBlock01.Data = "Testing 123: Add some special characters &é@#’öçà!£$<ù}";
var encryptedString1 = InfoBlockConverter.EncodeToString(InfoBlock01);
var encryptedString2 = InfoBlockConverter.EncodeToString(InfoBlock01);
var InfoBlock02 = InfoBlockConverter.DecodeFromString(encryptedString2);
// Assert
Assert.AreNotEqual(encryptedString1, encryptedString2,
"Infoblock should never be encrypted the same twice");
Assert.AreEqual(InfoBlock01.Token, InfoBlock02.Token,
"Tokens should match original value");
Assert.AreEqual(InfoBlock01.Data, InfoBlock02.Data,
"Data should match original value");
}