Reputation: 53
One of our partner has a PHP website where we can connect using an API. This API requires some encrypted string to be sent in order to validate the client. They are using OpenSSL
This is the PHP code that allow them to generate their "secret"
$username = "MY_USERNAME";
$token = "V+Ylfg2XSTB4P0OD5LpuoyAzJAhApK9K+bDlLUtUlgdUzBu1ldfeBTavL7EC3v4UL4W52F0BPC9mL2mJxfURgILh0q/xpebPj2Ek3VdTQ9o5SO4WLjNx1Stg5uMYgcyd";
$key = md5($username);
// openssl
$cryptAlgo = 'AES-256-CFB';
$iv = mb_substr(base64_decode($token), 0, 16, '8bit');
$secret = base64_encode($iv.openssl_encrypt($token, $cryptAlgo, $key, 0, $iv));
echo $secret;
On our side, we are a Windows ASP.NET C# web app. How can we encrypt the same way they are doing it.
Any help would be appreciated.
EDIT: Removed irrelevant code sample.
Upvotes: 2
Views: 2122
Reputation: 111810
I think that the OpenSSL CFB mode isn't compatible with .NET: there is a difference in the last block. OpenSSL CFB simply truncates it if it isn't full, while .NET will pad it. This code WILL work if you set the Mode = CipherMode.CBC
, but will produce different result if you use the Mode = CipherMode.CFB
(you can see that the last characters generated by GenerateSecret()
are different). I'm giving a GenerateSecret()
and a ExtractSecret()
written in C#.
If someone with more experience than me can explain this difference (I've seen the explanation here, but it isn't clear how I should "solve" it).
public static string GenerateSecret(string username, string token)
{
byte[] key;
using (var md5 = MD5.Create())
{
key = md5.ComputeHash(Encoding.UTF8.GetBytes(username));
}
key = BytesToLowerHexBytes(key);
var iv = Convert.FromBase64String(token);
if (iv.Length != 16)
{
Array.Resize(ref iv, 16);
}
byte[] encrypted;
using (var rijndael = new RijndaelManaged())
{
rijndael.Mode = CipherMode.CFB;
rijndael.Padding = PaddingMode.PKCS7;
rijndael.KeySize = 256;
using (var msEncrypt = new MemoryStream())
{
using (ICryptoTransform encryptor = rijndael.CreateEncryptor(key, iv))
using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
var buffer = Encoding.UTF8.GetBytes(token);
csEncrypt.Write(buffer, 0, buffer.Length);
}
encrypted = msEncrypt.ToArray();
}
}
var buffer2 = Encoding.UTF8.GetBytes(Convert.ToBase64String(encrypted));
using (var ms = new MemoryStream(iv.Length + buffer2.Length))
{
ms.Write(iv, 0, iv.Length);
ms.Write(buffer2, 0, buffer2.Length);
return Convert.ToBase64String(ms.GetBuffer(), 0, (int)ms.Length);
}
}
public static string ExtractSecret(string username, string encrypted)
{
byte[] key;
using (var md5 = MD5.Create())
{
key = md5.ComputeHash(Encoding.UTF8.GetBytes(username));
}
key = BytesToLowerHexBytes(key);
byte[] bytes = Convert.FromBase64String(encrypted);
byte[] iv = bytes;
Array.Resize(ref iv, 16);
byte[] decrypted;
using (var rijndael = new RijndaelManaged())
{
rijndael.Mode = CipherMode.CFB;
rijndael.Padding = PaddingMode.PKCS7;
rijndael.KeySize = 256;
using (var msDecrypt = new MemoryStream())
{
using (ICryptoTransform decryptor = rijndael.CreateDecryptor(key, iv))
using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Write))
{
var buffer = Convert.FromBase64String(Encoding.UTF8.GetString(bytes, 16, bytes.Length - 16));
csDecrypt.Write(buffer, 0, buffer.Length);
}
decrypted = msDecrypt.ToArray();
}
}
return Encoding.UTF8.GetString(decrypted);
}
private static byte[] BytesToLowerHexBytes(byte[] bytes)
{
// The hash is a hex string
var bytes2 = new byte[bytes.Length * 2];
for (int i = 0, j = 0; i < bytes.Length; i++)
{
byte b1 = (byte)(bytes[i] >> 4);
bytes2[j] = (byte)(b1 <= 9 ? '0' + b1 : 'a' + b1 - 10);
j++;
byte b2 = (byte)(bytes[i] & 15);
bytes2[j] = (byte)(b2 <= 9 ? '0' + b2 : 'a' + b2 - 10);
j++;
}
return bytes2;
}
Potentially working
I'm stripping the extra bytes generated by .NET for CFB mode. The BytesToLowerHexBytes
is from the previous sample. Note that I'm not expert enough in cryptography to guarantee that what I'm doing is correct.
public static string GenerateSecret(string username, string token)
{
byte[] key;
using (var md5 = MD5.Create())
{
key = md5.ComputeHash(Encoding.UTF8.GetBytes(username));
}
key = BytesToLowerHexBytes(key);
var iv = Convert.FromBase64String(token);
if (iv.Length != 16)
{
Array.Resize(ref iv, 16);
}
byte[] encrypted;
int encryptedLength;
using (var rijndael = new RijndaelManaged())
{
rijndael.Mode = CipherMode.CFB;
rijndael.Padding = PaddingMode.Zeros;
rijndael.KeySize = 256;
using (var msEncrypt = new MemoryStream())
{
var buffer = Encoding.UTF8.GetBytes(token);
using (ICryptoTransform encryptor = rijndael.CreateEncryptor(key, iv))
using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
csEncrypt.Write(buffer, 0, buffer.Length);
}
// CFB is a stream cipher, where the length of the encrypted text should be
// equal to the length of the original text... So we strip the last bytes
encrypted = msEncrypt.GetBuffer();
encryptedLength = buffer.Length;
}
}
var buffer2 = Encoding.UTF8.GetBytes(Convert.ToBase64String(encrypted, 0, encryptedLength));
using (var ms = new MemoryStream(iv.Length + buffer2.Length))
{
ms.Write(iv, 0, iv.Length);
ms.Write(buffer2, 0, buffer2.Length);
return Convert.ToBase64String(ms.GetBuffer(), 0, (int)ms.Length);
}
}
public static string ExtractSecret(string username, string encrypted)
{
byte[] key;
using (var md5 = MD5.Create())
{
key = md5.ComputeHash(Encoding.UTF8.GetBytes(username));
}
key = BytesToLowerHexBytes(key);
byte[] bytes = Convert.FromBase64String(encrypted);
byte[] iv = bytes;
Array.Resize(ref iv, 16);
byte[] decrypted;
int decryptedLength;
using (var rijndael = new RijndaelManaged())
{
rijndael.Mode = CipherMode.CFB;
rijndael.Padding = PaddingMode.Zeros;
rijndael.KeySize = 256;
using (var msDecrypt = new MemoryStream())
{
var buffer = Convert.FromBase64String(Encoding.UTF8.GetString(bytes, 16, bytes.Length - 16));
using (ICryptoTransform decryptor = rijndael.CreateDecryptor(key, iv))
using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Write))
{
csDecrypt.Write(buffer, 0, buffer.Length);
// We have to add the remaining bytes of the block. They aren't
// important, so we add zeroes
int remaining = 16 - (buffer.Length % 16);
if (remaining != 0)
{
csDecrypt.Write(new byte[remaining], 0, remaining);
}
}
// CFB is a stream cipher, where the length of the encrypted text should be
// equal to the length of the original text... So we strip the last bytes
decrypted = msDecrypt.GetBuffer();
decryptedLength = buffer.Length;
}
}
return Encoding.UTF8.GetString(decrypted, 0, decryptedLength);
}
Upvotes: 1