Cryptography is vital to security for obvious reasons. Encryption can be used to protect data confidentiality, hashing can be used to protect integrity by making it possible to detect tampering, and digital signatures can be used for authentication. Cryptography is typically used to protect data in transit or in storage. The two biggest mistakes that developers can make related to cryptography are: using homegrown cryptographic solutions and not properly securing encryption keys. Developers need to pay special attention to the following issues in order for cryptography to be effective: using cryptographic services provided by the platform, secure key generation, secure key storage, secure key exchange, and secure key maintenance.
It is never a good idea for developers to use custom security solutions since they are almost guaranteed to be weaker than the industry standard. Instead, managed code developers should use algorithms provided by the System.Security.Cryptography
namespace for encryption, decryption, hashing, random number generation, and digital signatures. We have already previously discussed the System.Security.Cryptography namespace. Many of the types in this namespace actually wrap around the CryptoAPI provided by the operated system.
During the key generation phase, developers must make sure that the keys generated are truly random, that PasswordDeriveBytes is used for password encryption and that the key sizes are sufficiently large. For programmatic generation of encryption keys,
RNGCryptoServiceProvicer should be used for key creation and initialization vectors. Random class should never be used because it does not provide sufficient entropy and produces a reliably identical stream of random numbers for a given seed. Thus with the same seed, the random number stream is known when using the Random class. On the other hand, RNGCryptoServiceProvider creates cryptographically strong random numbers that are FIPS-140 compliant. The code below demonstrates how secure keys can be generated with RNGCryptoServiceProvider.
using System.Security.Cryptography; . . .
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); byte[] key = new byte[keySize];
rng.GetBytes(key);
System.Security.Cryptography.DeriveByte namespace can be used to encrypt data that is based on a password that the user supplies. This can be accomplished via
PasswordDeriveBytes methods. The reason that we need to have a separate way to encrypt data on user supplied input (like password) is because user input does not tend to be truly random and will not have the same level of randomness as a key generated with RNGCryptoServiceProvider. In order to perform decryption, the user would have to provide the same password as that which was used to encrypt. For password
authentication, a password verifier may be stored as a hash value with a salt value. PasswordDeriveBytes takes in password, salt, encryption algorithm, hashing algorithm, key size in bits, and initialization vector data (if symmetric key algorithm is used) as arguments. After the key is used to encrypt the data, it should be cleared from memory, but salt and initialization vector should be stored securely, since they are needed for decryption.
Large keys are preferable to small keys since security is in the key. When generating encryption keys or key pairs, the largest possible key size should be used that a given algorithm would accommodate. Sometimes smaller key sizes may be necessary for different reasons (such as performance for instance), so the key size may be a judgment call made by the application designer or the developer. Larger keys do not enhance the security of the algorithm itself, but increase the amount of time it would take to perform a brute force attack on the key. The code snippet below demonstrates a way in which the largest supported key size for a given encryption algorithm can be found.
private int GetLargestSymKeySize(SymmetricAlgorithm symAlg) {
return sizes[sizes.Length].MaxSize; }
private int GetLargestAsymKeySize(AsymmetricAlgorithm asymAlg) {
KeySizes[] sizes = asymAlg.LegalKeySizes; return sizes[sizes.Length].MaxSize;
}
Ideally key management should be performed by a platform provided solution and not programmatically as part of the application. However, when encryption keys need to be stored, it is imperative to do so securely by storing then in a secure location. Microsoft recommends using DPAPI, a native encryption/decryption feature provided by Microsoft, Windows 2000 for key management. With DPAPI, the encryption key is managed by the operating system because it is created from the password that is associated with the process account calling the DPAPI functions. Encryption with DPAPI can be performed using a user key or a machine key. User key is the default, meaning that only a threat that runs under the security context of the user account that encrypted the data can decrypt the data. Alternatively, DPAPI can use the machine key. This can be accomplished by passing the CRYPOPROTECT_LOCAL_MACHINE flaw to the CryptProtectData API and then any user on the current machine can decrypt the data. The user key option requires a loaded user profile in the account used to perform the encryption. This somewhat limits portability, and so machine key should be used where portability is required. If machine key option is used, an ACL is required to secure the encrypted data. An optional entropy value can be passed to DPAPI if added security is desired. Of course then the entropy value has to also be managed. Alternatively, machine key can be used without an entropy value and then code access security can be used to validate users and code prior to calling the DPAPI code.
An axiom of key management states that keys should never be stored in code. Hard coded keys in the compiled assembly (MSIL) can be easily disassembled which would eliminate any benefits provided by cryptography. Additionally, access to stored keys should obviously be limited. Appropriate ACLs should be used to limit access to the key when keys are stored in a persistent storage to be used as the application is running. Access to the key should only be allowed to Administrators, SYSTEM, and the identity of the code at runtime (e.g. ASPNET identity). When backing up keys, they should be encrypted with DPAPI or a strong password and placed on removable media.
Key exchange is traditionally a hard problem to solve in any cryptosystem. The most widely used solution is usage of PKI to distribute symmetric keys. A symmetric key that needs to be exchanged is encrypted with the other party’s public key that is obtained from a certificate that is valid. Valid certificates are not outdated, contain verifiable signatures along the certificate chain, are of correct type, verified up to a trusted certificate authority, and are no in the Certificate Revocation List (CLR) of the issuer. If an enterprise web application needs to engage in key exchange, assembly code must perform all of the above steps in order to perform secure key exchange. Sometimes it is hard for developers to remember to get all of the steps right, particularly since developers are usually not security professionals. A common problem is with the use of SSL, where
proper authentication is not performed prior to communication. The net effect is secure communication with unauthenticated party.
Finally, keys must be securely maintained, which usually involves replacing the keys periodically and protecting exported private keys. Using the same key for a prolonged period of time is not a good strategy. It is also possible for keys to become compromised, either through theft, loss, or other methods. If the private key is compromised that is used for key exchange, users of the public key should be immediately notified that the key has been compromised. All documents digitally signed with the compromised private key need to be re-signed. If the private key that is used for certificate is compromised, the CA should be notified so that the certificate can be place on the CRL and key storage should be reevaluated. Exported private keys should be protected. PasswordDeriveBytes can be used to securely export RSA or DSA private keys. ToXmlString method in RSA and DSA classes can be used to export the public or private key (or both) from the key container, but it exports the keys in plain text. In order to export a private key securely, the key should be encrypted with PasswordDeriveBytes after exporting the key. The code snippet below shows how to use PasswordDeriveBytes to generate a symmetric key securely.
PasswordDeriveBytes deriver = new PasswordDeriveBytes(<strong password>, null); byte[] ivZeros = new byte[8];//This is not actually used but is currently
required.
//Derive key from the password
byte[] pbeKey = deriver.CryptDeriveKey("TripleDES", "SHA1", 192, ivZeros);