I've been breaking my head over this for a few days. To make an API call I have to encrypt some data. The receiving people gave me this code snippet to use for the encryption.
<?php
# Config
$algo = 'AES-256-GCM';
$hmac = 'SHA3-256';
$key = 'PUBLIC-KEY-HERE';
$encoded_key = hash_pbkdf2($hmac, substr($key, 0, 16), substr($key, 16), 4096, 32, TRUE);
$tagLength = 16;
$checksumlength = 64;
# Decrypt
$ciphertext = base64_decode("4T9Rm2FSRUrfkVH58Vd+oLCvwqOtVRKyHz8pDGMzM2IyZTNkZWE1NmFlNjQxMDgyNzZhYjIyOWNhZWI1YmU5OTc4N2U0OWVkYjY3ZjQ1NjZmYTI3NDQ3MzY3NzYAEHU4a3+czd8YSQ==");
$ivLength = openssl_cipher_iv_length('$AES-256-GCM');
$iv = substr($ciphertext, 0, $ivLength);
$tag = substr($ciphertext, $ivLength, $tagLength);
$checksum = substr($ciphertext, $ivLength + $tagLength, $checksumlength);
$payload = substr($ciphertext, $ivLength + $tagLength + $checksumlength);
try {
$result = openssl_decrypt($payload, $algo, $encoded_key, OPENSSL_RAW_DATA, $iv, $tag);
}
catch(Exception $e) {
echo $e->getMessage();
}
var_dump($result);
if(hash_hmac($hmac, $result, $key) == $checksum) {
echo "<p>Valid decryption</p>";
}
# Encrypt
$payload = 'Lorum Ipsum';
$iv = openssl_random_pseudo_bytes($ivLength);
$checksum = hash_hmac($hmac, $payload, $key);
$tag= null;
$encrypted_text = openssl_encrypt($payload, $algo, $encoded_key, OPENSSL_RAW_DATA, $iv, $tag, '', $tagLength);
$result = base64_encode($iv . $tag . $checksum . $encrypted_text);
var_dump($result);
This is what I've made
public static string EncryptUsingAes(string plainText, byte[] Key, byte[] IV)
{
// Check arguments.
if (plainText == null || plainText.Length <= 0)
throw new ArgumentNullException(nameof(plainText));
if (Key == null || Key.Length <= 0)
throw new ArgumentNullException(nameof(Key));
if (IV == null || IV.Length <= 0)
throw new ArgumentNullException(nameof(IV));
// with the specified key and IV.
var keyParam = new KeyParameter(Key);
var parameters = new ParametersWithIV(keyParam, IV);
var cipher = new GcmBlockCipher(new AesEngine());
cipher.Init(true, parameters);
var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
var ciphertext = new byte[cipher.GetOutputSize(plainTextBytes.Length)];
var len = cipher.ProcessBytes(plainTextBytes, 0, plainTextBytes.Length, ciphertext, 0);
cipher.DoFinal(ciphertext, len);
var tag = cipher.GetMac();
var checksum = GeneratePBKDF2Key(Encoding.UTF8.GetString(Key), plaintext);
var combinedBytes = CombineByteArrays(IV, tag, checksum, ciphertext);
var result = Convert.ToBase64String(combinedBytes);
return result;
}
public static string DecryptUsingAes(string encrypted, byte[] Key)
{
var decoded = Convert.FromBase64String(encrypted);
const int ivLength = 12;
const int tagLength = 16;
const int checksumLength = 32; //64 bytes
var iv = new byte[ivLength];
var tag = new byte[tagLength];
var checksum = new byte[checksumLength];
var encryptedText = new byte[decoded.Length - ivLength - tagLength - checksumLength];
Buffer.BlockCopy(decoded, 0, iv, 0, ivLength);
Buffer.BlockCopy(decoded, ivLength, tag, 0, tagLength);
Buffer.BlockCopy(decoded, ivLength + tagLength, checksum, 0, checksumLength);
Buffer.BlockCopy(decoded, ivLength + tagLength + checksumLength, encryptedText, 0, encryptedText.Length);
var keyParam = new KeyParameter(Key);
var parameters = new ParametersWithIV(keyParam, iv);
var cipher = new GcmBlockCipher(new AesEngine());
cipher.Init(false, parameters);
var plaintextBytes = new byte[cipher.GetOutputSize(encryptedText.Length)];
var len = cipher.ProcessBytes(encryptedText, 0, encryptedText.Length, plaintextBytes, 0);
cipher.DoFinal(plaintextBytes, len);
var checksum2 = GeneratePBKDF2Key(Encoding.UTF8.GetString(Key), Encoding.UTF8.GetString(plaintextBytes));
Console.WriteLine(checksum2.SequenceEqual(checksum)
? "The encrypted data is valid."
: "The encrypted data is invalid.");
return Encoding.UTF8.GetString(plaintextBytes);
}
public static byte[] CombineByteArrays(params byte[][] arrays)
{
var totalLength = 0;
foreach (var array in arrays)
{
totalLength += array.Length;
}
var combinedArray = new byte[totalLength];
var offset = 0;
foreach (var array in arrays)
{
Buffer.BlockCopy(array, 0, combinedArray, offset, array.Length);
offset += array.Length;
}
return combinedArray;
}
public static byte[] getIv()
{
var random = new SecureRandom();
var iv = new byte[12];
random.NextBytes(iv);
return iv;
}
public static byte[] GeneratePBKDF2Key(string key, string plaintext)
{
var keyBytes = Encoding.UTF8.GetBytes(key);
var plaintextBytes = Encoding.UTF8.GetBytes(plaintext);
IDigest digest = new Sha3Digest(256);
var keyParameter1 = new KeyParameter(keyBytes);
var hmac = new HMac(digest);
hmac.Init(keyParameter1);
var checksum = new byte[hmac.GetMacSize()];
hmac.BlockUpdate(plaintextBytes, 0, plaintextBytes.Length);
hmac.DoFinal(checksum, 0);
return checksum;
}
I have to use bouncycastle given the .NET version we are using.
I am able te encrypt and decrypt the message. But I get a "Cannot decrypt message" error when I do the API call.
I've searched the documentation of boucycastle, checked Stack Overflow, I've even asked ChatGPT to help with the code, but I can't seem to get it to work.
EDIT after some searching and trying I have come to this, it uses the PBKDF2 correctly now and it seems the tag is appended on the ciphertext, so now i pop it off en set it elsewhere.
public static string EncryptUsingAes(string plainText, byte[] Key, byte[] IV)
{
// Check arguments.
if (plainText == null || plainText.Length <= 0)
throw new ArgumentNullException(nameof(plainText));
if (Key == null || Key.Length <= 0)
throw new ArgumentNullException(nameof(Key));
if (IV == null || IV.Length <= 0)
throw new ArgumentNullException(nameof(IV));
// with the specified key and IV.
var unused = Convert.ToBase64String(Key);
var hashedKey = GeneratePBKDF2Hash(Key);
var keyParam = new KeyParameter(hashedKey);
var parameters = new AeadParameters(keyParam, 128, IV);
var hasedKeyString = Convert.ToBase64String(hashedKey);
var cipher = new GcmBlockCipher(new AesEngine());
cipher.Init(true, parameters);
var IVstring = Convert.ToBase64String(IV);
var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
var ciphertextTag = new byte[cipher.GetOutputSize(plainTextBytes.Length)];
var len = cipher.ProcessBytes(plainTextBytes, 0, plainTextBytes.Length, ciphertextTag, 0);
cipher.DoFinal(ciphertextTag, len);
var tag = ciphertextTag.Skip(ciphertextTag.Length - 16).Take(16).ToArray();
var ciphertext = ciphertextTag.Take(ciphertextTag.Length - 16).ToArray();
var TESTTAG = cipher.GetMac();
var checksum = GenerateHmacSha3_256(Key, ciphertext);
var combinedBytes = CombineByteArrays(IV, tag, checksum, ciphertext);
var result = Convert.ToBase64String(combinedBytes);
return result;
}
public static string DecryptUsingAes(string encrypted, byte[] Key)
{
var decoded = Convert.FromBase64String(encrypted);
const int ivLength = 12;
const int tagLength = 16;
const int checksumLength = 64;
var iv = new byte[ivLength];
var tag = new byte[tagLength];
var checksum = new byte[checksumLength];
var encryptedText = new byte[decoded.Length - ivLength - tagLength - checksumLength];
Buffer.BlockCopy(decoded, 0, iv, 0, ivLength);
Buffer.BlockCopy(decoded, ivLength, tag, 0, tagLength);
Buffer.BlockCopy(decoded, ivLength + tagLength, checksum, 0, checksumLength);
Buffer.BlockCopy(decoded, ivLength + tagLength + checksumLength, encryptedText, 0, encryptedText.Length);
var ciphertext = Convert.ToBase64String(encryptedText);
var ivtext = Convert.ToBase64String(iv);
//var generator = GeneratorUtilities.GetKeyGenerator("AES");
//generator.Init(new KeyGenerationParameters(new SecureRandom(), 256));
var hashedkey = GeneratePBKDF2Hash(Key);
var keyParam = new KeyParameter(hashedkey);
var parameters = new AeadParameters(keyParam, 128, iv);
var hashedKeyString = Convert.ToBase64String(hashedkey);
var cipher = new GcmBlockCipher(new AesEngine());
cipher.Init(false, parameters);
var encryptedTextTag = CombineByteArrays(encryptedText, tag);
var plaintextBytes = new byte[cipher.GetOutputSize(encryptedTextTag.Length)];
var len = cipher.ProcessBytes(encryptedTextTag, 0, encryptedTextTag.Length, plaintextBytes, 0);
cipher.DoFinal(plaintextBytes, len);
var checksum2 = GenerateHmacSha3_256(Key, encryptedText);
Console.WriteLine(checksum2.SequenceEqual(checksum)
? "The encrypted data is valid."
: "The encrypted data is invalid.");
return Encoding.UTF8.GetString(plaintextBytes);
}
public static byte[] CombineByteArrays(params byte[][] arrays)
{
var totalLength = 0;
foreach (var array in arrays)
{
totalLength += array.Length;
}
var combinedArray = new byte[totalLength];
var offset = 0;
foreach (var array in arrays)
{
Buffer.BlockCopy(array, 0, combinedArray, offset, array.Length);
offset += array.Length;
}
return combinedArray;
}
public static byte[] getIv()
{
var random = new SecureRandom();
var iv = new byte[12];
random.NextBytes(iv);
return iv;
}
public static byte[] GeneratePBKDF2Hash(byte[] key)
{
int iterationCount = 4096;
int keySize = 32;
string hmacAlgorithm = "SHA3-256";
// Split the key into two parts: first 16 bytes and the remaining bytes
var keyPart1 = new byte[16];
var keyPart2 = new byte[key.Length - 16];
Buffer.BlockCopy(key, 0, keyPart1, 0, 16);
Buffer.BlockCopy(key, 16, keyPart2, 0, key.Length - 16);
// Create a digest based on the specified HMAC algorithm
IDigest digest;
if (hmacAlgorithm.Equals("SHA3-256", StringComparison.OrdinalIgnoreCase))
digest = new Sha3Digest(256);
else
throw new NotSupportedException("Unsupported HMAC algorithm.");
// Generate the PBKDF2 parameters
var generator = new Pkcs5S2ParametersGenerator(digest);
generator.Init(keyPart1, keyPart2, iterationCount);
// Generate the derived key
var keyParameter = (KeyParameter)generator.GenerateDerivedParameters(keySize * 8);
var unused = Convert.ToBase64String(keyParameter.GetKey());
return keyParameter.GetKey();
}
public static byte[] GenerateHmacSha3_256(byte[] key, byte[] payload)
{
// Create a SHA3-256 digest
IDigest digest = new Sha3Digest(256);
// Create an HMAC generator with the digest and key
var hmac = new HMac(digest);
hmac.Init(new KeyParameter(key));
// Compute the HMAC
hmac.BlockUpdate(payload, 0, payload.Length);
var hmacBytes = new byte[hmac.GetMacSize()];
hmac.DoFinal(hmacBytes, 0);
var unused = Convert.ToBase64String(hmacBytes);
return hmacBytes;
}