Container formats should have a version. Configuration options such assas iteration count should be stored with the ciphertext, or they should be linked to a specific version. Salts should be stored with each file; I'm hoping here that the salt differs for each file or the same keyset will be used for each file encryption.
Container formats should have a version. Configuration options such ass iteration count should be stored with the ciphertext, or they should be linked to a specific version. Salts should be stored with each file; I'm hoping here that the salt differs for each file or the same keyset will be used for each file encryption.
Container formats should have a version. Configuration options such as iteration count should be stored with the ciphertext, or they should be linked to a specific version. Salts should be stored with each file; I'm hoping here that the salt differs for each file or the same keyset will be used for each file encryption.
The code is not mature and should not be used in production. It doesn't adhere to cryptography best practices, even though the code is clearly written with knowledge of quite a few of these principles.
Cryptographers generally have high confidence in most cipher algorithms such as AES, so this class doesn't seem needed. Using password based encryption is much more of a concern. Using hybrid encryption with a classic and PQC cipher is a much better idea, as you don't even need the private key during encryption.
Finally, the library is very memory inefficient.
General issues
The protocol isn't specified. It is unclear to the reader of the documentation how the encryption takes place, which algorithms are used and when etc.
This assumes that the file is filled with text, but this is not mentioned anywhere. Usually files are encrypted as binary.
It doesn't make much sense to load the entire file into a string in memory, then to convert it to bytes in memory, and then to store the ciphertext in memory too. This thing will create new arrays for each ciphertext, and then also forgets to wipe the intermediate arrays. The keys are also not directly destroyed before the next run.
The plaintext bytes are wiped, but as you just loaded the file into a string, the plaintext will still be in memory after encryption, until it gets garbage collected - if ever.
EncryptFile and DecryptFile do not encrypt the file that is passed in, instead they return the ciphertext and plaintext respectively.
public static int BoundedInt(int min, int max) This will generally return a biased number as lower values in the range will be more prominent.
The entire shuffle idea doesn't add security, only complexity. Key derivation already produces random results. At the worst, an attacker can use the shuffle method to wait for a configuration that is beneficial to attack.
Container formats should have a version. Configuration options such ass iteration count should be stored with the ciphertext, or they should be linked to a specific version. Salts should be stored with each file; I'm hoping here that the salt differs for each file or the same keyset will be used for each file encryption.
Compression is a well known side channel. Leaking the size of the plaintext can also be a side channel of course and can give a lot of info on what is stored, but adding compression generally makes this worse as the resulting size is now also content-specific.
Minor notes
username and password are single words, so no need to use camelCase for them.
if (fileBytes == null || fileBytes.Length == 0 || salt == null || salt.Length == 0) throw new ArgumentException("Value was empty."); This doesn't show what value was empty. And empty files are just "normal" files. There is no specific need to treat them differently.
Constants are not all UPPERCASE. Hash and SecurePassword (this time Password is a full word I guess) etc. are not constants.
It doesn't make much sense to store the RndNum in a static field, creating these kind of objects is generally lightweight.
It doesn't make much sense to group all constants, hash methods etc. together. Code should be grouped by functionality, and the constants should be included with the code that does encryption or message authentication. The password hashing can also be replaced by e.g. public key encryption so it should be in a separate class.
Encryption using a cipher uses the generic Engine construct is always following the same idea. It is possible to simply create a single interface for encryption. If you need to use the Cipher construct then just create a small adapter so that you can use the same interface.
The good things
You're using Aes.Create() rather than the constructor, which will use an optimized AES when used.
Cipher's implementations are only instantiated when needed. Cipher and engine instances are relatively lightweight, so this makes more sense than having them as fields.
In principle encrypting with multiple algorithms can add some security in case any of the algorithms is broken. It's a bit overkill and of course it is much more likely that e.g. a bad password kills security, but in principle it does work.
Note that you're not quadrupling key size as meet-in-the-middle attacks are theoretically possible. This is not a problem, you don't need that large a key size anyway and the memory requirements would be infeasible. So this is OK as long as long as you don't put down bad claims.
The keys are derived using a modern (PB)KDF. Furthermore, HMAC is used for authentication, and that's a well proven (if relatively slow) method of authenticating ciphertext.
Ciphertext is authenticated rather than the plaintext, encrypt-then-MAC is the way to go. The IV is indeed included within the HMAC calculation.
Code is relatively easy to read and most identifiers are reasonable. Guard statements are also provided, maybe even overly so (if some methods are made private then the checking would not be necessary, and most people can do without a wrapper for EncryptThreeFish fine).
Hints
If you have a set of keys then create a data class around it. That way you can be sure that the state is captured correctly and all this exception handling is not required anymore. This can also have a clear method etc. It also allows separate unit testing of the class. And it saves a crapload of typing out documentation when done correctly.
One of the hints of the .NET cryptography API is that they are using streams. That should have been a hint on how to best perform encryption / decryption. For instance, it would be an idea to "stack streams" and perform encryption / decryption directly. You can always put the result in a MemoryArray or write it to a temporary file which can be discarded if the authentication tag calculation fail.
If you do want to store things in memory, then the input and output array can usually be the same (as long as the input isn't overwritten before it is read) for ciphers. It is possible to rewrite this using a single array used for both the plaintext and the intermediate ciphertexts.
Try and work without global variables. You can create a class that simply gets a configuration without static variables fine, so instances can be configured separately. This could be helpful when settings change in time.
The part where the ciphertext is written to file seems to be missing. However, I'd warn you that most filesystems do not allow atomic actions on files. If the file writing part is interrupted then you might end up with a file that contains partial bytes. The authentication of these files will fail, and you will have no way of knowing why. Storing a size / checksum at the start of the file may alleviate that issue.