我有一个使用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, 0, inputBytes.Length);
cs.FlushFinalBlock();
return HttpServerUtility.UrlTokenEncode(ms.ToArray());
}
public static string Decrypt(string toDecrypt, string key)
{
var des = new DESCryptoServiceProvider();
var ms = new MemoryStream();
des.Key = HashKey(key, des.KeySize / 8);
des.IV = HashKey(key, des.KeySize / 8);
byte[] inputBytes = HttpServerUtility.UrlTokenDecode(toDecrypt);
var cs = new CryptoStream(ms, des.CreateDecryptor(), CryptoStreamMode.Write);
cs.Write(inputBytes, 0, 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, 0, truncateHash, 0, 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, key, { iv: key });
进行简单加密时,它会给出与C#方法不同的加密值,我认为这就是解密失败的原因。
我发现,但不确定如何解决的问题是,当我传递密钥的值和要解密的值时,它们需要是字符串,然而在C#版本中,CryptoStream采用字节数组,所以我必须做的是将要解密的数值作为字符串传递,我不确定这是否有效果。密钥也是如此,DESCryptoServiceProvider
接受密钥和iv作为字节数组,但是当我转换crypto-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模式的上下文中,这会在解密后产生损坏的明文开头。除此之外,还会为密钥抛出异常,因此IV长度不等于8字节
假设IV的覆盖是复制/粘贴错误。因此,线des.IV = Encoding.UTF8.GetBytes(key)
在下文中被忽略。
除了这个问题,这两个代码在以下方面有所不同:
-
在C#代码中,
HttpServerUtility.UrlTokenEncode()
执行Base64url编码,将Base64填充字节的数量附加为0、1或2,而不是通常的填充字节(=
(。这不能由JavaScript代码中使用的decodeURI()
解码,因为decodeURI()
解码URL编码。此外,由于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, b64url.length - 1).replace(/-/g, '+').replace(/_/g, '/')
var numberPaddingBytes = parseInt(b64url.substring(b64url.length - 1))
var b64 = b64withoutPadding.padEnd(b64withoutPadding.length + numberPaddingBytes, '=');
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与密文一起传递(通常是级联的(