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"
Related
I am trying to decrypt a ciphersaber encrypted hexadecimal message using an IV mixing round of 20 with the key MyKey.
The messages is:
bad85d9e7f5aff959b6b332b44af2cc554d8a6eb
I am doing this in pure C# and it should return the message: Hola Mundo
using System;
using System.Text;
public class Program
{
public static void Main(string[] args)
{
// Hexadecimal text
string hexText = "bad85d9e7f5aff959b6b332b44af2cc554d8a6eb";
// Convert hexadecimal text to byte array
byte[] encryptedData = new byte[hexText.Length / 2];
for (int i = 0; i < encryptedData.Length; i++)
{
encryptedData[i] = Convert.ToByte(hexText.Substring(i * 2, 2), 16);
}
// IV length
int ivLength = 1;
// Key loop iterations
int keyIterations = 20;
// Encryption key
string encryptionKey = "MyKey";
// Convert encryption key to byte array
byte[] keyData = Encoding.UTF8.GetBytes(encryptionKey);
// Create an array to store the IV
byte[] ivData = new byte[ivLength];
// Copy the first `ivLength` bytes of the encrypted data to the IV array
Array.Copy(encryptedData, 0, ivData, 0, ivLength);
// Create an array to store the encrypted message
byte[] messageData = new byte[encryptedData.Length - ivLength];
// Copy the remaining bytes of the encrypted data to the message data array
Array.Copy(encryptedData, ivLength, messageData, 0, messageData.Length);
// Create an array to store the decrypted message
byte[] decryptedData = new byte[messageData.Length];
// Perform the decryption
for (int i = 0; i < messageData.Length; i++)
{
decryptedData[i] = (byte)(messageData[i] ^ keyData[i % keyData.Length]);
for (int j = 0; j < keyIterations; j++)
{
decryptedData[i] = (byte)(decryptedData[i] ^ ivData[j % ivData.Length]);
}
}
// Convert the decrypted data to a string and print it
string decryptedMessage = Encoding.UTF8.GetString(decryptedData);
Console.WriteLine("Decrypted message: " + decryptedMessage);
}
}
Now when I try it returns: �$�#���Jf=�I���
What mistake am I making in the code or am I implementing it wrong?
I tested the text with the following site to see if it was ok: https://ruletheweb.co.uk/cgi-bin/saber.cgi
CipherSaber uses as IV the first 10 bytes of the encrypted message. The rest is the actual ciphertext. The IV is appended to the key (giving the key setup input), which is used as input to the CipherSaber key setup, see CipherSaber, Technical description, 1st section.
In the posted code, an IV length of 1 is applied instead of 10, which incorrectly determines IV (and thus key setup input) and actual ciphertext. The correct determination of IV and actual ciphertext is:
private static (byte[], byte[]) SeparateIvCiphertext(byte[] ivCiphertext)
{
int ivLen = 10;
byte[] iv = new byte[ivLen];
Buffer.BlockCopy(ivCiphertext, 0, iv, 0, iv.Length);
byte[] ciphertext = new byte[ivCiphertext.Length - iv.Length];
Buffer.BlockCopy(ivCiphertext, iv.Length, ciphertext, 0, ciphertext.Length);
return (iv, ciphertext);
}
and of the key setup input:
private static byte[] GetKeySetupInput(byte[] key, byte[] iv)
{
byte[] keySetupInput = new byte[key.Length + iv.Length];
Buffer.BlockCopy(key, 0, keySetupInput, 0, key.Length);
Buffer.BlockCopy(iv, 0, keySetupInput, key.Length, iv.Length);
return keySetupInput;
}
Furthermore, the decryption itself seems to be implemented incorrectly or at least incompletely. CipherSaber uses RC4 as its encryption/decryption algorithm, which can be divided into a key setup and the actual encryption/decryption:
The referenced website performs decryption using CipherSaber-2. Compared to the original CipherSaber (referred to as CipherSaber-1), a modified key setup is used in which the CipherSaber-1/RC4 key setup is repeated multiple times, 20 times in the case of the posted data.
A description of the CipherSaber-1/RC4 key setup can be found here, Key-scheduling algorithm (KSA), a possible implementation for CipherSaber-2 is:
private static byte[] sBox = new byte[256];
private static void KeySetup(byte[] input, int iterations)
{
for (int i = 0; i < 256; i++)
{
sBox[i] = (byte)i;
}
int j = 0;
for (int cs2loop = 0; cs2loop < iterations; cs2loop++) // CipherSaber-2 modification
{
for (int i = 0; i < 256; i++)
{
j = (j + sBox[i] + input[i % input.Length]) % 256;
Swap(ref sBox[i], ref sBox[j]);
}
}
}
private static void Swap(ref byte val1, ref byte val2)
{
if (val1 == val2) return;
val1 = (byte)(val1 ^ val2);
val2 = (byte)(val2 ^ val1);
val1 = (byte)(val1 ^ val2);
}
The loop marked CipherSaber-2 modification in the code snippet is the modification compared to CipherSaber-1/RC4!
The actual encryption/decryption is described here, Pseudo-random generation algorithm (PRGA), a possible implememtation is:
private static byte[] Process(byte[] input)
{
int i = 0, j = 0;
byte[] result = new byte[input.Length];
for (int k = 0; k < input.Length; k++)
{
i = (i + 1) % 256;
j = (j + sBox[i]) % 256;
Swap(ref sBox[i], ref sBox[j]);
result[k] = (byte)(sBox[(sBox[i] + sBox[j]) % 256] ^ input[k]);
}
return result;
}
Note that this algorithm is used for both encryption and decryption.
With this, the posted encrypted message can be decrypted as follows:
using System;
using System.Text;
...
byte[] key = Encoding.UTF8.GetBytes("MyKey");
byte[] encryptedData = Convert.FromHexString("bad85d9e7f5aff959b6b332b44af2cc554d8a6eb");
(byte[] iv, byte[] ciphertext) = SeparateIvCiphertext(encryptedData);
byte[] keySetupInput = GetKeySetupInput(key, iv);
int iterations = 20;
KeySetup(keySetupInput, iterations);
byte[] plaintext = Process(ciphertext);
Console.WriteLine(Encoding.UTF8.GetString(plaintext)); // Hola Mundo
which gives Hola Mundo as plaintext.
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.
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.
I'm trying to do the following test to return results that should return a specific cipher. They provide the Key, IV and Plaintext string as seen below.
But I am getting "Specified initialization vector (IV) does not match the block size for this algorithm."
I been stuck on this for a while and can't find a good simple example and tried a combination of things.
Below is my C# code. I tried to keep it very simple.
string AesPlainText = "1654001d3e1e9bbd036a2f26d9a77b7f";
string AesKey = "3ccb6039c354c9de72adc9ffe9f719c2c8257446c1eb4b86f2a5b981713cf998";
string AesIV = "ce7d4f9679dfc3930bc79aab81e11723";
AesCryptoServiceProvider aes = new AesCryptoServiceProvider();
aes.KeySize = 256;
aes.IV = HexToByteArray(AesIV);
aes.Key = HexToByteArray(AesKey);
aes.Mode = CipherMode.CBC;
// Convert string to byte array
byte[] src = Encoding.Unicode.GetBytes(AesPlainText);
// encryption
using (ICryptoTransform encrypt = aes.CreateEncryptor())
{
byte[] dest = encrypt.TransformFinalBlock(src, 0, src.Length);
// Convert byte array to Base64 strings
Console.WriteLine(Convert.ToBase64String(dest));
}
UPDATED PER ANSWER:
Thanks, great observation. I changed Encoding.UTF8.GetBytes to use HexToByteArray in the above example and it works now.
public static byte[] HexToByteArray(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;
}
Your plaintext, key and IV seem to be specified in hexadecimals, so you need to decode the hexadecimals to get to the underlying bytes instead of performing UTF8 encoding.
You can get a byte array from hex here. Note that the name of the method should have something with hex in in, don't call it StringToByteArray or atoi or something stupid like that.
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...