DES Encryption from C# to PHP not working as expected - c#

I have to recreate things from c# to php.
I dont really know what to do cuz I never really had to deal with things like encryption or something.
In c# I got this:
public static byte[] decrypt(byte[] data, byte[] key)
{
DESCryptoServiceProvider des = new DESCryptoServiceProvider();
des.Key = key;
des.Mode = CipherMode.ECB;
des.Padding = PaddingMode.None;
return des.CreateDecryptor().TransformFinalBlock(data, 0, data.Length);
}
public static byte[] encrypt(byte[] data, byte[] key)
{
DESCryptoServiceProvider des = new DESCryptoServiceProvider();
des.Key = key;
des.Mode = CipherMode.ECB;
des.Padding = PaddingMode.None;
return des.CreateEncryptor().TransformFinalBlock(data, 0, data.Length);
}
public static byte[] get8byte(string input)
{
byte[] ByteArray = new byte[8];
string tmp = string.Empty;
int j = 0;
for (int i = 0; i < 16; i++)
{
tmp = "" + input[i];
tmp = tmp + input[i + 1];
ByteArray[j] = byte.Parse(tmp, System.Globalization.NumberStyles.HexNumber);
j++;
i++;
}
return ByteArray;
}
and the key i have to use is encrypted like the following:
var Buffer = new char[16];
var cMasterKey = new byte[8];
byte[] Key = {
(byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5',
(byte) '6', (byte) '7', (byte) '8'
};
cMasterKey = DESUtils.get8byte(new string(Buffer));
MasterKey = DESUtils.decrypt(cMasterKey, Key);
The "Buffer" comes from an USB Drive which has a File on it which contains a Masterkey of 16 Chars.
I really don't know how to realize it in PHP. I tried a lot of things like pack('C*', $var) and things like that but I didnt get the same result.
Is there anyone here who knows how to handle it? I dont know if I'm on the right way but I tried things like this:
$key = pack('C*', 1, 2, 3, 4, 5, 6, 7, 8);
$masterbyte = pack('C*', $buffer);
$decmasterkey = mcrypt_decrypt(MCRYPT_DES, $key, $masterbyte, MCRYPT_MODE_ECB);

'1' in C# is a character literal. Characters can be directly cast to a byte under the default ASCII assumption. So '1' is actually a 0x31 byte and not a 0x01 byte like you have in PHP.
You "want":
$key = "12345678";
Whether the decoding of $buffer is correct depends on its content and how you read it.
Some notes:
Don't use DES nowadays. It's really insecure. AES is a better alternative.
Never use ECB mode. It's deterministic and therefore not semantically secure. You should at the very least use a randomized mode like CBC or CTR. It is better to authenticate your ciphertexts so that attacks like a padding oracle attack are not possible. This can be done with authenticated modes like GCM or EAX, or with an encrypt-then-MAC scheme.

Related

Specified padding mode is not valid for this algorithm when using AES & PKCS#7 padding in .Net core 2.0

I spent a whole day investigating this and search all related questions on Stack Overflow for this question so please don't mention about possible duplicates.
The code below gives me a System.Security.Cryptography.CryptographicException: 'Specified padding mode is not valid for this algorithm.'
While using the very same parameters on this website : http://aes.online-domain-tools.com it decrypts perfectly into "Hello world" then filled with five 'x05' bytes for padding (PKCS#7 padding).
However the code below will always yield an exception when calling the TransformFinalBlock()
Context:
Console application running on Win8.1 with .NET Core 2.0 / Algorithm is AES / CBC / padding PKCS#7
I also tried the proposed solution here: Specified padding mode is not valid for this algorithm - c# - System.Security.Cryptography but no success (I also don't understand why if IV is already set in the SymmetricAlgorithm instance, it should be used later on when deciphering?
static void Main(string[] args)
{
string encryptedStr = "e469acd421dd71ade4937736c06fdc9d";
string passphraseStr = "1e089e3c5323ad80a90767bdd5907297b4138163f027097fd3bdbeab528d2d68";
string ivStr = "07dfd3f0b90e25e83fd05ba338d0be68";
// Convert hex strings to their ASCII representation
ivStr = HexStringToString(ivStr);
passphraseStr = HexStringToString(passphraseStr);
encryptedStr = HexStringToString(encryptedStr);
// Convert our ASCII strings to byte arrays
byte[] encryptedBytes = Encoding.ASCII.GetBytes(encryptedStr);
byte[] key = Encoding.ASCII.GetBytes(passphraseStr);
byte[] iv = Encoding.ASCII.GetBytes(ivStr);
// Configure our AES decryptor
SymmetricAlgorithm algorithm = Aes.Create();
algorithm.Mode = CipherMode.CBC;
algorithm.Padding = PaddingMode.PKCS7;
algorithm.KeySize = 256;
//algorithm.BlockSize = 128;
algorithm.Key = key;
algorithm.IV = iv;
Console.WriteLine("IV length " + iv.Length); // 16
Console.WriteLine("Key length " + key.Length); // 32
ICryptoTransform transform = algorithm.CreateDecryptor(algorithm.Key, algorithm.IV);
// Perform decryption
byte[] outputBuffer = transform.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length);
// Convert it back to a string
string result = Encoding.ASCII.GetString(outputBuffer);
Console.WriteLine(result);
Console.ReadLine();
}
public static string HexStringToString(string hexString)
{
var sb = new StringBuilder();
for (var i = 0; i < hexString.Length; i += 2)
{
var hexChar = hexString.Substring(i, 2);
sb.Append((char)Convert.ToByte(hexChar, 16));
}
return sb.ToString();
}
The problem is in the way how you convert hex string to byte array. Try to debug your code and check the value of array encryptedBytes. You'll see the following array:
{ 0x3f, 0x69, 0x3f, 0x3f, 0x21, 0x3f, 0x71, 0x3f, 0x3f, 0x3f, 0x77, 0x36, 0x3f, 0x6f, 0x3f, 0x3f }
which is far from input e469acd421dd71ade4937736c06fdc9d.
You shouldn't use System.String object as just a holder of binary char codes because .Net strings are UTF16-encoded.
Now when root cause is clear, the fix is pretty straighforward. Change your HexStringToString method so that it converts hex string to bytes array directly:
public static byte[] HexStringToByteArray(string hexString)
{
if (hexString.Length % 2 != 0)
{
throw new InvalidOperationException($"Inalid hex string '{hexString}'");
}
byte[] bytes = new byte[hexString.Length / 2];
for (var i = 0; i < hexString.Length; i += 2)
{
var hexChar = hexString.Substring(i, 2);
bytes[i / 2] = Convert.ToByte(hexChar, 16);
}
return bytes;
}
Then adjust the code in Main():
byte[] encryptedBytes = HexStringToByteArray(encryptedStr);
byte[] key = HexStringToByteArray(passphraseStr);
byte[] iv = HexStringToByteArray(ivStr);
This will give you desired Hello world in result variable.

C# TripleDESCryptoServiceProvider - how it is possible that it can work with short key

I'm trying to port following code to java, but wondering how this can work in C#.
Provider is being initialized with keySize = 192, but key is only 16 bytes long.
When doing this in java I got error about incorrect key size.
Can someone explain what is going on here?
public static byte[] TripleDesEncryptOneBlock(byte[] plainText)
{
//This is key of length 16 bytes (128 bits)
byte[] key = Encoding.ASCII.GetBytes("0123456789abcdef");
byte[] result;
try
{
int count = (plainText.Length > 8) ? 8 : plainText.Length;
byte[] array = new byte[8];
Buffer.BlockCopy(plainText, 0, array, 0, count);
ICryptoTransform cryptoTransform = new TripleDESCryptoServiceProvider
{
KeySize = 192,
Key = key,
Mode = CipherMode.ECB,
Padding = PaddingMode.None
}.CreateEncryptor();
byte[] array2 = cryptoTransform.TransformFinalBlock(array, 0, 8);
result = array2;
}
catch (Exception)
{
result = null;
}
return result;
}
It's copying the first 8 bytes of the key to the end of the key. The TDEA standard calls this "keying option 2", and it only provides 80 bits of effective security (that is, it's a weak cipher).
Many Java providers won't do this automatically; by forcing the application to do it explicitly, unsuspecting fallback to a weaker scheme is less likely.

Issue with 3DES encryption - different ciphertext each time

I tried implementing 3DES in ECB mode in c#.
The problem is code below gives me different ciphertext each time I run it, even though I pass it same parameters as you can see - and I use ECB mode.
Can someone help what is wrong? The output must be same each time I run program below isn't it?
public static byte[] SingleBlock3DES_ECB_Encrypt(byte [] plain, byte [] key)
{
if(plain.Length != 8)
throw new Exception("Plain text length for single block should be 8 bytes");
TripleDESCryptoServiceProvider tdes = new TripleDESCryptoServiceProvider();
// set the secret key for the tripleDES algorithm
tdes.Key = key;
// mode of operation. there are other 4 modes.
// We choose ECB(Electronic code Book)
tdes.Mode = CipherMode.ECB;
// padding mode(if any extra byte added)
tdes.Padding = PaddingMode.None;
// Set key size
tdes.KeySize = 192;
ICryptoTransform cTransform = tdes.CreateEncryptor();
// transform the specified region of bytes array to resultArray
byte[] resultArray = cTransform.TransformFinalBlock(plain, 0, plain.Length);
// Release resources held by TripleDes Encryptor
tdes.Clear();
return resultArray;
}
static void Main(string[] args)
{
byte[] plain = new byte[8];
byte[] key = new byte[24];
for (int i = 0; i < 8; i++)
plain[i] = 1;
for (int i = 0; i < 24; i++)
key[i] = (byte) i;
byte[] res = SingleBlock3DES_ECB_Encrypt(plain, key);
string hex = BitConverter.ToString(res);
Console.WriteLine(hex);
}
So in simple words if I run this program multiple times I get different output each time. Clearly there must be some problem somewhere?
As you have written, if you remove tdes.KeySize = 192 the code works. But what happens in truth is that when you
tdes.KeySize = 192;
the key is reset to a random value.
So you could move the
tdes.KeySize = 192;
BEFORE the
tdes.Key = key;
or simply remove it, because for 3DES the KeySize is fixed to 192.
I solved the problem. By some miracle, when I remove this
tdes.KeySize = 192;
from code, it works.

Decrypt returning junk data

I got my encryption algorithm working fine and left it alone for a few months. Today I needed to use it again and somehow it broke. I have poked at it for a while and have been unable to spot what the problem is. There are no errors, it just returns junk data.
The Setup: A PHP script(that has worked in production for a long time) encrypts some string using:
function hexstr($hexstr)
{
// return pack('H*', $hexstr); also works but it's much harder to understand.
$return = '';
for ($i = 0; $i < strlen($hexstr); $i+=2) {
$return .= chr(hexdec($hexstr[$i] . $hexstr[$i+1]));
}
return $return;
}
function encrypt($str, $key)
{
$key = hexstr($key);
$size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($size, MCRYPT_RAND);
return $iv . mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $str, MCRYPT_MODE_CBC,$iv);
}
If I decrypt this on the php side it works fine.
Then the string is base64 encoded:
$encoded = base64_encode($encrypted);
Finally the C# program gets a hold of it and does the following:
....
byte[] decoded = Convert.FromBase64String(myWebText);
Decrypt(decoded)
.....
public static byte[] Decrypt(byte[] p)
{
RijndaelManaged aes128 = new RijndaelManaged();
aes128.KeySize = 128;
//aes128.BlockSize =
aes128.Padding = PaddingMode.Zeros;
aes128.Mode = CipherMode.CBC;
aes128.Key = StringToByteArray("SOMEHEXSTRING");
//pull the iv off the front of the byte array
if (p.Length <= 16)
{
Utils.ReportError("byte array too short");
return null;
}
byte[] iv = new byte[16];
Array.Copy(p, 0, iv, 0, 16);
aes128.IV = iv;
ICryptoTransform transform = aes128.CreateDecryptor();
byte[] result = transform.TransformFinalBlock(p, 16, p.Length - 16);
Debug.Log("If this encrypted stuff was text it would be:"+System.Text.Encoding.UTF8.GetString(result));
return result;
}
public static byte[] StringToByteArray(string hex)
{
if (hex.Length % 2 == 1)
throw new Exception("The binary key cannot have an odd number of digits");
byte[] arr = new byte[hex.Length >> 1];
for (int i = 0; i < hex.Length >> 1; ++i)
{
arr[i] = (byte)((GetHexVal(hex[i << 1]) << 4) + (GetHexVal(hex[(i << 1) + 1])));
}
return arr;
}
Does anyone know what this might not be working? It must be very close because I am sure it worked at one point.
Update: I did a binary compare of when i C# encode\decode and php encode decode. The php encoded Hello World is not the same as the c# Hello world when using the same IV. is this possible or does this indicate they are not using the same configuration somehow.
Turns out I had changed the key to have non upper case letters and my GetHexVal function shown there only took upper case letters... Ouch
IDEA: The decryption key has changed.
Very nasty when it happens.
Are you using a config file with decryption key?
If not What is the decryptionKey used ?
Has it changed?
If using localApp.config or ROOT.config and the key was changed, you can go hunting around all backups to get the key.
<machineKey decryptionKey="SomeHexValueHere" validationKey="SomeveryLongHexvalueHere" />
But if your PHP version is working, the key must be "known"

Decrypted text from AES encryption has extra spaces

I'm trying to decrypt passwords that were stored in a database from a standard SqlMembershipProvider. In order to do this, I hacked together following console app:
static void Main(string[] args)
{
const string encryptedPassword = #"wGZmgyql4prPIr7t1uaxa+RBRJC51qOPBO5ZkSskUtUCY1aBpqNifQGknEfWzky4";
const string iv = #"Jc0RhfDog8SKvtF9aI+Zmw==";
var password = Decrypt(encryptedPassword, iv);
Console.WriteLine(password);
Console.ReadKey();
}
public static string Decrypt(string toDecrypt, string iv)
{
var ivBytes = Convert.FromBase64String(iv);
const string decryptKey = "DECRYPTION_KEY_HERE";
var keyArray = StringToByteArray(decryptKey);
var toEncryptArray = Convert.FromBase64String(toDecrypt);
var rDel = new AesCryptoServiceProvider() { Key = keyArray, IV = ivBytes};
var cTransform = rDel.CreateDecryptor();
var resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
return Encoding.UTF8.GetString(resultArray);
}
public static byte[] StringToByteArray(String hex)
{
var numberChars = hex.Length;
var bytes = new byte[numberChars / 2];
for (var i = 0; i < numberChars; i += 2)
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
return bytes;
}
This does indeed decrypt the text, however instead of the resulting text being something like "Password1", it's "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0P\0a\0s\0s\0w\0o\0r\0d\01\0" which writes to the console as a bunch of spaces, then "P a s s w o r d 1". Any ideas what I'm doing wrong?
I suspect that part of the problem might be that the original password was encoded as UTF-16 before encryption, and you're decoding it as UTF-8. Try changing the final line of your Decrypt method:
return Encoding.Unicode.GetString(resultArray);
That doesn't explain all those spurious leading zeros though. Very strange...
EDIT...
Actually, I seem to remember that SqlMembershipProvider prefixes the password bytes with a 16-byte salt before encryption, in which case you'll probably be able to get away with something like this:
return Encoding.Unicode.GetString(resultArray, 16, resultArray.Length - 16);
But that still doesn't explain why those 16 bytes are all zeros rather than a bunch of random values...

Categories