My application is MVC 5 using EF 6.2. I am decrypting certain columns while generating a list, it works but slow. Is there a better way to improve the performance of this approach?
var mylist = await _db.vw_LearnerCourse.AsNoTracking().ToListAsync();
var grid1 = mylist.Select(c => new
{
FirstName = Encryption.Decrypt5(c.FirstName),
LastName = Encryption.Decrypt5(c.LastName)
}).ToList();
public static string Decrypt5(string cipherText)
{
if (string.IsNullOrWhiteSpace(cipherText)) return null;
if (!string.IsNullOrWhiteSpace(cipherText))
{
string strOut;
try
{
var arrOffsets = new ArrayList();
arrOffsets.Insert(0, 73);
arrOffsets.Insert(1, 56);
arrOffsets.Insert(2, 31);
arrOffsets.Insert(3, 58);
arrOffsets.Insert(4, 77);
arrOffsets.Insert(5, 75);
strOut = "";
int intCounter;
for (intCounter = 0;
intCounter <= cipherText.Length - 1;
intCounter += 2)
{
var strSub = cipherText.Substring(intCounter, 1);
var strSub1 = cipherText.Substring(intCounter + 1, 1);
var intVal = int.Parse(strSub,
NumberStyles.HexNumber) * 16 + int.Parse(strSub1,
NumberStyles.HexNumber);
var intMod = intCounter / 2 % arrOffsets.Count;
var intNewVal = intVal -
Convert.ToInt32(arrOffsets[intMod]) + 256;
intNewVal = intNewVal % 256;
var strDecimal = ((char)intNewVal).ToString();
strOut = strOut + strDecimal;
}
}
catch (Exception err)
{
throw new Exception(err.Message);
}
var encryptionKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
cipherText = strOut;
cipherText = cipherText.Replace(" ", "+");
var cipherBytes = Convert.FromBase64String(cipherText);
using (var encryptor = Aes.Create())
{
var pdb = new Rfc2898DeriveBytes(encryptionKey, new byte[]
{
xxxxxxx
});
encryptor.Key = pdb.GetBytes(32);
encryptor.IV = pdb.GetBytes(16);
using (var ms = new MemoryStream())
{
using (var cs = new CryptoStream(ms, encryptor.CreateDecryptor(),
CryptoStreamMode.Write))
{
cs.Write(cipherBytes, 0, cipherBytes.Length);
cs.Close();
}
cipherText = Encoding.Unicode.GetString(ms.ToArray());
}
}
return cipherText;
}
return null;
}
The main reason for the poor performance is the execution of the PBKDF2 key derivation (via Rfc2898DeriveBytes) for every encryption. Key derivations are intentionally slow to slow down attackers. I.e. each encryption adds an artificial delay, which leads to the observed performance loss over runtime.
The execution time of PBKDF2 can be tuned via the iteration count. In general, the value is set as high as possible while maintaining acceptable performance. A typical value is 10,000 iterations, see e.g. here.
The posted code uses the default value of 1000 iterations (see here), which is already very low. Reducing the value also reduces security and is therefore less recommended.
Apart from that, changing the iteration count would lead to incompatibility with already existing data, so a data migration would be necessary.
Another alternative to improve performance is to perform key derivation on list level (instead of for each encryption of the list) and pass key and IV to the Decrypt5() method. This also ensures that compatibility with the old data is not lost:
// Key/IV derivation
var encryptionKey = "This is my passphrase";
var salt = new byte[] {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07};
var pdb = new Rfc2898DeriveBytes(encryptionKey, salt);
byte[] key = pdb.GetBytes(32);
byte[] iv = pdb.GetBytes(16);
// Decryption, key/IV are passed
var mylist = await _db.vw_LearnerCourse.AsNoTracking().ToListAsync();
var grid1 = mylist.Select(c => new
{
FirstName = Encryption.Decrypt5(c.FirstName, key, iv),
LastName = Encryption.Decrypt5(c.LastName, key, iv)
}).ToList();
...
// Decryption without key/IV derivation
public static string Decrypt5(string cipherText, byte[] key, byte[] iv)
{
...
var cipherBytes = Convert.FromBase64String(cipherText);
using (var encryptor = Aes.Create())
{
encryptor.Key = key;
encryptor.IV = iv;
using (var ms = new MemoryStream())
{
using (var cs = new CryptoStream(ms, encryptor.CreateDecryptor(), CryptoStreamMode.Write))
{
cs.Write(cipherBytes, 0, cipherBytes.Length);
cs.Close();
}
cipherText = Encoding.Unicode.GetString(ms.ToArray());
}
}
return cipherText;
...
}
This change significantly improves performance while maintaining compatibility with existing data.
Security:
A vulnerability of the current code is the static salt. A static salt is generally insecure because in case of a successful attack the entire data is affected. Since in the current case the key derivation additionally derives the IV besides the key, this is further aggravated by the inevitable key/IV reuse.
To increase security, these vulnerabilities should be eliminated. However, it is impossible to avoid losing compatibility with the old data, so data migration is necessary.
To improve security, instead of the static salt, a random salt should be generated for each encryption or here with regard to performance at least for each list.
To avoid reuse of key/IV pairs within a list, the IV should not be derived via key derivation, but a fresh, random IV should be generated for each encryption. Although the generation of the random IV has a negative impact on performance, this is significantly outweighed by the gain due to the shift of the key derivation.
Salt and IV are not secret and are usually concatenated with the ciphertext: salt|IV|ciphertext. During decryption, the portions are separated based on the known lengths of salt and IV. Since the same salt is used here for all encryptions of a list, the salt could also be stored separately.
For the iteration count not the default value (of 1000 iterations) should be used, but the largest possible value with acceptable performance as well.
Hard coding of the password should be refrained from (otherwise, e.g. access to the code is enough to compromise the password), instead the password should be entered at runtime.
You may create a Decryptor instance every time when you Decrypt a string. Try create a static decryptor, and use this instance every time.
TripleDESCryptoServiceProvider des = new TripleDESCryptoServiceProvider
{
Key = key,
Mode = CipherMode.ECB
};
// use this instance all time
var decryptor = des.CreateDecryptor();
var mylist = await _db.vw_LearnerCourse.AsNoTracking().ToListAsync();
var grid1 = mylist.Select(c => new
{
FirstName = Encryption.Decrypt5(decryptor,c.FirstName),
LastName = Encryption.Decrypt5(decryptor,c.LastName)
}).ToList();
public static string Decrypt5(ICryptoTransform decryptor, string cipherText)
{
if (string.IsNullOrWhiteSpace(cipherText)) return null;
if (!string.IsNullOrWhiteSpace(cipherText))
{
xxxxxxxx
}
The one thing I'm certain there is no need for manual Encryption\Decryption.
OPTION1
You should use EncryptedColumns EF feature and let encryption/decryption be handled naturally.
https://sd.blackball.lv/articles/read/18805 see example of implementation. Github project https://github.com/emrekizildas/EntityFrameworkCore.EncryptColumn.
Init context with encryption key:
private readonly IEncryptionProvider _provider;
public ExampleDbContext()
{
Initialize.EncryptionKey = "example_encrypt_key";
this._provider = new GenerateEncryptionProvider();
}
Let modelBinder know about encryption:
modelBuilder.UseEncryption(this._provider);
Set the columns
public class User
{
public Guid ID { get; set; }
public string Firstname { get; set; }
public string Lastname { get; set; }
[EncryptColumn]
public string EmailAddress { get; set; }
[EncryptColumn]
public string IdentityNumber { get; set; }
}
OPTION2
You can also opt for Always Encrypted, this will encrypt entire DB
https://techcommunity.microsoft.com/t5/sql-server-blog/using-always-encrypted-with-entity-framework-6/ba-p/384433 for details and some limitations of approach.
The idea is straightforward, we delegate all encryption process to database (for ex MS SQL Server) if it supports this and using SQL Server features organize encryption. The EF Core we instruct via connection string that DB is encrypted and that's really it. Noticeable difference to normal DB design:
Entity Framework assumes order-comparability of PKs in many cases. If PK is encrypted, some scenarios will not work.
Also EF will sometimes print values of the Entity key in exception messages. This could cause sensitive information to be leaked to inappropriate parties. (issue tracked, MS recommended to use surrogate keys)
EF query will fail if we compare encrypted column to a constant, instead need to pass constant argument as closure
EF can transparently replace constants in the query with parameters. This can be achieved using query interception extensibility features.
Sorting based on encrypted column is not supported on the database due to limitations of Always Encrypted feature. This need to be done on client side.
Some GroupBy operations are not supported (namely the LINQ specific grouping, without projecting group key or aggregate function) if entity key is encrypted. Reason is that those queries (that simply aggregate elements into IGrouping<,> statements) are translated into TSQL containing ORDER BY operation on the key. If the key is encrypted, the query will fail. You have to use unencrypted surrogate key, to let OrderBy work
Queries that project a collection don’t work with encrypted columns if the key (or any part of the composite key) is encrypted. The reason is same OrderBy not work for encrypted columns. Solution as previous omit encrypting columns that need server side sorting.
Similarly to the above case, Include operation performed on a collection is not supported if the PK of the principal entity is encrypted
Perhaps try and use a concrete class instead of a dynamic object. Dynamic objects create some overhead and if there's a lot of them I guess that could be what slows it down
Like so:
class Names
{
public string? FirstName { get; }
public string? LastName { get; }
public Names(string? firstName, string? lastName)
{
FirstName = firstName;
LastName = lastName;
}
}
var grid1 = mylist.Select(c =>
new Names(
Encryption.Decrypt5(c.FirstName),
Encryption.Decrypt5(c.LastName))
.ToList();
It also looks like you're unnecessarily checking if cipherText is null or white space twice
Related
For an application purpose, I want to store an complete content of the email (string) in the dictionary.
[I know that this is what every hash function provides but wanted to explicitly state that the hash for the same string should always be the same]
Since its not for cryptographic reason and only for storing in dictionary. Can any one please suggest a good hashing function that is available in .Net. My concern is that the email string can be pretty big and i want my hash function to support the big string and not cause frequent collision. I am looking for storing around 500 entries.
Please note i dont want to write my own hash funciton but leverage an existing availaible hash function in .Net
You may consider to use HashAlgorithm.ComputeHash.
Here is an example which is provided with this function:
using System;
using System.Security.Cryptography;
using System.Text;
public class Program
{
public static void Main()
{
string source = "Hello World!";
using (SHA256 sha256Hash = SHA256.Create())
{
string hash = GetHash(sha256Hash, source);
Console.WriteLine($"The SHA256 hash of {source} is: {hash}.");
Console.WriteLine("Verifying the hash...");
if (VerifyHash(sha256Hash, source, hash))
{
Console.WriteLine("The hashes are the same.");
}
else
{
Console.WriteLine("The hashes are not same.");
}
}
}
private static string GetHash(HashAlgorithm hashAlgorithm, string input)
{
// Convert the input string to a byte array and compute the hash.
byte[] data = hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes(input));
// Create a new Stringbuilder to collect the bytes
// and create a string.
var sBuilder = new StringBuilder();
// Loop through each byte of the hashed data
// and format each one as a hexadecimal string.
for (int i = 0; i < data.Length; i++)
{
sBuilder.Append(data[i].ToString("x2"));
}
// Return the hexadecimal string.
return sBuilder.ToString();
}
// Verify a hash against a string.
private static bool VerifyHash(HashAlgorithm hashAlgorithm, string input, string hash)
{
// Hash the input.
var hashOfInput = GetHash(hashAlgorithm, input);
// Create a StringComparer an compare the hashes.
StringComparer comparer = StringComparer.OrdinalIgnoreCase;
return comparer.Compare(hashOfInput, hash) == 0;
}
}
I hope it helps 😊
I'm trying to convert a couple of function from Java to c# using Portable.BouncyCastle and while there are plenty of example out there, I don't seem to be able to find one to match my requirements as most example seem to explain one specific encryption/decryption method while this function appears to be more generic. I could be wrong of course as I'm a complete newbie to this and don't have any experience in either BouncyCastle, Java or encryption, so please bear with me on this one.
The java function is:
public static byte[] Cipher(int mode, byte[] key,
byte[] data, string algorithm, AlgorithmParameterSpec spec)
{
Cipher cipher = Cipher.getInstance(algorithm);
SecretKeySpec keySpec = new SecretKeySpec(key, algorithm);
if (spec != null)
cipher.init(mode, keySpec, spec);
else
cipher.init(mode, keySpec);
return cipher.doFinal(data);
}
I found some code from BouncyCasle where I can match most of the functionality from what I can see:
byte[] K = Hex.Decode("404142434445464748494a4b4c4d4e4f");
byte[] N = Hex.Decode("10111213141516");
byte[] P = Hex.Decode("68656c6c6f20776f726c642121");
byte[] C = Hex.Decode("39264f148b54c456035de0a531c8344f46db12b388");
KeyParameter key = ParameterUtilities.CreateKeyParameter("AES", K);
IBufferedCipher inCipher = CipherUtilities.
GetCipher("AES/CCM/NoPadding");
inCipher.Init(true, new ParametersWithIV(key, N));
byte[] enc = inCipher.DoFinal(P);
1. SecretKeySpec:
SecretKeySpec keySpec = new SecretKeySpec(key, algorithm);
How do I create this using BC? Is that the equivalent of the SecretKeySpec:
KeyParameter key = ParameterUtilities.CreateKeyParameter("AES", K);
If it is, can I pass the "AES/CCM/NoPadding" instead of AES as it is done in Java?
2. spec parameter:
It passes parameters of type IvParameterSpec to the Cypher function when called from `Java` via the `AlgorithmParameterSpec spec` parameter:
Cipher(ENCRYPT_MODE, key, clearBytes,
algorithm,
new IvParameterSpec(iv))
`BouncyCastle` does not have an overloaded function for `.Init` to allow me to pass the spec parameter as it does in `Java`, so how do I mimic this behaviour?
3. IvParameterSpec: You can see that when cypher is called from java, it passes the AlgorithmParameterSpec spec as new IvParameterSpec(iv) but with BouncyCastle, it seems to be expecting a key?
ParametersWithIV(key, N)
Will that difference have any impact on the encryption/decryption?
This is current my attempt at "converting" this function:
public static byte[] Cipher(bool isEncrypt, byte[] key, byte[] data,
string algorithm, ICipherParameters spec)
{
IBufferedCipher cipher = CipherUtilities.GetCipher(algorithm);
KeyParameter keySpec = ParameterUtilities.
CreateKeyParameter(algorithm, key);
cipher.Init(isEncrypt, new ParametersWithIV(keySpec,
keySpec.GetKey()));
return cipher.DoFinal(data);
}
As you can see I've changed the spec parameter to ICipherParameters spec but I don't know if this will work as when using Bouncy, it looks like that when I create a new ParametersWithIV, it needs a key and from the test sample I provided above, that key is created using KeyParameter key = ParameterUtilities.CreateKeyParameter("AES", K); so technically won't work when trying to call my Cipher function as I will have called this function yet. Should I change spec parameter to iv and pass a byte[] instead?
Apologies if there is confusion or if things are not explained correctly but as I said, I'm new to this and trying to understand it better while also converting. I hope most of it makes sense and you'll be able to help.
Many Thanks.
PS: Note that I'm not in a position to test these in Java yet, but I will hopefully have an environment set up correctly in the new few days which will hopefully help testing values between .net & java.
UPDATE 1
Passing AES/CCM/NoPadding to:
KeyParameter key = ParameterUtilities.CreateKeyParameter
Throws an error, so this partially answers one of my questions. Is there a function in BouncyCastle to determine the correct value that is required i.e. AES when AES/CCM/NoPadding is passed?
Ended up using the code below as it appears to be working as expected but still annoyed I had to hardcode the AES as the key of IV parameter part. Ideally I would have liked this to have been based on my Algorithm. So, now I have a single function to encrypt and decrypt:
public static byte[] Cipher(bool forEncryption, byte[] key,
byte[] data, string algorithm, byte[] iv)
{
IBufferedCipher cipher = CipherUtilities.GetCipher(algorithm);
KeyParameter keySpec = ParameterUtilities.CreateKeyParameter("AES", key);
cipher.Init(forEncryption, new ParametersWithIV(keySpec, iv));
return cipher.DoFinal(data);
}
Hope this helps.
I want to generate a API key for new clients that want to use any of my API services.
because I'm using a open API service i don't want to use authentication only identify the client usage by the API key
I tried to use this code
public static string GetAPIKey()
{
string sig = string.Empty;
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
var ex = rsa.ExportParameters(true);
sig = Convert.ToBase64String(ex.DQ);
sig = sig
.Replace("+", "")
.Replace("/", "")
.TrimEnd('=');
}
return sig.Substring(0, 64);
}
In my tests i do get a random 64 length string, but something not feeling right with the method usage. proberly because of the RSACryptoServiceProvider usage, especially when i try to generate the DQ property
Do you know any better implementation of generating a random 64 string?
If you do not want to use a GUID you can use the standard Random class in C#.
private static readonly Random Random = new Random();
public static string GetApiKey()
{
var bytes = new byte[48];
Random.NextBytes(bytes);
var result = Convert.ToBase64String(bytes);
return result;
}
Since 48 bytes will map to 64 characters in Base64, this gives you 64 random characters. It does not guarantee uniqueness however.
Why not just use a GUID?
Use it twice to generate a 64 character string, which is completely random and unique.
From what I can tell, TLS 1.1 requires the contents of the CertificateVerify message to be a digitally signed value using the concatenation of two hash algorithms (MD5 and SHA1). Is this possible to do in .NET using the RSACryptoServiceProvider?
This does not work:
using (var rsa = new RSACryptoServiceProvider())
{
rsa.ImportParameters(...);
rsa.SignData(data, new MD5SHA1());
}
This also does not work:
using (var rsa = new RSACryptoServiceProvider())
{
rsa.ImportParameters(...);
rsa.SignHash(new MD5SHA1().ComputeHash(data), "MD5SHA1");
}
(MD5SHA1 is an implementation of HashAlgorithm.)
Presumably this does not work because the signature embeds the OID of the hash algorithm, and MD5-SHA1 does not have a valid OID. Is this possible in .NET? Am I misunderstanding TLS 1.1?
In case it helps anyone else, I used the BigInteger class to make this work. What is called "signing" in TLS 1.1 is really just private-key encryption, which can be done using BigInteger math.
Sign
var hash = new MD5SHA1().ComputeHash(data);
var input = new BigInteger(hash);
return input.ModPow(new BigInteger(privateExponent),
new BigInteger(modulus)).GetBytes();
Verify
var hash = new MD5SHA1().ComputeHash(data);
var input = new BigInteger(signature);
var output = input.ModPow(new BigInteger(publicExponent),
new BigInteger(modulus)).GetBytes();
var rehash = SubArray(output, output.Length - 36);
return SequencesAreEqual(hash, rehash);
Note that you still have to add padding yourself to the output. (0x0001FFFFFF...FF00{data})
Signing can be optimized using CRT parameters (p, q, etc.), but that's a problem for another day.
I am computing md5hash of files to check if identical so I wrote the following
private static byte[] GetMD5(string p)
{
FileStream fs = new FileStream(p, FileMode.Open);
HashAlgorithm alg = new HMACMD5();
byte[] hashValue = alg.ComputeHash(fs);
fs.Close();
return hashValue;
}
and to test if for the beginning I called it like
var v1 = GetMD5("C:\\test.mp4");
var v2 = GetMD5("C:\\test.mp4");
and from debugger I listed v1 and v2 values and they are different !! why is that ?
It's because you're using HMACMD5, a keyed hashing algorithm, which combines a key with the input to produce a hash value. When you create an HMACMD5 via it's default constructor, it will use a random key each time, therefore the hashes will always be different.
You need to use MD5:
private static byte[] GetMD5(string p)
{
using(var fs = new FileStream(p, FileMode.Open))
{
using(var alg = new MD5CryptoServiceProvider())
{
return alg.ComputeHash(fs);
}
}
}
I've changed the code to use usings as well.
From the HMACMD5 constructor doc:
HMACMD5 is a type of keyed hash algorithm that is constructed from the
MD5 hash function and used as a Hash-based Message Authentication Code
(HMAC). The HMAC process mixes a secret key with the message data,
hashes the result with the hash function, mixes that hash value with
the secret key again, and then applies the hash function a second
time. The output hash will be 128 bits in length.
With this constructor, a 64-byte, randomly generated key is used.
(Emphasis mine)
With every call to GetMD5(), you're generating a new random key.
You might want to use System.Security.Cryptography.MD5Cng
My guess is that you did something like:
Console.WriteLine(v1);
Console.WriteLine(v2);
or
Console.WriteLine(v1 == v2);
That just shows that the variable values refer to distinct arrays - it doesn't say anything about the values within those arrays.
Instead, try this (to print out the hex):
Console.WriteLine(BitConverter.ToString(v1));
Console.WriteLine(BitConverter.ToString(v2))
Use ToString() methode to get the value of the array byte