I'm consuming an external API which returns me a Blowfish encrypted JSON array. First I tried to implement Blowfish encrypt/decrypt methods using the BountyCastle package based on this post c# Bouncy Castle Blowfish Decryption - Pad block corrupted.
internal class Program
{
private static void Main(string[] args)
{
string key = "KgKnVRujrgAv4XjD4bKCqdQVN5De0DCw8zpu1URnPw8="; // random
string content = "[{'id':1},{'id':2}]";
string encryptedContent = Encrypt(content, key);
string decryptedContent = Decrypt(encryptedContent, key);
/*
decryptedContent returns
[{'id':1},{'id':2}]\0\0\0\0\0
so I think this should be fine
*/
}
private static string Encrypt(string content, string encryptionKey)
{
byte[] contentBytes = Encoding.UTF8.GetBytes(content);
return SharedCode(
contentBytes,
encryptionKey,
true,
encryptedContentBytes => BitConverter
.ToString(encryptedContentBytes)
.Replace("-", ""));
}
private static string Decrypt(string encryptedContent, string decryptionKey)
{
byte[] contentBytes = Hex.Decode(encryptedContent);
return SharedCode(contentBytes, decryptionKey, false, decryptedContentBytes =>
{
string decryptedContentString = BitConverter
.ToString(decryptedContentBytes)
.Replace("-", "");
byte[] hexBytes = new byte[decryptedContentString.Length / 2];
for (int i = 0; i < hexBytes.Length; i++)
{
string currentHexString = decryptedContentString.Substring(i * 2, 2);
hexBytes[i] = Convert.ToByte(currentHexString, 16);
}
return Encoding.UTF8.GetString(hexBytes);
});
}
private static string SharedCode(byte[] contentBytes, string key, bool forceEncryption, Func<byte[], string> processor)
{
BlowfishEngine blowfishEngine = new BlowfishEngine();
PaddedBufferedBlockCipher paddedBufferedBlockCipher = new PaddedBufferedBlockCipher(blowfishEngine);
byte[] keyBytes = Encoding.UTF8.GetBytes(key);
KeyParameter keyParameter = new KeyParameter(keyBytes);
paddedBufferedBlockCipher.Init(forceEncryption, keyParameter);
int outputLength = paddedBufferedBlockCipher.GetOutputSize(contentBytes.Length);
byte[] outputBytes = new byte[outputLength];
int processedBytes = paddedBufferedBlockCipher.ProcessBytes(contentBytes, 0, contentBytes.Length, outputBytes, 0);
paddedBufferedBlockCipher.DoFinal(outputBytes, processedBytes);
return processor(outputBytes);
}
}
Now I want to decrypt the API response. The Api returns me the following Blowfish encrypted JSON body content
$-1$cb8ba9e30b19ff2a$d1157421764fe503d1fa9810fb9e6c3b424a1e8d014a321f5a2fb47ec6ebc8287d4d6236448d3623be42cf927fb883ca48810037c1a62bd229f937727c272c76420eb1f630bb2856c27d10c955220a1539f64e07c5708db90787ac470cad8372ea086501981c7a53ca69740c7ccfced856e234a6801efcf1f71178e75646441ba2716ea75a75ff3e6e002ba08ad18efeef95a909c9a5c68087cc63ed138a63c6788b9bbc43f3c04d2a496660f84ac98f011d3930c61ce9d5565131d2cba65db7c9bef824dd9a6594
I received a decryption key and this PHP code sample as a "documentation". The response itself contains three groups
.
<?php
function decryptData(string $data, string $key): string{
$matches = [];
if(!preg_match('|^\$([^$]{2,4})\$([a-f0-9]{16,64})\$|i', $data, $matches)){
return '';
}
$data = (string) substr($data, strlen($matches[0]));
switch($matches[1]){
default:
return '';
case '-1':
$data = (string) hex2bin($data);
case '-1a':
$algo = 'blowfish';
return (string) openssl_decrypt($data, $algo, (string) base64_decode($key), OPENSSL_RAW_DATA, (string) hex2bin($matches[2]));
}
}
I personally don't know what's the purpose of the salt since I don't need it for my implementation but I tried to update my code to this
internal class Program
{
private static void Main(string[] args)
{
string key = "KgKnVRujrgAv4XjD4bKCqdQVN5De0DCw8zpu1URnPw8"; // this decryption key is not the correct one to use
string apiResponse = "$-1$cb8ba9e30b19ff2a$d1157421764fe503d1fa9810fb9e6c3b424a1e8d014a321f5a2fb47ec6ebc8287d4d6236448d3623be42cf927fb883ca48810037c1a62bd229f937727c272c76420eb1f630bb2856c27d10c955220a1539f64e07c5708db90787ac470cad8372ea086501981c7a53ca69740c7ccfced856e234a6801efcf1f71178e75646441ba2716ea75a75ff3e6e002ba08ad18efeef95a909c9a5c68087cc63ed138a63c6788b9bbc43f3c04d2a496660f84ac98f011d3930c61ce9d5565131d2cba65db7c9bef824dd9a6594";
Match matches = Regex.Match(apiResponse, @"^\$([^$]{2,4})\$([a-f0-9]{16,64})\$([a-f0-9]*)");
Group algorithm = matches.Groups[1];
Group salt = matches.Groups[2];
Group content = matches.Groups[3];
string encryptedContent = content.ToString();
string decryptedContent = Decrypt(encryptedContent, key);
}
private static string Decrypt(string encryptedContent, string decryptionKey)
{
byte[] contentBytes = Hex.Decode(encryptedContent);
BlowfishEngine blowfishEngine = new BlowfishEngine();
PaddedBufferedBlockCipher paddedBufferedBlockCipher = new PaddedBufferedBlockCipher(blowfishEngine);
byte[] keyBytes = Encoding.UTF8.GetBytes(decryptionKey);
KeyParameter keyParameter = new KeyParameter(keyBytes);
paddedBufferedBlockCipher.Init(false, keyParameter);
int outputLength = paddedBufferedBlockCipher.GetOutputSize(contentBytes.Length);
byte[] outputBytes = new byte[outputLength];
int processedBytes = paddedBufferedBlockCipher.ProcessBytes(contentBytes, 0, contentBytes.Length, outputBytes, 0);
paddedBufferedBlockCipher.DoFinal(outputBytes, processedBytes); // throws Org.BouncyCastle.Crypto.InvalidCipherTextException: 'pad block corrupted'
string decryptedContentString = BitConverter
.ToString(outputBytes)
.Replace("-", "");
byte[] hexBytes = new byte[decryptedContentString.Length / 2];
for (int i = 0; i < hexBytes.Length; i++)
{
string currentHexString = decryptedContentString.Substring(i * 2, 2);
hexBytes[i] = Convert.ToByte(currentHexString, 16);
}
return Encoding.UTF8.GetString(hexBytes);
}
}
Unfortunately the code at
paddedBufferedBlockCipher.DoFinal(outputBytes, processedBytes);
throws a
Org.BouncyCastle.Crypto.InvalidCipherTextException: 'pad block corrupted'
exception. Does someone know how to decrypt this Api response?
The PHP code first separates the three parts. The second part is the IV, which is hex decoded and thus has a size of 8 bytes. The third part is the data, which is first hex decoded because of the -1 in the first part and then decrypted using the key and IV by applying Blowfish in CBC mode with PKCS7 padding.
To check the C# implementation, test data is useful:
MDEyMzQ1Njc4OTAxMjM0NTY3ODk=
$-1$cb8ba9e30b19ff2a$1cb8430f6b9c109e4334874408dbd26be36b0c9600383d63afd70669efcec38bea1290dfbe6b71519b1f48b514957845
If you use this data in the PHP code, decryptData()
returns:
The quick brown fox jumps over the lazy dog
The following changes must be made in the C# code:
This results e.g. in the implementation below:
private static void Main(string[] args)
{
//string key = "KgKnVRujrgAv4XjD4bKCqdQVN5De0DCw8zpu1URnPw8"; // this decryption key is not the correct one to use
//string apiResponse = "$-1$cb8ba9e30b19ff2a$d1157421764fe503d1fa9810fb9e6c3b424a1e8d014a321f5a2fb47ec6ebc8287d4d6236448d3623be42cf927fb883ca48810037c1a62bd229f937727c272c76420eb1f630bb2856c27d10c955220a1539f64e07c5708db90787ac470cad8372ea086501981c7a53ca69740c7ccfced856e234a6801efcf1f71178e75646441ba2716ea75a75ff3e6e002ba08ad18efeef95a909c9a5c68087cc63ed138a63c6788b9bbc43f3c04d2a496660f84ac98f011d3930c61ce9d5565131d2cba65db7c9bef824dd9a6594";
string key = "MDEyMzQ1Njc4OTAxMjM0NTY3ODk=";
string apiResponse = "$-1$cb8ba9e30b19ff2a$1cb8430f6b9c109e4334874408dbd26be36b0c9600383d63afd70669efcec38bea1290dfbe6b71519b1f48b514957845";
Match matches = Regex.Match(apiResponse, @"^\$([^$]{2,4})\$([a-f0-9]{16,64})\$([a-f0-9]*)");
Group algorithm = matches.Groups[1];
Group iv = matches.Groups[2]; // 2nd part is the IV
Group content = matches.Groups[3];
string decryptedContent = Decrypt(content.ToString(), key, iv.ToString()); // Pass additionally the IV
Console.WriteLine(decryptedContent);
}
private static string Decrypt(string encryptedContent, string decryptionKey, string iv)
{
byte[] contentBytes = Hex.Decode(encryptedContent);
byte[] keyBytes = Convert.FromBase64String(decryptionKey); // Base64 decode the key
KeyParameter keyParameter = new KeyParameter(keyBytes);
ParametersWithIV keyParamWithIv = new ParametersWithIV(keyParameter, Hex.Decode(iv)); // Consider the IV
BlowfishEngine blowfishEngine = new BlowfishEngine();
PaddedBufferedBlockCipher paddedBufferedBlockCipher = new PaddedBufferedBlockCipher(new CbcBlockCipher(blowfishEngine), new Pkcs7Padding()); // Consider CBC mode and PKCS7 padding
paddedBufferedBlockCipher.Init(false, keyParamWithIv);
int outputLength = paddedBufferedBlockCipher.GetOutputSize(contentBytes.Length);
byte[] outputBytes = new byte[outputLength];
int processedBytes = paddedBufferedBlockCipher.ProcessBytes(contentBytes, 0, contentBytes.Length, outputBytes, 0);
processedBytes += paddedBufferedBlockCipher.DoFinal(outputBytes, processedBytes);
return Encoding.UTF8.GetString(outputBytes, 0, processedBytes); // The quick brown fox jumps over the lazy dog
}
If this code is executed, the test plaintext results. Therefore, with this code your ciphertext should be decryptable using your key.