简体   繁体   中英

crypto-js DES Decryption from C#

I have a C# encryption method that uses DES encryption. I need to decrypt that value in a node.js api, I am creating. I have managed to recreate most of the decryption method in the api, but when I pass in the secret and the value to decrypt, I get a different result.

Encryption.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;
}

This is the code I have inherited, and so far I have managed to recreate this:

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);

The hashing process of the secret is something I have been able to recreate and I have confirmed that the value trunc in the api is the same byte array that the HashKey method outputs.

However the when I do a simple encryption using var bytes = CryptoJS.DES.decrypt(decoded, key, { iv: key });it gives a different encrypted value than the C# method, which I think is why the decryption fails.

Something I have found, but am unsure how to address is that when I pass the value of the key and the value to decrypt they need to be strings, however in the C# version the CryptoStream takes a bytearray, so what I am having to do is pass the value to decrypt as a string and I am not sure if this is having an effect. The same goes for the key, the DESCryptoServiceProvider accepts the key and iv as byte arrays, however when I convert the crypto-js truncated array it simply converts the literal text of the byte array. I am currently trying that conversion using the following:

var key = trunc.toString(CryptoJS.enc.Utf8)

Am I missing a step in the process, have I missed something?

In the encryption and decryption part of the C# code, the IV is determined with des.IV = HashKey(key, des.KeySize / 8) . In the encryption part this value is later overwritten with des.IV = Encoding.UTF8.GetBytes(key) .
As a result, encryption and decryption use different IVs, which in the context of CBC mode produces a corrupted beginning of the plaintext after decryption. Apart from that, an exception is thrown for key and thus IV lengths not equal to 8 bytes.
Presumably the overwriting of the IV is a copy/paste error. Thus the line des.IV = Encoding.UTF8.GetBytes(key) is ignored in the following.

Apart from this problem, the two codes differ in the following respects:

  • In the C# code, HttpServerUtility.UrlTokenEncode() performs a Base64url encoding, appending the number of Base64 padding bytes 0, 1 or 2 instead of the usual padding bytes ( = ). This cannot be decoded by decodeURI() used in the JavaScript code, since decodeURI() decodes a URL encoding . Furthermore, since CryptoJS can handle a Base64 encoding, a conversion to Base64 is more efficient than a Base64url decoding. A conversion to Base64 is possible eg with:

     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 }
  • In the C# code, the first 8 bytes of the SHA1 hash are used as the DES key. The CryptoJS code needs the DES key as a WordArray , which is implemented incorrectly in the CryptoJS code. A possible implementation is:

     var keyDES = CryptoJS.lib.WordArray.create(sh1KeyVal.words.slice(0, 8 / 4));

With these changes, decryption is possible with the CryptoJS code. In the following example the ciphertext was generated with the C# code:

 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>

Note that both codes have serious vulnerabilities (s. also the comment):

  • DES is an insecure algorithm. Better use eg AES.
  • SHA1 is also insecure. It is better to choose eg SHA256
  • Deriving the key from a digest is insecure. It is better to use a reliable key derivation function like Argon2 or PBKDF2.
  • Using the key as an IV is generally insecure. The correct way is to generate a random IV for each encryption. The non-secret IV is passed along with the ciphertext (typically concatenated).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM