I have two applications, one writen in C#, the other in PHP.
C# application encrypt messages using AES 256 CBC. Key used for encrypt is located in a byte[] property hardcoded in the class. The Initialization vector is also hardcoded and is the same through the time.
C# Application
byte[] key = {142, 237, ....};
byte[] InitilizationVector = {132, ...};
var mensajeSinEncriptar = "";
SymmetricAlgorithm algoritmo = SymmetricAlgorithm.Create("Rijndael");
algoritmo.BlockSize = 128;
algoritmo.Mode = CipherMode.CBC;
algoritmo.Padding = PaddingMode.Zeros;
algoritmo.KeySize = 256;
algoritmo.Key = key;
algoritmo.IV = InitilizationVector;
ICryptoTransform encriptador = algoritmo.CreateEncryptor();
byte[] textoPlano = Encoding.Default.GetBytes(mensajeSinEncriptar);
MemoryStream memoryStream = new MemoryStream();
CryptoStream cryptoStream = new CryptoStream(memoryStream, encriptador, CryptoStreamMode.Write);
cryptoStream.Write(textoPlano, 0, textoPlano.Length);
cryptoStream.FlushFinalBlock();
memoryStream.Close();
cryptoStream.Close();
return Convert.ToBase64String(memoryStream.ToArray());
Then, in my PHP application I want to decrypt the messages generated by c # using OpenSSL.
I use the same key and iv used in C#. I convert them to characters because the function does not accept anything other than string.
PHP
private function decrypt(string $message)
{
$stringOf = function ($bytes) {
return implode('', array_map('chr', $bytes));
};
$key = [142, 237, ...];
$iv = [132, ... ];
$result = openssl_decrypt(
base64_decode($message),
'aes-256-cbc',
$stringOf($key),
1,
$stringOf($iv)
);
if (is_bool($result) && !$result) {
return new Error('Error: ' . openssl_error_string());
}
return $result;
}
When I try to decrypt I get this error
Error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
I guess it's a mistake of mine when trying to convert the key to a string. Since I also have a function to encrypt which gives me different results using the same key and iv used in C#.
Related
I'm trying to decrypt a Rijndael-128 encrypted cipher, these are the values:
Cipher: "QfJzZ9V6Jm43jYPiVaXP9mu+f88S/JC24saHbOMxxC8="
Key: "45744855535472525844494538555934",
Mode: CBC
Result should be: "abcd#1234"
This website seems to decrypt the cipher just fine:
https://codebeautify.org/encrypt-decrypt
I'm trying to do the same thing in C# with absolutely no luck, what am I missing here?
class Program
{
static void Main(string[] args)
{
var text = Decrypt("QfJzZ9V6Jm43jYPiVaXP9mu+f88S/JC24saHbOMxxC8=", Convert.FromBase64String("45744855535472525844494538555934"));
}
public static string Decrypt(string Text, byte[] keyBytes)
{
var textBytes = Convert.FromBase64String(Text);
var rijKey = new RijndaelManaged();
rijKey.IV = textBytes.Take(rijKey.BlockSize / 8).ToArray();
rijKey.Padding = PaddingMode.None;
rijKey.Mode = CipherMode.CBC;
var decryptor = rijKey.CreateDecryptor(keyBytes, rijKey.IV);
var memoryStream = new MemoryStream(textBytes);
var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read);
var pTextBytes = new byte[textBytes.Length];
var decryptedByteCount = cryptoStream.Read(pTextBytes, 0, pTextBytes.Length);
memoryStream.Close();
cryptoStream.Close();
string plainText = Encoding.UTF8.GetString(pTextBytes, 0, decryptedByteCount);
return plainText;
}
}
The problem with your code is this line:
rijKey.GenerateIV();
You need the original IV. You can't just use a random one.
If you go to the site you linked to, each time you press encrypt with the key and text you have given, you get a different encrypted text (because a random IV is used). The web page must be prepending the random IV used to encrypt to the encrypted text (or less likely, the web page is storing it), which is why it can then decrypt the encrypted text.
[Your code also needs using statements.]
Is it possible to NOT use the IV when implementing Rijndael decryption?
How to Use Rijndael ManagedEncryption with C#
I have this code in CryptoJS, inside browser:
var decrypt = function (cipherText) {
var key = "a_long_key_goes_here";
var iv = "initial_vector_goes_here";
key = CryptoJS.enc.Hex.parse(key);
iv = CryptoJS.enc.Hex.parse(iv);
var decrypted = CryptoJS.TripleDES.decrypt({
ciphertext: CryptoJS.enc.Hex.parse(cipherText)
}, key, {
iv: iv,
mode: CryptoJS.mode.CBC
});
var clearText = decrypted.toString(CryptoJS.enc.Utf8);
return clearText;
};
This code is not written by me. Also the cipherText come from another server that I have no access to. However, I have access to key and to iv.
I can decrypt that cipherText inside a browser's console. But I want to use these keys to decrypt that cipherText inside C# code. Here's the code I've written:
public void Desrypt()
{
ICryptoTransform decryptor;
UTF8Encoding encoder;
string key = "a_long_key_goes_here";
string iv = "initial_vector_goes_here";
var cipherText = "cipher_text_goes_here";
string clearText = "";
byte[] cipherBytes = FromHexString(cipherText);
using (Aes aes = Aes.Create())
{
Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(key, new byte[] { });
aes.Key = pdb.GetBytes(32);
aes.IV = pdb.GetBytes(16);
using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Write))
{
cs.Write(cipherBytes, 0, cipherBytes.Length);
cs.Close();
}
clearText = Encoding.Unicode.GetString(ms.ToArray());
}
}
return clearText;
}
public static byte[] FromHexString(string hexString)
{
var bytes = new byte[hexString.Length / 2];
for (var i = 0; i < bytes.Length; i++)
{
bytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);
}
return bytes;
}
I have some problems though. I don't understand if I'm correctly decoding the given cipherText from hexadecimal or not. Also I can't instantiate Rfc2898DeriveBytes, because I don't know what the second parameter (salt) should be.
Also I don't know where should I use that iv I've gotten from the CryptoJS code.
Could you please help?
So that both codes are compatible, the following changes of the C# code are necessary:
The return type of the Decrypt method must be changed from void to string.
Key and IV have to be decoded hexadecimal like the ciphertext with FromHexString.
Instead of AES, TripleDES must be used.
Rfc2898DeriveBytes implements PBKDF2 and must not be applied (since the JavaScript code does not use PBKDF2 either).
The decrypted data must not be decoded with Encoding.Unicode (which corresponds to UTF16LE in .NET), but with Encoding.UTF8.
The C# code can handle 24 bytes keys (to support 3TDEA) and 16 bytes keys (to support the less secure 2TDEA). The posted CryptoJS code also handles these key sizes plus additionally 8 bytes keys (to support the least secure, DES compatible variant 1TDEA).
The following C# code decrypts a ciphertext generated with CryptoJS and 3TDEA:
public string Decrypt()
{
byte[] key = FromHexString("000102030405060708090a0b0c0d0e0f1011121314151617"); // 24 bytes (3TDEA)
byte[] iv = FromHexString("0001020304050607"); // 8 bytes
byte[] ciphertext = FromHexString("2116057c372e0e95dbe91fbfd148371b8e9974187b71e7c018de89c757280ad342d4191d29472040ee70d19015b025e1");
string plaintext = "";
using (TripleDES tdes = TripleDES.Create())
{
tdes.Key = key;
tdes.IV = iv;
using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream(ms, tdes.CreateDecryptor(tdes.Key, tdes.IV), CryptoStreamMode.Write))
{
cs.Write(ciphertext, 0, ciphertext.Length);
}
plaintext = Encoding.UTF8.GetString(ms.ToArray());
}
}
return plaintext;
}
The decryption is also possible with the posted JavaScript code, which shows the functional equivalence of both codes.
Note: Since AES is more performant than TripleDES, AES should be used if possible.
I found an implementation for AES encryption/decryption that's supposed to work in PHP and C#:
https://odan.github.io/2017/08/10/aes-256-encryption-and-decryption-in-php-and-csharp.html
However, in this example, the IV is always 0, which is not good.
So, as the author suggests, I use the openssl_random_pseudo_bytes() function:
$ivlen = openssl_cipher_iv_length($method);
$iv = openssl_random_pseudo_bytes($ivlen);
However, the problem now is, that I have to store the IV somewhere so that the C# app can use it for decryption. My idea was to base64_encode it and prepend it to the encrypted message with ":" separating both, so that I can simply split the string, then base64 decode it in C# and can use the IV. However, this does not work. This is the code:
public string DecryptString(string cipherText, byte[] key, byte[] iv)
{
// Instantiate a new Aes object to perform string symmetric encryption
Aes encryptor = Aes.Create();
encryptor.Mode = CipherMode.CBC;
// Set key and IV
byte[] aesKey = new byte[32];
Array.Copy(key, 0, aesKey, 0, 32);
encryptor.Key = aesKey;
encryptor.IV = iv;
// Instantiate a new MemoryStream object to contain the encrypted bytes
MemoryStream memoryStream = new MemoryStream();
// Instantiate a new encryptor from our Aes object
ICryptoTransform aesDecryptor = encryptor.CreateDecryptor();
// Instantiate a new CryptoStream object to process the data and write it to the
// memory stream
CryptoStream cryptoStream = new CryptoStream(memoryStream, aesDecryptor, CryptoStreamMode.Write);
// Will contain decrypted plaintext
string plainText = String.Empty;
try {
// Convert the ciphertext string into a byte array
byte[] cipherBytes = Convert.FromBase64String(cipherText);
// Decrypt the input ciphertext string
cryptoStream.Write(cipherBytes, 0, cipherBytes . Length);
// Complete the decryption process
cryptoStream.FlushFinalBlock();
// Convert the decrypted data from a MemoryStream to a byte array
byte[] plainBytes = memoryStream.ToArray();
// Convert the decrypted byte array to string
plainText = Encoding.ASCII.GetString(plainBytes, 0, plainBytes.Length);
} finally {
// Close both the MemoryStream and the CryptoStream
memoryStream.Close();
cryptoStream.Close();
}
// Return the decrypted data as a string
return plainText;
}
And this is how I try to call the function with the IV prepended to the encrypted message:
private void btnDecrypt_Click(object sender, EventArgs e)
{
string encrypted = txtEncrypted.Text;
string password = txtPW.Text;
string[] temp = encrypted.Split(":".ToCharArray());
string iv_str = temp[0];
encrypted = temp[1];
SHA256 mySHA256 = SHA256Managed.Create();
byte[] key = mySHA256.ComputeHash(Encoding.ASCII.GetBytes(password));
iv_str = Encoding.Default.GetString(Convert.FromBase64String(iv_str));
byte[] iv = Encoding.ASCII.GetBytes(iv_str);
txtDecrypted.Text = this.DecryptString(encrypted, key, iv);
}
Doesn't work. It decrypts something, but that's just gibberish and not the message I encrypted in PHP.
I think it has to do with the IV somehow.
#Topaco:
Thank you very much... now I see what my mistake was. It works now with generated IV.
By the way, two more questions on the strength of the encryption:
Is it ok to use the following function to generate an IV (GenerateIV() for some reason won't work):
byte[] iv = new byte[16];
Random rnd = new Random();
rnd.NextBytes(iv);
I added a randomly generated salt to the password of the user, so that the key is a hash from the user password and a salt. I also prepend the salt, together with the now randomly generated IV, to the message. Then for the decryption process I use the prepended salt and the key the user enters. Works so far. But does it weaken the encryption? No, right?
Thank you very much.
// Edit: Code format doesn't work, don't know why.
I am trying to replicate this C# function into a NodeJS function.
Here is the C# function first.
private byte[] Decrypt(byte[] plainText)
{
using (var aes = new AesManaged())
{
aes.Mode = CipherMode.ECB;
aes.Padding = PaddingMode.Zeros;
var decryptor = aes.CreateDecryptor(Key, Key);
var decrypted = decryptor.TransformFinalBlock(plainText, 0, plainText.Length);
return decrypted;
}
}
And here is the NodeJS function that I am trying to convert it to.
var crypto = require("crypto")
require('./config');
function decrypt(key, data) {
var decipher = crypto.createDecipher('aes-256-ecb', key);
var decrypted = decipher.update(data, "utf8", "utf8");
decipher.setAutoPadding(true);
decrypted += decipher.final('utf8');
return decrypted;
}
decryptedText = decrypt(process.env.password, process.env.encryptedMessage);
console.log("Decrypted Text: ", decryptedText);
The NodeJS function is wrong can someone convert the C# function into a NodeJS function as I have limited experience with this as I am a frontend developer.
Currently the output that I am getting is:
Error: error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length
at Error (native)
at Decipher.Cipher.final (crypto.js:158:26)
at decrypt (/Users/dave/Tests/sqs-consumer/app3.js:8:31)
I have been given a set of codes from a third party that need encrypting/decrypting however the sample encryption code they gave me was in C# and I am primarily a front-end PHP developer.
I have set-up a slimmed down working example of the code I was provided
here using the sample key of A818163DD5E0DE87.
public static byte[] HexStringToByteArray(String hex)
{
int NumberChars = hex.Length;
byte[] bytes = new byte[NumberChars / 2];
for (int i = 0; i < NumberChars; i += 2) {
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
}
return bytes;
}
// Convers a byte array to a HEX string
public static string ByteArrayToHexString(byte[] bytes)
{
StringBuilder hexString = new StringBuilder(bytes.Length * 2);
for (int i = 0; i < bytes.Length; i++)
{
hexString.Append(bytes[i].ToString("X2"));
}
return hexString.ToString();
}
public static byte[] Encrypt()
{
string plainText = "GROW06BP";
DESCryptoServiceProvider desCrypto = new DESCryptoServiceProvider();
desCrypto.Key = HexStringToByteArray("A818163DD5E0DE87");
desCrypto.IV = HexStringToByteArray("A818163DD5E0DE87");
desCrypto.Mode = CipherMode.CBC;
desCrypto.Padding = PaddingMode.Zeros;
// Create a buffer for the Plain Text using ASCIIEncoding
byte[] plaintextBytes = (new ASCIIEncoding()).GetBytes(plainText);
// Create a memory stream for the encrypted bytes
MemoryStream msEncrypt = new MemoryStream();
// Create a CryptoStream using the memory stream and the passed Algorithm
CryptoStream csEncrypt = new CryptoStream(msEncrypt, desCrypto.CreateEncryptor(), CryptoStreamMode.Write);
// Write the plaintext to the CryptoStream
csEncrypt.Write(plaintextBytes, 0, plaintextBytes.Length);
// Close the CryptoStream
csEncrypt.Close();
// Read the Encrypted bytes into our buffer
byte[] encryptedTextBytes = msEncrypt.ToArray();
// Close the Memory Stream
msEncrypt.Close();
// And return the encrypted buffer
return encryptedTextBytes;
}
I have scoured stack overflow and other sites in an attempt to replicate this in PHP but nothing comes close to the correct output. I'm also confused by which cipher I am meant to be using and how to convert the key and iv to match the C# example. Below is what I have attempted so far.
$key = unpack('H*', "A818163DD5E0DE87");
$key = "A818163DD5E0DE87";
$iv = $key;
$plaintext = "GROW06BP";
$ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $plaintext,MCRYPT_MODE_CBC, $iv);
echo base64_encode($ciphertext);
Any help would be appreciated.
Things you need to consider:
DESCryptoServiceProvider -> mcrypt_module_open('des'
desCrypto.Mode = CipherMode.CBC; -> mcrypt_module_open(...,..., 'cbc',
key,iv and the cipher output are "treated" with HexStringToByteArray(), pack('H*) can undo that
So, given the output of the .net fiddle (7860D97E56DA6A40) that leads to
<?php
$msgHex = '7860D97E56DA6A40';
$keyHex = 'A818163DD5E0DE87';
$ivHex = 'A818163DD5E0DE87'; // really? invalidates the use-case of an iv :-/
// this reverts the effect of HexStringToByteArray()
$msg = pack('H*', $msgHex);
$key = pack('H*', $keyHex);
$iv = pack('H*', $ivHex);
// add error handing !
$module = mcrypt_module_open('des', '', 'cbc', '');
mcrypt_generic_init($module, $key, $iv);
$plaintext = mdecrypt_generic($module, $msg);
mcrypt_generic_deinit($module);
echo $plaintext;
output: GROW06BP
As I've already mentioned in my comment, you're using the wrong algorithm in your PHP code since it's Rijndael. What you should use is MCRYPT_DES.
$key = "A818163DD5E0DE87";
// Here you need pack instead of unpack
$packKey = pack("H*",$key);
// you should use the key as the initialization vector
// use something like mcrypt_create_iv to generate an IV
$iv = $packKey;
$plaintext = "GROW06BP";
// replaced MCRYPT_RIJNDAEL_128 with MCRYPT_DES
$ciphertext = mcrypt_encrypt(MCRYPT_DES, $packKey, $plaintext,MCRYPT_MODE_CBC, $iv);
echo base64_encode($ciphertext);
This will produce the same output as the C# code