我需要使用AES加密对字符串进行加密。这种加密是在C#之前进行的,但是需要将其转换为JavaScript(将在浏览器上运行)。
C#中用于加密的当前代码如下-
public static string EncryptString(string plainText, string encryptionKey)
{
byte[] clearBytes = Encoding.Unicode.GetBytes(plainText);
using (Aes encryptor = Aes.Create())
{
Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(encryptionKey, new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 });
encryptor.Key = pdb.GetBytes(32);
encryptor.IV = pdb.GetBytes(16);
using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateEncryptor(), CryptoStreamMode.Write))
{
cs.Write(clearBytes, 0, clearBytes.Length);
cs.Close();
}
plainText = Convert.ToBase64String(ms.ToArray());
}
}
return plainText;
}
我尝试使用CryptoJS复制相同的功能,但是没有给我等效的加密base64字符串。这是我的CryptoJS代码-
function encryptString(encryptString, secretKey) {
var iv = CryptoJS.enc.Hex.parse('Ivan Medvedev');
var key = CryptoJS.PBKDF2(secretKey, iv, { keySize: 256 / 32, iterations: 500 });
var encrypted = CryptoJS.AES.encrypt(encryptString, key,{iv:iv);
return encrypted;
}
必须将加密的字符串发送到能够对其解密的服务器。服务器能够解密从C#代码生成的加密字符串,但不能解密从JS代码生成的加密字符串。我试图比较两个代码生成的加密字符串,发现C#代码正在生成更长的加密字符串。例如,将“ Example String”保留为plainText,将“ Example Key”保留为键,则得到以下结果-
C# - eAQO+odxOdGlNRB81SHR2XzJhyWtz6XmQDko9HyDe0w=
JS - 9ex5i2g+8iUCwdwN92SF+A==
JS加密字符串的长度总是比C#短。我做错什么了吗?我只需要将C#代码复制到JS代码中即可。
更新:
我当前在Zergatul's answer之后的代码是-
function encryptString(encryptString, secretKey) {
var keyBytes = CryptoJS.PBKDF2(secretKey, 'Ivan Medvedev', { keySize: 48 / 4, iterations: 1000 });
console.log(keyBytes.toString());
// take first 32 bytes as key (like in C# code)
var key = new CryptoJS.lib.WordArray.init(keyBytes.words, 32);
// skip first 32 bytes and take next 16 bytes as IV
var iv = new CryptoJS.lib.WordArray.init(keyBytes.words.splice(32 / 4), 16);
console.log(key.toString());
console.log(iv.toString());
var encrypted = CryptoJS.AES.encrypt(encryptString, key, { iv: iv });
return encrypted;
}
如他/她的答案所示,如果C#代码使用ASCII而不是Unicode将plainText转换为字节,则C#和JS代码都将产生准确的结果。但是,由于我无法修改解密代码,因此必须将其转换为与使用Unicode的原始C#代码等效。
因此,我试图看一下C#中ASCII和Unicode字节转换之间的字节数组之间有什么区别。这是我发现的-
ASCII Byte Array: [69,120,97,109,112,108,101,32,83,116, 114, 105, 110, 103]
Unicode Byte Array: [69,0,120,0,97,0,109,0,112,0,108,0,101,0,32,0,83,0,116,0, 114,0, 105,0, 110,0, 103,0]
因此,C#中的每个字符都可以使用一些额外的字节(因此,Unicode为每个字符分配的字节数是ASCII的两倍)。
分别是Unicode和ASCII转换之间的区别-
ASCII
clearBytes: [69,120,97,109,112,108,101,32,83,116,114,105,110,103,]
encryptor.Key: [123,213,18,82,141,249,182,218,247,31,246,83,80,77,195,134,230,92,0,125,232,210,135,115,145,193,140,239,228,225,183,13,]
encryptor.IV: [101,74,46,177,46,233,68,252,83,169,211,13,249,61,118,167,]
Result: eQus9GLPKULh9vhRWOJjog==
Unicode:
clearBytes: [69,0,120,0,97,0,109,0,112,0,108,0,101,0,32,0,83,0,116,0,114,0,105,0,110,0,103,0,]
encryptor.Key: [123,213,18,82,141,249,182,218,247,31,246,83,80,77,195,134,230,92,0,125,232,210,135,115,145,193,140,239,228,225,183,13,]
encryptor.IV: [101,74,46,177,46,233,68,252,83,169,211,13,249,61,118,167,]
Result: eAQO+odxOdGlNRB81SHR2XzJhyWtz6XmQDko9HyDe0w=
因此,由于要生成的键和iv在Unicode和ASCII方法中都具有完全相同的字节数组,因此它应该不会生成不同的输出,但是可以这样做。我认为这是由于clearBytes的长度所致,因为它使用其长度来写入CryptoStream。
我试图查看JS代码中生成的字节的输出是什么,发现它使用需要使用toString()
方法转换为字符串的单词。
keyBytes: 7bd512528df9b6daf71ff653504dc386e65c007de8d2877391c18cefe4e1b70d654a2eb12ee944fc53a9d30df93d76a7
key: 7bd512528df9b6daf71ff653504dc386e65c007de8d2877391c18cefe4e1b70d
iv: 654a2eb12ee944fc53a9d30df93d76a7
从那时起,我无法在JS代码中影响生成的加密字符串的长度(无法直接访问写入流),因此仍然停留在这里。
答案 0 :(得分:2)
这里是如何在C#
和CryptoJS
之间重现相同密文的示例:
static void Main(string[] args)
{
byte[] plainText = Encoding.Unicode.GetBytes("Example String"); // this is UTF-16 LE
string cipherText;
using (Aes encryptor = Aes.Create())
{
var pdb = new Rfc2898DeriveBytes("Example Key", Encoding.ASCII.GetBytes("Ivan Medvedev"));
encryptor.Key = pdb.GetBytes(32);
encryptor.IV = pdb.GetBytes(16);
using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateEncryptor(), CryptoStreamMode.Write))
{
cs.Write(plainText, 0, plainText.Length);
cs.Close();
}
cipherText = Convert.ToBase64String(ms.ToArray());
}
}
Console.WriteLine(cipherText);
}
和JS:
var keyBytes = CryptoJS.PBKDF2('Example Key', 'Ivan Medvedev', { keySize: 48 / 4, iterations: 1000 });
// take first 32 bytes as key (like in C# code)
var key = new CryptoJS.lib.WordArray.init(keyBytes.words, 32);
// skip first 32 bytes and take next 16 bytes as IV
var iv = new CryptoJS.lib.WordArray.init(keyBytes.words.splice(32 / 4), 16);
// use the same encoding as in C# code, to convert string into bytes
var data = CryptoJS.enc.Utf16LE.parse("Example String");
var encrypted = CryptoJS.AES.encrypt(data, key, { iv: iv });
console.log(encrypted.toString());
两个代码都返回:eAQO+odxOdGlNRB81SHR2XzJhyWtz6XmQDko9HyDe0w=
答案 1 :(得分:2)
TL; DR 的最终代码如下所示-
function encryptString(encryptString, secretKey) {
encryptString = addExtraByteToChars(encryptString);
var keyBytes = CryptoJS.PBKDF2(secretKey, 'Ivan Medvedev', { keySize: 48 / 4, iterations: 1000 });
console.log(keyBytes.toString());
var key = new CryptoJS.lib.WordArray.init(keyBytes.words, 32);
var iv = new CryptoJS.lib.WordArray.init(keyBytes.words.splice(32 / 4), 16);
var encrypted = CryptoJS.AES.encrypt(encryptString, key, { iv: iv, });
return encrypted;
}
function addExtraByteToChars(str) {
let strResult = '';
for (var i = 0; i < str.length; ++i) {
strResult += str.charAt(i) + String.fromCharCode(0);
}
return strResult;
}
说明:
Zergatul's answer中的C#代码(感谢他/她)正在使用ASCII来将plainText转换为字节,而我的C#代码正在使用Unicode。 Unicode正在为结果字节数组中的每个字符分配额外的字节,这不会影响密钥字节和iv字节的生成,但会影响结果,因为cryptoString的长度取决于从plainText生成的字节的长度。 br /> 如下所示,分别使用“ Example String”和“ Example Key”分别作为plainText和secretKey为每个字节生成的字节-
ASCII
clearBytes: [69,120,97,109,112,108,101,32,83,116,114,105,110,103,]
encryptor.Key: [123,213,18,82,141,249,182,218,247,31,246,83,80,77,195,134,230,92,0,125,232,210,135,115,145,193,140,239,228,225,183,13,]
encryptor.IV: [101,74,46,177,46,233,68,252,83,169,211,13,249,61,118,167,]
Result: eQus9GLPKULh9vhRWOJjog==
Unicode:
clearBytes: [69,0,120,0,97,0,109,0,112,0,108,0,101,0,32,0,83,0,116,0,114,0,105,0,110,0,103,0,]
encryptor.Key: [123,213,18,82,141,249,182,218,247,31,246,83,80,77,195,134,230,92,0,125,232,210,135,115,145,193,140,239,228,225,183,13,]
encryptor.IV: [101,74,46,177,46,233,68,252,83,169,211,13,249,61,118,167,]
Result: eAQO+odxOdGlNRB81SHR2XzJhyWtz6XmQDko9HyDe0w=
JS的结果也很相似,这表明它正在使用ASCII字节转换-
keyBytes: 7bd512528df9b6daf71ff653504dc386e65c007de8d2877391c18cefe4e1b70d654a2eb12ee944fc53a9d30df93d76a7
key: 7bd512528df9b6daf71ff653504dc386e65c007de8d2877391c18cefe4e1b70d
iv: 654a2eb12ee944fc53a9d30df93d76a7
因此,我只需要增加plainText的长度以使其使用Unicode等效字节生成即可(抱歉,对该术语不熟悉)。由于Unicode为byteArray中的每个字符分配了2个空格,第二个空格保持为0,因此我基本上在plainText的字符中创建了间隙,并使用addExtraByteToChars()
函数用ASCII值为0的字符填充了该间隙。一切都变了。
肯定是一种解决方法,但已开始适用于我的情况。我想这可能对其他人有用,也可能没有用,因此分享了发现。如果有人可以建议更好地实现addExtraByteToChars()
函数(此转换可能是代替ASCII转换为Unicode的某种术语,或者是一种更好,高效且不易破解的方法),请提出建议。