I need to encrypt some data in a client application and verify it later in a server application. I'm assuming that if the message was decrypted then it was from a valid client, since the key is necessary to create a valid crypted string.
I'using AES implementation from MSDN https://learn.microsoft.com/pt-br/dotnet/api/system.security.cryptography.aes?view=netframework-4.8
I choose AES because in my tests it generated a short string. It is an important issue for me.
public static void Main()
{
string original = "message to secure";
using (Aes myAes = Aes.Create())
{
myAes.Key = Convert.FromBase64String("AAECAwQFBgcICQoLDA0ODw==");
byte[] encrypted = EncryptStringToBytes_Aes(original, myAes.Key, myAes.IV);
var encryptedString = Convert.ToBase64String(encrypted);
string roundtrip = DecryptStringFromBytes_Aes(Convert.FromBase64String(encryptedString), myAes.Key, myAes.IV);
Console.WriteLine("Encrypted: " + encryptedString);
Console.WriteLine("Decrypted: " + roundtrip);
}
Console.ReadKey();
}
static byte[] EncryptStringToBytes_Aes(string plainText, byte[] Key, byte[] IV)
{
if (plainText == null || plainText.Length <= 0)
throw new ArgumentNullException("plainText");
if (Key == null || Key.Length <= 0)
throw new ArgumentNullException("Key");
if (IV == null || IV.Length <= 0)
throw new ArgumentNullException("IV");
byte[] encrypted;
using (Aes aesAlg = Aes.Create())
{
aesAlg.Key = Key;
aesAlg.IV = IV;
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
swEncrypt.Write(plainText);
}
encrypted = msEncrypt.ToArray();
}
}
}
return encrypted;
}
static string DecryptStringFromBytes_Aes(byte[] cipherText, byte[] Key, byte[] IV)
{
if (cipherText == null || cipherText.Length <= 0)
throw new ArgumentNullException("cipherText");
if (Key == null || Key.Length <= 0)
throw new ArgumentNullException("Key");
if (IV == null || IV.Length <= 0)
throw new ArgumentNullException("IV");
string plaintext = null;
using (Aes aesAlg = Aes.Create())
{
aesAlg.Key = Key;
aesAlg.IV = IV;
ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
using (MemoryStream msDecrypt = new MemoryStream(cipherText))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
{
plaintext = srDecrypt.ReadToEnd();
}
}
}
}
return plaintext;
}
But I noticed that if there is a change in the last caracter (before equals sign) the string is decrypted as nothing was changed.
For example:
HdPAmfHTxkMmj8D3VelWjH2A8iGm6gnzzPYGNT5NR14= was generated and I changed it to HdPAmfHTxkMmj8D3VelWjH2A8iGm6gnzzPYGNT5NR15= and a got the same result.
Could someone give me some guidance in how I guarantee that if the generated string was change it cannot be decrypted?
Solomon has more or less hit the nail on the head with all their comments.
I'm assuming that if the message was decrypted then it was from a valid client, since the key is necessary to create a valid crypted string.
This base assumption is actually false. There are a number of scenarios (in unauthenticated modes of operation) where decryption can succeed even if the ciphertext has been modified - resulting in a plaintext different from the one that was originally encrypted.
Recall that AES is a block cipher. It transforms one block of 128 bits to another block of 128 bits. The only other variables are the key used, and the operation (e.g. encrypt or decrypt). There is no mechanism to detect if the block of 128 bits passed in has been modified since some prior operation - AES isn't aware of that. It is simply a keyed transformation function.
To avoid this problem, use an authenticated mode of operation like GCM, or use an HMAC. See the examples in this repository for an example of use GCM in C#.
As to the second issue:
But I noticed that if there is a change in the last caracter (before equals sign) the string is decrypted as nothing was changed.
Technically nothing was changed - this is a "feature". Each base64 character represents 6 bits of the original data. This means that, unless your ciphertext length is divisible by both 8 and 6, there are bits "left over". See the example below where we encode 16-bits:
Raw : { 0x00, 0x01 }
Binary : 00000000 00000001
Base64 : AAE=
Binary (6 Digit Grouping): 000000 000000 000100
Binary (8 Digit Grouping): 00000000 00000001 00
^^ these bits are irrelevant
In essence, nothing to worry about.