问题描述
我有一个使用 DES 加密的 C# 加密方法。我需要在 node.js api 中解密该值,我正在创建。我已经设法在 api 中重新创建了大部分解密方法,但是当我传入要解密的密钥和值时,我得到了不同的结果。
加密.cs
public static string Encrypt(string toEncrypt,string key)
{
var des = new DESCryptoServiceProvider();
var ms = new MemoryStream();
des.Key = HashKey(key,des.KeySize / 8);
des.IV = HashKey(key,des.KeySize / 8);
string s = Encoding.UTF8.GetString (des.Key);
des.IV = Encoding.UTF8.GetBytes (key);
byte[] inputBytes = Encoding.UTF8.GetBytes(toEncrypt);
var cs = new CryptoStream(ms,des.CreateEncryptor(),CryptoStreamMode.Write);
cs.Write(inputBytes,inputBytes.Length);
cs.FlushFinalBlock();
return HttpServerUtility.UrlTokenEncode(ms.ToArray());
}
public static string Decrypt(string toDecrypt,des.KeySize / 8);
byte[] inputBytes = HttpServerUtility.UrlTokenDecode(toDecrypt);
var cs = new CryptoStream(ms,des.CreateDecryptor(),inputBytes.Length);
cs.FlushFinalBlock();
var encoding = Encoding.UTF8;
return encoding.GetString(ms.ToArray());
}
public static byte[] HashKey(string key,int length)
{
var sha = new SHA1CryptoServiceProvider();
byte[] keyBytes = Encoding.UTF8.GetBytes(key);
byte[] hash = sha.ComputeHash(keyBytes);
byte[] truncateHash = new byte[length];
Array.copy(hash,truncateHash,length);
return truncateHash;
}
这是我继承的代码,到目前为止我已经设法重新创建:
app.js
var keyHex = 'Secret'
var ciphertext = 'EncryptedValue'
// Decrypt
var keyBytes = CryptoJS.enc.Utf8.parse(keyHex)
var sh1keyval = CryptoJS.SHA1(keyBytes)
var trunc = convertWordArrayToUint8Array(sh1keyval).slice(0,8)
var decoded = decodeURI(ciphertext)
var key = trunc.toString(CryptoJS.enc.Utf8)
var bytes = CryptoJS.DES.decrypt(decoded,key,{ iv: key });
var originalText = bytes.toString(CryptoJS.enc.Utf8);
console.log('Message: ',originalText);
秘密的散列过程是我能够重新创建的,并且我已经确认 api 中的值 trunc
与 HashKey
方法输出的字节数组相同。
但是,当我使用 var bytes = CryptoJS.DES.decrypt(decoded,{ iv: key });
进行简单加密时,它提供了与 C# 方法不同的加密值,我认为这就是解密失败的原因。
我发现了一些东西,但我不确定如何解决,当我传递密钥的值和解密的值时,它们需要是字符串,但是在 C# 版本中,CryptoStream 采用字节数组,所以我是必须做的是将值作为字符串传递给解密,我不确定这是否有效。
密钥也是如此,DESCryptoServiceProvider
接受密钥和 iv 作为字节数组,但是当我转换加密 js 截断数组时,它只是转换字节数组的文字文本。
我目前正在尝试使用以下方法进行转换:
var key = trunc.toString(CryptoJS.enc.Utf8)
我是否遗漏了流程中的某个步骤,是否遗漏了什么?
解决方法
在C#代码的加解密部分,IV是用des.IV = HashKey(key,des.KeySize / 8)
确定的。在加密部分,此值稍后会被 des.IV = Encoding.UTF8.GetBytes(key)
覆盖。
因此,加密和解密使用不同的 IV,这在 CBC 模式的上下文中会在解密后产生损坏的明文开头。除此之外,key会抛出异常,因此IV长度不等于8字节。
据推测,IV 的覆盖是复制/粘贴错误。因此,下面的行 des.IV = Encoding.UTF8.GetBytes(key)
将被忽略。
除此问题外,这两个代码的不同之处在于:
-
在 C# 代码中,
HttpServerUtility.UrlTokenEncode()
执行 Base64url 编码,附加 Base64 填充字节 0、1 或 2 的数量,而不是通常的填充字节 (=
) .这不能被 JavaScript 代码中使用的decodeURI()
解码,因为decodeURI()
解码 URL encoding。此外,由于 CryptoJS 可以处理 Base64 编码,因此转换为 Base64 比 Base64url 解码更有效。可以转换为 Base64,例如与:function toBase64(b64url){ var b64withoutPadding = b64url.substring(0,b64url.length - 1).replace(/-/g,'+').replace(/_/g,'/') var numberPaddingBytes = parseInt(b64url.substring(b64url.length - 1)) var b64 = b64withoutPadding.padEnd(b64withoutPadding.length + numberPaddingBytes,'='); return b64 }
-
在 C# 代码中,SHA1 哈希的前 8 个字节用作 DES 密钥。 CryptoJS 代码需要 DES 密钥作为
WordArray
,这在 CryptoJS 代码中实现不正确。一个可能的实现是:var keyDES = CryptoJS.lib.WordArray.create(sh1KeyVal.words.slice(0,8 / 4));
通过这些更改,可以使用 CryptoJS 代码进行解密。在下面的例子中,密文是用 C# 代码生成的:
var key = 'my passphrase'
var ciphertextB64url = 'jUtdTa7mUnBrL1yW5uA85GrD2mwUFLOzzsiZH0chPWo1'
var ciphertextB64 = toBase64(ciphertextB64url);
var keyUtf8 = CryptoJS.enc.Utf8.parse(key)
var sha1KeyVal = CryptoJS.SHA1(keyUtf8)
var keyDES = CryptoJS.lib.WordArray.create(sha1KeyVal.words.slice(0,8 / 4));
var bytes = CryptoJS.DES.decrypt(ciphertextB64,keyDES,{ iv: keyDES });
var originalText = bytes.toString(CryptoJS.enc.Utf8);
console.log('Message: ',originalText); // Message: The quick brown fox jumps...
function toBase64(b64url){
var b64withoutPadding = b64url.substring(0,'=');
return b64
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
请注意,这两个代码都有严重的漏洞(也是注释):
- DES 是一种不安全的算法。更好地使用例如AES。
- SHA1 也不安全。最好选择例如SHA256
- 从摘要中获取密钥是不安全的。最好使用可靠的密钥派生函数,例如 Argon2 或 PBKDF2。
- 使用密钥作为 IV 通常是不安全的。正确的方法是为每个加密生成一个随机 IV。非秘密 IV 与密文一起传递(通常是串联的)。