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
- a string for the switch statement (e.g. -1)
- a hex string representing a salt
- a hex string representing the content
.
<?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:
If you use this data in the PHP code,
decryptData()returns:The following changes must be made in the C# code:
This results e.g. in the implementation below:
If this code is executed, the test plaintext results. Therefore, with this code your ciphertext should be decryptable using your key.