For some reason, I've been asked to implement some basic encryption to secure a specific value transmitted to the client (depending on the client).
Context is: we are the one who generate the encrypted key, we pass that encrypted key to the client, and the client will never have to decrypt it (but we will have to in backend)
Example => we give the encrypted key "123ABCDE==" to the client. The client calls our API passing data + that encrypted key, like:
{
"encKey": "123ABCDE==",
"payload": "somedatahere"
}
Then we decrypt the key, if it matches a specific value in DB (again, depending on client), we continue with some other operations.
So, I decided to go with AES encryption. Following is what I have for now.
public class KeyInfo
{
public byte[] Key { get; }
public byte[] Iv { get; }
public KeyInfo()
{
using (var myAes = Aes.Create())
{
Key = myAes.Key;
Iv = myAes.IV;
}
}
public KeyInfo(string key, string iv)
{
Key = Convert.FromBase64String(key);
Iv = Convert.FromBase64String(iv);
}
}
private static byte[] Encrypt_AES(string plainText, byte[] key, byte[] iv)
{
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));
byte[] encrypted;
using (var aesAlgo = Aes.Create())
{
aesAlgo.Key = key;
aesAlgo.IV = iv;
var encryptor = aesAlgo.CreateEncryptor(aesAlgo.Key, aesAlgo.IV);
using (var msEncrypt = new MemoryStream())
{
using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (var swEncrypt = new StreamWriter(csEncrypt))
{
swEncrypt.Write(plainText);
}
encrypted = msEncrypt.ToArray();
}
}
}
return encrypted;
}
private static string Decrypt_AES(byte[] cipherText, byte[] key, byte[] iv)
{
if (cipherText == null || cipherText.Length <= 0)
throw new ArgumentNullException(nameof(cipherText));
if (key == null || key.Length <= 0)
throw new ArgumentNullException(nameof(key));
if (iv == null || iv.Length <= 0)
throw new ArgumentNullException(nameof(iv));
string plaintext;
using (var aesAlgo = Aes.Create())
{
aesAlgo.Key = key;
aesAlgo.IV = iv;
var decryptor = aesAlgo.CreateDecryptor(aesAlgo.Key, aesAlgo.IV);
using (var msDecrypt = new MemoryStream(cipherText))
{
using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (var srDecrypt = new StreamReader(csDecrypt))
{
plaintext = srDecrypt.ReadToEnd();
}
}
}
}
return plaintext;
}
}
"EncryptionKey": "myEncryptionKeyHere",
"EncryptionInitialVector": "myInitialVectorHere",
services.AddTransient(ec => new EncryptionService(new KeyInfo(appSettings.EncryptionKey, appSettings.EncryptionInitialVector)));
I have a few question about all of this.
Thanks for reading!
EDIT:
You say "we are the one who generate the encrypted key, we pass that encrypted key to the client" - how does that happen securely?
-> The client will have to connect to his account where he could access that encKey
.
So, reading @vcsjones
answer, AES may not be the right thing to implement here. Since I don't want to store the IV on database, if the client loses it, it means he would have to generate another key, with another IV, and change the encKey
in all applications.
Would an asymetric encryption be better? If I understood correctly, it would mean to encrypt the value with a private key, give that encrypted value to the client + the public key (which would be the same for every client?)
is AES the right choice for my needs?
By itself, no it is not. AES is a cryptographic primitive - a building block - and such building blocks are not usually useful by themselves. For example with AES-CBC (the mode you are using), this is currently vulnerable to a padding oracle attack and lacks authentication. AES might be the right choice when combined with other primitives that provide authentication, like an HMAC.
The best way to solve this problem is to treat primitives for what they are - primitives that are insufficient for use on their own. There are other libraries, like libsodium, that are more abstracted concepts of cryptography, and provides simple APIs that "do the right thing".
You say "we are the one who generate the encrypted key, we pass that encrypted key to the client" - how does that happen securely?
Barring using something like libsodium, there are some issues to address.
There is no authentication of the ciphertext ("authentication" in cryptography has its own meaning, not like sign-on authentication).
The initialization vector should never be used more than once with the same key. You appear to be using a fixed IV. Every thing you encrypt should use its own random IV. The IV is not a secret. It should be authenticated however along with the cipher text as per point 1.
where should I store the Key and IV ?
The IV, since there should be a 1:1 of them with each cipher text (encrypted output) should be stored with the cipher text. When it's time to decrypt, the caller will need to provide the IV again.
The Key is the real secret. Storing them securely is important. If you store it in appsettings.json, then the security of your key is the same as the security of that JSON file. A more common approach in a cloud environment is to use a managed service. Azure has Azure Key Vault, AWS has KMS and Secret Manager.
how do I generate the Key and IV ? Is there tools for that?
They should be generated with a CSPRNG. For example, to do so programmatically:
byte[] iv = new byte[128 / 8];
RandomNumberGenerator.Fill(iv);
//iv now contains random bytes
You can do the same for generating a key.