Section150.1:ModernExamplesofSymmetricAuthenticated Encryption of a string
Cryptographyissomethingveryhardandafterspendingalotoftimereadingdifferentexamplesandseeinghow easyitistointroducesomeformofvulnerabilityIfoundanansweroriginallywrittenby@jbtulethatIthinkisvery good. Enjoy reading:
“The general best practice for symmetric encryption is to use Authenticated Encryption with Associated Data (AEAD),howeverthisisn’tapartofthestandard.netcryptolibraries.SothefirstexampleusesAES256andthen HMAC256, a two step Encrypt then MAC, which requires more overhead and more keys.
ThesecondexampleusesthesimplerpracticeofAES256-GCMusingtheopensourceBouncyCastle(vianuget).
Bothexampleshaveamainfunctionthattakessecretmessagestring,key(s)andanoptionalnon-secretpayload and return and authenticated encrypted string optionally prepended with the non-secret data. Ideally you would use these with 256bit key(s) randomly generated see NewKey().
Bothexamplesalsohaveahelpermethodsthatuseastringpasswordtogeneratethekeys.Thesehelpermethods are provided as a convenience to match up with other examples, however they are farlesssecurebecause the strength of the password is going to be far weaker than a 256 bit key.
Update:Addedbyte[]overloads,andonlytheGisthasthefullformattingwith4spacesindentandapidocsdueto StackOverflow answer limits.”
.NETBuilt-inEncrypt(AES)-Then-MAC(HMAC)[Gist]
/*
* Thiswork(ModernEncryptionofaStringC#,byJamesTuley),
* identifiedbyJamesTuley,isfreeofknowncopyrightrestrictions.
* https://gist.github.com/4336842
* http://creativecommons.org/publicdomain/mark/1.0/
*/
usingSystem;
usingSystem.IO;
usingSystem.Security.Cryptography;
usingSystem.Text;
namespaceEncryption
{
publicstaticclassAESThenHMAC
{
privatestaticreadonlyRandomNumberGeneratorRandom=RandomNumberGenerator.Create();
//PreconfiguredEncryptionParameters
publicstaticreadonlyintBlockBitSize=128;
publicstaticreadonlyintKeyBitSize=256;
//PreconfiguredPasswordKeyDerivationParameters
publicstaticreadonlyintSaltBitSize=64;
publicstaticreadonlyintIterations=10000;
publicstaticreadonlyintMinPasswordLength=12;
///<summary>
///Helperthatgeneratesarandomkeyoneachcall.
///</summary>
///<returns></returns>
publicstaticbyte[]NewKey()
{
varkey=newbyte[KeyBitSize/8];
Random.GetBytes(key);
returnkey;
}
///<summary>
///SimpleEncryption(AES)thenAuthentication(HMAC)foraUTF8Message.
///</summary>
///<paramname=”secretMessage”>Thesecretmessage.</param>
///<paramname=”cryptKey”>Thecryptkey.</param>
///<paramname=”authKey”>Theauthkey.</param>
///<paramname=”nonSecretPayload”>(Optional)Non-SecretPayload.</param>
///<returns>
///EncryptedMessage
///</returns>
///<exceptioncref=”System.ArgumentException”>SecretMessageRequired!;secretMessage</exception>
///<remarks>
///Addsoverheadof(Optional-Payload+BlockSize(16)+Message-Padded-To-Blocksize+ HMac-Tag(32))*1.33Base64
///</remarks>
publicstaticstringSimpleEncrypt(stringsecretMessage,byte[]cryptKey,byte[]authKey, byte[]nonSecretPayload=null)
{
if(string.IsNullOrEmpty(secretMessage))
thrownewArgumentException(“SecretMessageRequired!”,”secretMessage”);
varplainText=Encoding.UTF8.GetBytes(secretMessage);
varcipherText=SimpleEncrypt(plainText,cryptKey,authKey,nonSecretPayload); returnConvert.ToBase64String(cipherText);
}
///<summary>
///SimpleAuthentication(HMAC)thenDecryption(AES)forasecretsUTF8Message.
///</summary>
///<paramname=”encryptedMessage”>Theencryptedmessage.</param>
///<paramname=”cryptKey”>Thecryptkey.</param>
///<paramname=”authKey”>Theauthkey.</param>
///<paramname=”nonSecretPayloadLength”>Lengthofthenonsecretpayload.</param>
///<returns>
///DecryptedMessage
///</returns>
///<exceptioncref=”System.ArgumentException”>EncryptedMessage Required!;encryptedMessage</exception>
publicstaticstringSimpleDecrypt(stringencryptedMessage,byte[]cryptKey,byte[]authKey, intnonSecretPayloadLength=0)
{
if(string.IsNullOrWhiteSpace(encryptedMessage))
thrownewArgumentException(“EncryptedMessageRequired!”,”encryptedMessage”);
varcipherText=Convert.FromBase64String(encryptedMessage);
varplainText=SimpleDecrypt(cipherText,cryptKey,authKey,nonSecretPayloadLength); returnplainText==null?null:Encoding.UTF8.GetString(plainText);
}
///<summary>
///SimpleEncryption(AES)thenAuthentication(HMAC)ofaUTF8message
///usingKeysderivedfromaPassword(PBKDF2).
///</summary>
///<paramname=”secretMessage”>Thesecretmessage.</param>
///<paramname=”password”>Thepassword.</param>
///<paramname=”nonSecretPayload”>Thenonsecretpayload.</param>
///<returns>
///EncryptedMessage
///</returns>
///<exceptioncref=”System.ArgumentException”>password</exception>
///<remarks>
///Significantlylesssecurethanusingrandombinarykeys.
///Addsadditionalnonsecretpayloadforkeygenerationparameters.
///</remarks>
publicstaticstringSimpleEncryptWithPassword(stringsecretMessage,stringpassword, byte[]nonSecretPayload=null)
{
if(string.IsNullOrEmpty(secretMessage))
thrownewArgumentException(“SecretMessageRequired!”,”secretMessage”);
varplainText=Encoding.UTF8.GetBytes(secretMessage);
varcipherText=SimpleEncryptWithPassword(plainText,password,nonSecretPayload); returnConvert.ToBase64String(cipherText);
}
///<summary>
///SimpleAuthentication(HMAC)andthenDescryption(AES)ofaUTF8Message
///usingkeysderivedfromapassword(PBKDF2).
///</summary>
///<paramname=”encryptedMessage”>Theencryptedmessage.</param>
///<paramname=”password”>Thepassword.</param>
///<paramname=”nonSecretPayloadLength”>Lengthofthenonsecretpayload.</param>
///<returns>
///DecryptedMessage
///</returns>
///<exceptioncref=”System.ArgumentException”>EncryptedMessage Required!;encryptedMessage</exception>
///<remarks>
///Significantlylesssecurethanusingrandombinarykeys.
///</remarks>
publicstaticstringSimpleDecryptWithPassword(stringencryptedMessage,stringpassword, intnonSecretPayloadLength=0)
{
if(string.IsNullOrWhiteSpace(encryptedMessage))
thrownewArgumentException(“EncryptedMessageRequired!”,”encryptedMessage”);
varcipherText=Convert.FromBase64String(encryptedMessage);
varplainText=SimpleDecryptWithPassword(cipherText,password,nonSecretPayloadLength); returnplainText==null?null:Encoding.UTF8.GetString(plainText);
}
publicstaticbyte[]SimpleEncrypt(byte[]secretMessage,byte[]cryptKey,byte[]authKey, byte[]nonSecretPayload=null)
{
//UserErrorChecks
if(cryptKey==null||cryptKey.Length!=KeyBitSize/8)
thrownewArgumentException(String.Format(“Keyneedstobe{0}bit!”,KeyBitSize), “
cryptKey”);
if(authKey==null||authKey.Length!=KeyBitSize/8)
thrownewArgumentException(String.Format(“Keyneedstobe{0}bit!”,KeyBitSize), “
authKey”);
if(secretMessage==null||secretMessage.Length<1)
thrownewArgumentException(“SecretMessageRequired!”,”secretMessage”);
//non-secretpayloadoptional
nonSecretPayload=nonSecretPayload??newbyte[]{};
byte[]cipherText;
byte[]iv;
using(varaes=newAesManaged
{
KeySize=KeyBitSize,
BlockSize=BlockBitSize,Mode=Ci
pherMode.CBC,
Padding=PaddingMode.PKCS7
})
{
//UserandomIV
aes.GenerateIV();
iv=aes.IV;
using(varencrypter=aes.CreateEncryptor(cryptKey,iv))
using(varcipherStream=newMemoryStream())
{
using(varcryptoStream=newCryptoStream(cipherStream,encrypter, CryptoStreamMode.Write))
using(varbinaryWriter=newBinaryWriter(cryptoStream))
{
//EncryptData
binaryWriter.Write(secretMessage);
}
cipherText=cipherStream.ToArray();
}
}
//Assembleencryptedmessageandaddauthentication
using(varhmac=newHMACSHA256(authKey))
using(varencryptedStream=newMemoryStream())
{
using(varbinaryWriter=newBinaryWriter(encryptedStream))
{
//Prependnon-secretpayloadifany
binaryWriter.Write(nonSecretPayload);
//PrependIV
binaryWriter.Write(iv);
//WriteCiphertext
binaryWriter.Write(cipherText);
binaryWriter.Flush();
//Authenticatealldata
vartag=hmac.ComputeHash(encryptedStream.ToArray());
//Postpendtag
binaryWriter.Write(tag);
}
returnencryptedStream.ToArray();
}
}
publicstaticbyte[]SimpleDecrypt(byte[]encryptedMessage,byte[]cryptKey,byte[]authKey, intnonSecretPayloadLength=0)
{
//BasicUsageErrorChecks
if(cryptKey==null||cryptKey.Length!=KeyBitSize/8)
thrownewArgumentException(String.Format(“CryptKeyneedstobe{0}bit!”,KeyBitSize), “cryptKey”);
if(authKey==null||authKey.Length!=KeyBitSize/8)
thrownewArgumentException(String.Format(“AuthKeyneedstobe{0}bit!”,KeyBitSize), “authKey”);
if(encryptedMessage==null||encryptedMessage.Length==0)
thrownewArgumentException(“EncryptedMessageRequired!”,”encryptedMessage”);
using(varhmac=newHMACSHA256(authKey))
{
varsentTag=newbyte[hmac.HashSize/8];
//CalculateTag
varcalcTag=hmac.ComputeHash(encryptedMessage,0,encryptedMessage.Length- sentTag.Length);
varivLength=(BlockBitSize/8);
//ifmessagelengthistosmalljustreturnnull
if(encryptedMessage.Length<sentTag.Length+nonSecretPayloadLength+ivLength) return null;
//GrabSentTag
Array.Copy(encryptedMessage,encryptedMessage.Length-sentTag.Length,sentTag,0, sentTag.Length);
//CompareTagwithconstanttimecomparison
varcompare=0;
for(vari=0;i<sentTag.Length;i++)
compare|=sentTag[i]^calcTag[i];
//ifmessagedoesn’tauthenticatereturnnull
if(compare!=0)
returnnull;
using(varaes=newAesManaged
{
KeySize=KeyBitSize,
BlockSize=BlockBitSize,Mode=Ci
pherMode.CBC,
Padding=PaddingMode.PKCS7
})
{
//GrabIVfrommessage
variv=newbyte[ivLength];
Array.Copy(encryptedMessage,nonSecretPayloadLength,iv,0,iv.Length);
using(vardecrypter=aes.CreateDecryptor(cryptKey,iv))
using(varplainTextStream=newMemoryStream())
{
using(vardecrypterStream=newCryptoStream(plainTextStream,decrypter, CryptoStreamMode.Write))
using(varbinaryWriter=newBinaryWriter(decrypterStream))
{
//DecryptCipherTextfromMessage
binaryWriter.Write(encryptedMessage,
nonSecretPayloadLength+iv.Length,
encryptedMessage.Length-nonSecretPayloadLength-iv.Length-sentTag.Length
);
}
//ReturnPlainText
returnplainTextStream.ToArray();
}
}
}
}
publicstaticbyte[]SimpleEncryptWithPassword(byte[]secretMessage,stringpassword,byte[] nonSecretPayload=null)
{
nonSecretPayload=nonSecretPayload??newbyte[]{};
//UserErrorChecks
if(string.IsNullOrWhiteSpace(password)||password.Length<MinPasswordLength) thrownewArgumentException(String.Format(“Musthaveapasswordofatleast{0}
characters!”,MinPasswordLength),”password”);
if(secretMessage==null||secretMessage.Length==0)
thrownewArgumentException(“SecretMessageRequired!”,”secretMessage”); varpayload=newbyte[((SaltBitSize/8)*2)+nonSecretPayload.Length];
Array.Copy(nonSecretPayload,payload,nonSecretPayload.Length); intpayloadIndex=nonSecretPayload.Length;
byte[]cryptKey;
byte[]authKey;
//UseRandomSalttopreventpre-generatedweakpasswordattacks.
using(vargenerator=newRfc2898DeriveBytes(password,SaltBitSize/8,Iterations))
{
varsalt=generator.Salt;
//GenerateKeys
cryptKey=generator.GetBytes(KeyBitSize/8);
//CreateNonSecretPayload
Array.Copy(salt,0,payload,payloadIndex,salt.Length);
payloadIndex+=salt.Length;
}
//Derivingseparatekey,mightbelessefficientthanusingHKDF,
//butnowcompatiblewithRNEncryptorwhichhadaverysimilarwireformatandrequiresless codethanHKDF.
using(vargenerator=newRfc2898DeriveBytes(password,SaltBitSize/8,Iterations))
{
varsalt=generator.Salt;
//GenerateKeys
authKey=generator.GetBytes(KeyBitSize/8);
//CreateRestofNonSecretPayload
Array.Copy(salt,0,payload,payloadIndex,salt.Length);
}
returnSimpleEncrypt(secretMessage,cryptKey,authKey,payload);
}
publicstaticbyte[]SimpleDecryptWithPassword(byte[]encryptedMessage,stringpassword,int nonSecretPayloadLength=0)
{
//UserErrorChecks
if(string.IsNullOrWhiteSpace(password)||password.Length<MinPasswordLength) thrownewArgumentException(String.Format(“Musthaveapasswordofatleast{0}
characters!”,MinPasswordLength),”password”);
if(encryptedMessage==null||encryptedMessage.Length==0)
thrownewArgumentException(“EncryptedMessageRequired!”,”encryptedMessage”);
varcryptSalt=newbyte[SaltBitSize/8];
varauthSalt=newbyte[SaltBitSize/8];
//GrabSaltfromNon-SecretPayload
Array.Copy(encryptedMessage,nonSecretPayloadLength,cryptSalt,0,cryptSalt.Length); Array.Copy(encryptedMessage,nonSecretPayloadLength+cryptSalt.Length,authSalt,0,
authSalt.Length);
byte[]cryptKey;
byte[]authKey;
//Generatecryptkey
using(vargenerator=newRfc2898DeriveBytes(password,cryptSalt,Iterations))
{
cryptKey=generator.GetBytes(KeyBitSize/8);
}
//Generateauthkey
using(vargenerator=newRfc2898DeriveBytes(password,authSalt,Iterations))
{
authKey=generator.GetBytes(KeyBitSize/8);
}
returnSimpleDecrypt(encryptedMessage,cryptKey,authKey,cryptSalt.Length+authSalt.Length
+nonSecretPayloadLength);
}
}
}
BouncyCastleAES-GCM[Gist]
/*
* Thiswork(ModernEncryptionofaStringC#,byJamesTuley),
* identifiedbyJamesTuley,isfreeofknowncopyrightrestrictions.
* https://gist.github.com/4336842
* http://creativecommons.org/publicdomain/mark/1.0/
*/
usingSystem;
usingSystem.IO;
usingSystem.Text;
usingOrg.BouncyCastle.Crypto;
usingOrg.BouncyCastle.Crypto.Engines;
usingOrg.BouncyCastle.Crypto.Generators;
usingOrg.BouncyCastle.Crypto.Modes;
usingOrg.BouncyCastle.Crypto.Parameters;
usingOrg.BouncyCastle.Security;
namespaceEncryption
{
publicstaticclassAESGCM
{
privatestaticreadonlySecureRandomRandom=newSecureRandom();
//PreconfiguredEncryptionParameters
publicstaticreadonlyintNonceBitSize=128;
publicstaticreadonlyintMacBitSize=128;
publicstaticreadonlyintKeyBitSize=256;
//PreconfiguredPasswordKeyDerivationParameters
publicstaticreadonlyintSaltBitSize=128;
publicstaticreadonlyintIterations=10000;
publicstaticreadonlyintMinPasswordLength=12;
///<summary>
///Helperthatgeneratesarandomnewkeyoneachcall.
///</summary>
///<returns></returns>
publicstaticbyte[]NewKey()
{
varkey=newbyte[KeyBitSize/8];
Random.NextBytes(key);
returnkey;
}
///<summary>
///SimpleEncryptionAndAuthentication(AES-GCM)ofaUTF8string.
///</summary>
///<paramname=”secretMessage”>Thesecretmessage.</param>
///<paramname=”key”>Thekey.</param>
///<paramname=”nonSecretPayload”>Optionalnon-secretpayload.</param>
///<returns>
///EncryptedMessage
///</returns>
///<exceptioncref=”System.ArgumentException”>SecretMessageRequired!;secretMessage</exception>
///<remarks>
///Addsoverheadof(Optional-Payload+BlockSize(16)+Message+ HMac-Tag(16))*1.33Base64
///</remarks>
publicstaticstringSimpleEncrypt(stringsecretMessage,byte[]key,byte[]nonSecretPayload= null)
{
if(string.IsNullOrEmpty(secretMessage))
thrownewArgumentException(“SecretMessageRequired!”,”secretMessage”);
varplainText=Encoding.UTF8.GetBytes(secretMessage);
varcipherText=SimpleEncrypt(plainText,key,nonSecretPayload);
returnConvert.ToBase64String(cipherText);
}
///<summary>
///SimpleDecryption&Authentication(AES-GCM)ofaUTF8Message
///</summary>
///<paramname=”encryptedMessage”>Theencryptedmessage.</param>
///<paramname=”key”>Thekey.</param>
///<paramname=”nonSecretPayloadLength”>Lengthoftheoptionalnon-secretpayload.</param>
///<returns>DecryptedMessage</returns>
publicstaticstringSimpleDecrypt(stringencryptedMessage,byte[]key,intnonSecretPayloadLength=0)
{
if(string.IsNullOrEmpty(encryptedMessage))
thrownewArgumentException(“EncryptedMessageRequired!”,”encryptedMessage”);
varcipherText=Convert.FromBase64String(encryptedMessage);
varplainText=SimpleDecrypt(cipherText,key,nonSecretPayloadLength);
returnplainText==null?null:Encoding.UTF8.GetString(plainText);
}
///<summary>
///SimpleEncryptionAndAuthentication(AES-GCM)ofaUTF8String
///usingkeyderivedfromapassword(PBKDF2).
///</summary>
///<paramname=”secretMessage”>Thesecretmessage.</param>
///<paramname=”password”>Thepassword.</param>
///<paramname=”nonSecretPayload”>Thenonsecretpayload.</param>
///<returns>
///EncryptedMessage
///</returns>
///<remarks>
///Significantlylesssecurethanusingrandombinarykeys.
///Addsadditionalnonsecretpayloadforkeygenerationparameters.
///</remarks>
publicstaticstringSimpleEncryptWithPassword(stringsecretMessage,stringpassword, byte[]nonSecretPayload=null)
{
if(string.IsNullOrEmpty(secretMessage))
thrownewArgumentException(“SecretMessageRequired!”,”secretMessage”);
varplainText=Encoding.UTF8.GetBytes(secretMessage);
varcipherText=SimpleEncryptWithPassword(plainText,password,nonSecretPayload); returnConvert.ToBase64String(cipherText);
}
///<summary>
///SimpleDecryptionandAuthentication(AES-GCM)ofaUTF8message
///usingakeyderivedfromapassword(PBKDF2)
///</summary>
///<paramname=”encryptedMessage”>Theencryptedmessage.</param>
///<paramname=”password”>Thepassword.</param>
///<paramname=”nonSecretPayloadLength”>Lengthofthenonsecretpayload.</param>
///<returns>
///DecryptedMessage
///</returns>
///<exceptioncref=”System.ArgumentException”>EncryptedMessage Required!;encryptedMessage</exception>
///<remarks>
///Significantlylesssecurethanusingrandombinarykeys.
///</remarks>
publicstaticstringSimpleDecryptWithPassword(stringencryptedMessage,stringpassword, intnonSecretPayloadLength=0)
{
if(string.IsNullOrWhiteSpace(encryptedMessage))
thrownewArgumentException(“EncryptedMessageRequired!”,”encryptedMessage”);
varcipherText=Convert.FromBase64String(encryptedMessage);
varplainText=SimpleDecryptWithPassword(cipherText,password,nonSecretPayloadLength); returnplainText==null?null:Encoding.UTF8.GetString(plainText);
}
publicstaticbyte[]SimpleEncrypt(byte[]secretMessage,byte[]key,byte[]nonSecretPayload= null)
{
//UserErrorChecks
if(key==null||key.Length!=KeyBitSize/8)
thrownewArgumentException(String.Format(“Keyneedstobe{0}bit!”,KeyBitSize),”key”);
if(secretMessage==null||secretMessage.Length==0)
thrownewArgumentException(“SecretMessageRequired!”,”secretMessage”);
//Non-secretPayloadOptional
nonSecretPayload=nonSecretPayload??newbyte[]{};
//Usingrandomnoncelargeenoughnottorepeat
varnonce=newbyte[NonceBitSize/8];
Random.NextBytes(nonce,0,nonce.Length);
varcipher=newGcmBlockCipher(newAesFastEngine());
varparameters=newAeadParameters(newKeyParameter(key),MacBitSize,nonce, nonSecretPayload);
cipher.Init(true,parameters);
//GenerateCipherTextWithAuthTag
varcipherText=newbyte[cipher.GetOutputSize(secretMessage.Length)];
varlen=cipher.ProcessBytes(secretMessage,0,secretMessage.Length,cipherText,0); cipher.DoFinal(cipherText, len);
//AssembleMessage
using(varcombinedStream=newMemoryStream())
{
using(varbinaryWriter=newBinaryWriter(combinedStream))
{
//PrependAuthenticatedPayload
binaryWriter.Write(nonSecretPayload);
//PrependNonce
binaryWriter.Write(nonce);
//WriteCipherText
binaryWriter.Write(cipherText);
}
returncombinedStream.ToArray();
}
}
publicstaticbyte[]SimpleDecrypt(byte[]encryptedMessage,byte[]key,int nonSecretPayloadLength=0)
{
//UserErrorChecks
if(key==null||key.Length!=KeyBitSize/8)
thrownewArgumentException(String.Format(“Keyneedstobe{0}bit!”,KeyBitSize),”key”);
if(encryptedMessage==null||encryptedMessage.Length==0)
thrownewArgumentException(“EncryptedMessageRequired!”,”encryptedMessage”);
using(varcipherStream=newMemoryStream(encryptedMessage)) using(varcipherReader=newBinaryReader(cipherStream))
{
//GrabPayload
varnonSecretPayload=cipherReader.ReadBytes(nonSecretPayloadLength);
//GrabNonce
varnonce=cipherReader.ReadBytes(NonceBitSize/8);
varcipher=newGcmBlockCipher(newAesFastEngine());
varparameters=newAeadParameters(newKeyParameter(key),MacBitSize,nonce, nonSecretPayload);
cipher.Init(false,parameters);
//DecryptCipherText
varcipherText=cipherReader.ReadBytes(encryptedMessage.Length-nonSecretPayloadLength- nonce.Length);
varplainText=newbyte[cipher.GetOutputSize(cipherText.Length)];
try
{
varlen=cipher.ProcessBytes(cipherText,0,cipherText.Length,plainText,0); cipher.DoFinal(plainText, len);
}
catch(InvalidCipherTextException)
{
//Returnnullifitdoesn’tauthenticate
returnnull;
}
returnplainText;
}
}
publicstaticbyte[]SimpleEncryptWithPassword(byte[]secretMessage,stringpassword,byte[] nonSecretPayload=null)
{
nonSecretPayload=nonSecretPayload??newbyte[]{};
//UserErrorChecks
if(string.IsNullOrWhiteSpace(password)||password.Length<MinPasswordLength) thrownewArgumentException(String.Format(“Musthaveapasswordofatleast{0}
characters!”,MinPasswordLength),”password”);
if(secretMessage==null||secretMessage.Length==0)
thrownewArgumentException(“SecretMessageRequired!”,”secretMessage”); vargenerator=newPkcs5S2ParametersGenerator();
//UseRandomSalttominimizepre-generatedweakpasswordattacks.
varsalt=newbyte[SaltBitSize/8]; Random.NextBytes(salt);
generator.Init( PbeParametersGenerator.Pkcs5PasswordToBytes(password.ToCharArray()),salt,
Iterations);
//GenerateKey
varkey=(KeyParameter)generator.GenerateDerivedMacParameters(KeyBitSize);
//CreateFullNonSecretPayload
varpayload=newbyte[salt.Length+nonSecretPayload.Length]; Array.Copy(nonSecretPayload,payload,nonSecretPayload.Length); Array.Copy(salt,0,payload,nonSecretPayload.Length,salt.Length);
returnSimpleEncrypt(secretMessage,key.GetKey(),payload);
}
publicstaticbyte[]SimpleDecryptWithPassword(byte[]encryptedMessage,stringpassword,int nonSecretPayloadLength=0)
{
//UserErrorChecks
if(string.IsNullOrWhiteSpace(password)||password.Length<MinPasswordLength)
thrownewArgumentException(String.Format(“Musthaveapasswordofatleast{0} characters!”,MinPasswordLength),”password”);
if(encryptedMessage==null||encryptedMessage.Length==0)
thrownewArgumentException(“EncryptedMessageRequired!”,”encryptedMessage”); vargenerator=newPkcs5S2ParametersGenerator();
//GrabSaltfromPayload
varsalt=newbyte[SaltBitSize/8];
Array.Copy(encryptedMessage,nonSecretPayloadLength,salt,0,salt.Length);
generator.Init(
PbeParametersGenerator.Pkcs5PasswordToBytes(password.ToCharArray()),salt,
Iterations);
//GenerateKey
varkey=(KeyParameter)generator.GenerateDerivedMacParameters(KeyBitSize);
returnSimpleDecrypt(encryptedMessage,key.GetKey(),salt.Length+nonSecretPayloadLength);
}
}
}
Section150.2:IntroductiontoSymmetricandAsymmetric Encryption
You can improve the security for data transit or storing by implementing encrypting techniques. Basically there are twoapproacheswhenusingSystem.Security.Cryptography:symmetricandasymmetric.
SymmetricEncryption
Thismethodusesaprivatekeyinordertoperformthedatatransformation. Pros:
Cons:
- Symmetricalgorithmsconsumelessresourcesandarefasterthanasymmetricones.
- The amount of data you can encrypt is unlimited.
- Encryptionanddecryptionusethesamekey.Someonewillbeabletodecryptyourdataifthekeyis compromised.You could end up with many different secret keys to manage if you choose to use a different secret key for
- different data.
Under System.Security.Cryptography you have different classes that perform symmetric encryption, they are known as block ciphers:
- AesManaged(AESalgorithm).
- AesCryptoServiceProvider(AESalgorithm FIPS 140-2 complaint). DESCryptoServiceProvider(DESalgorithm).
- RC2CryptoServiceProvider(Rivest Cipher 2algorithm).
- RijndaelManaged(AESalgorithm). Note: RijndaelManaged is notFIPS-197complaint. TripleDES(TripleDESalgorithm).
- AsymmetricEncryption
Thismethodusesacombinationofpublicandprivatekeysinordertoperformthedatatransformation. Pros:
Cons:
- Ituseslargerkeysthansymmetricalgorithms,thustheyarelesssusceptibletobeingcrackedbyusingbrute force.
- Itiseasiertoguaranteewhoisabletoencryptanddecryptthedatabecauseitreliesontwokeys(publicand private).
- There is a limit on the amount of data that you can encrypt. The limit is different for each algorithm and is typically proportional with the key size of the algorithm. For example, an RSACryptoServiceProvider objectwithakeylengthof1,024bitscanonlyencryptamessagethatissmallerthan128bytes.
- Asymmetric algorithmsareveryslowincomparison tosymmetricalgorithms.
- UnderSystem.Security.Cryptographyyouhaveaccesstodifferentclassesthatperformasymmetricencryption: DSACryptoServiceProvider(Digital Signature Algorithmalgorithm)
- RSACryptoServiceProvider(RSA Algorithmalgorithm)
Section150.3:SimpleSymmetricFileEncryption
ThefollowingcodesampledemonstratesaquickandeasymeansofencryptinganddecryptingfilesusingtheAES symmetric encryption algorithm.
The code randomly generates the Salt and Initialization Vectors each time a file is encrypted, meaning that encrypting the same file with the same password will always lead to different output. The salt and IV are written to the output file so that only the password is required to decrypt it.
publicstaticvoidProcessFile(stringinputPath,stringpassword,boolencryptMode,stringoutputPath)
{
using(varcypher=newAesManaged())
using(varfsIn=newFileStream(inputPath,FileMode.Open)) using(varfsOut=newFileStream(outputPath,FileMode.Create))
{
constintsaltLength=256;
varsalt=newbyte[saltLength];
variv=newbyte[cypher.BlockSize/8];
if(encryptMode)
{
//GeneraterandomsaltandIV,thenwritethemtofile
using(varrng=newRNGCryptoServiceProvider())
{
rng.GetBytes(salt);
rng.GetBytes(iv);
}
fsOut.Write(salt,0,salt.Length);
fsOut.Write(iv,0,iv.Length);
}
else
{
//ReadthesaltandIVfromthefile
fsIn.Read(salt,0,saltLength);
fsIn.Read(iv,0,iv.Length);
}
//Generateasecurepassword,basedonthepasswordandsaltprovided
var pdb = new Rfc2898DeriveBytes(password, salt);
varkey=pdb.GetBytes(cypher.KeySize/8);
//Encryptordecryptthefile
using(varcryptoTransform=encryptMode
?cypher.CreateEncryptor(key,iv)
:cypher.CreateDecryptor(key,iv))
using(varcs=newCryptoStream(fsOut,cryptoTransform,CryptoStreamMode.Write))
{
fsIn.CopyTo(cs);
}
}
}
Section150.4:CryptographicallySecureRandomData
There are times when the framework’s Random() class may not be considered random enough, given that it is based on a psuedo-random number generator. The framework’s Crypto classes do, however, provide something more robust in the form of RNGCryptoServiceProvider.
ThefollowingcodesamplesdemonstratehowtogenerateCryptographicallySecurebytearrays,stringsand numbers.
RandomByteArray
publicstaticbyte[]GenerateRandomData(intlength)
{
varrnd=newbyte[length];
using(varrng=newRNGCryptoServiceProvider()) rng.GetBytes(rnd);
returnrnd;
}
RandomInteger(withevendistribution)
publicstaticintGenerateRandomInt(intminVal=0,intmaxVal=100)
{
varrnd=newbyte[4];
using(varrng=newRNGCryptoServiceProvider()) rng.GetBytes(rnd);
vari=Math.Abs(BitConverter.ToInt32(rnd,0));
returnConvert.ToInt32(i%(maxVal-minVal+1)+minVal);
}
RandomString
publicstaticstringGenerateRandomString(intlength,stringallowableChars=null)
{
if(string.IsNullOrEmpty(allowableChars))
allowableChars=@”ABCDEFGHIJKLMNOPQRSTUVWXYZ”;
//Generaterandomdata
varrnd=newbyte[length];
using(varrng=newRNGCryptoServiceProvider()) rng.GetBytes(rnd);
//Generatetheoutputstring
varallowable=allowableChars.ToCharArray(); var l
=allowable.Length;
var chars = newchar[length];
for(vari =0;i <length;i++)
chars[i]=allowable[rnd[i]%l];
returnnew string(chars);
}
Passwords should never be stored as plain text! They should be hashed with a randomly generated salt (to defend againstrainbowtableattacks)usingaslowpasswordhashingalgorithm.Ahighnumberofiterations(>10k)canbe used to slow down brute force attacks. A delay of ~100ms is acceptable to a user logging in, but makes breaking a long password difficult. When choosing a number of iterations you should use the maximum tolerable value foryour application and increase it as computer performance improves. You will also need to consider stopping repeated requests which could be used as a DoS attack.
When hashing for the first time a salt can be generated for you, the resulting hash and salt can then be stored to a file.
privatevoidfirstHash(stringuserName,stringuserPassword,intnumberOfItterations)
{
Rfc2898DeriveBytesPBKDF2=newRfc2898DeriveBytes(userPassword,8,numberOfItterations);
//Hashthepasswordwitha8bytesalt
byte[]hashedPassword=PBKDF2.GetBytes(20); //Returnsa20bytehash
byte[]salt=PBKDF2.Salt;
writeHashToFile(userName,hashedPassword,salt,numberOfItterations);//Storethehashed passwordwiththesaltandnumberofitterationstocheckagainstfuturepasswordentries
}
Checking an existing users password, read their hash and salt from a file and compare to the hash of the entered password
privateboolcheckPassword(stringuserName,stringuserPassword,intnumberOfItterations)
{
byte[]usersHash=getUserHashFromFile(userName);
byte[]userSalt=getUserSaltFromFile(userName);
Rfc2898DeriveBytesPBKDF2=newRfc2898DeriveBytes(userPassword,userSalt, numberOfItterations); //Hashthepasswordwiththeuserssalt
byte[]hashedPassword=PBKDF2.GetBytes(20); //Returnsa20bytehash
boolpasswordsMach=comparePasswords(usersHash,hashedPassword);Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â //Comparesbytearrays
returnpasswordsMach;
}
Section150.6:FastAsymmetricFileEncryption
AsymmetricencryptionisoftenregardedaspreferabletoSymmetricencryptionfortransferringmessagestoother parties.Thisismainlybecauseitnegatesmanyoftherisksrelatedtotheexchangeofasharedkeyandensures thatwhilstanyonewiththepublickeycanencryptamessagefortheintendedrecipient,onlythatrecipientcan decryptit.Unfortunatelythemajordown-sideofasymmetricencryptionalgorithmsisthattheyaresignificantly slowerthantheirsymmetriccousins.Assuchtheasymmetricencryptionoffiles,especiallylargeones,canoftenbe averycomputationallyintensiveprocess.
Inorder toprovide both securityAND performance,a hybrid approachcan betaken. This entailsthe
cryptographicallyrandomgenerationofakeyandinitializationvectorforSymmetricencryption.Thesevaluesare then encrypted using an Asymmetric algorithm and written to the output file, before being used to encrypt the source data Symmetrically and appending it to the output.
This approach provides a high degree of both performance and security, in that the data is encrypted using a symmetric algorithm (fast) and the key and iv, both randomly generated (secure) are encrypted by an asymmetric algorithm (secure). It also has the added advantage that the same payload encrypted on different occasions will have very different cyphertext, because the symmetric keys are randomly generated each time.
The following class demonstrates asymmetric encryption of strings and byte arrays, as well as hybrid file encryption.
publicstaticclassAsymmetricProvider
{
#regionKeyGeneration
publicclassKeyPair
{
publicstringPublicKey{get;set;}
publicstringPrivateKey{get;set;}
}
publicstaticKeyPairGenerateNewKeyPair(intkeySize=4096)
{
//KeySizeismeasuredinbits.1024isthedefault,2048isbetter,4096ismorerobustbut takesafairbitlongertogenerate.
using(varrsa=newRSACryptoServiceProvider(keySize))
{
returnnewKeyPair{PublicKey=rsa.ToXmlString(false),PrivateKey= rsa.ToXmlString(true)};
}
}
#endregion
#regionAsymmetricDataEncryptionandDecryption
publicstaticbyte[]EncryptData(byte[]data,stringpublicKey)
{
using(varasymmetricProvider=newRSACryptoServiceProvider())
{
asymmetricProvider.FromXmlString(publicKey);
returnasymmetricProvider.Encrypt(data,true);
}
}
publicstaticbyte[]DecryptData(byte[]data,stringpublicKey)
{
using(varasymmetricProvider=newRSACryptoServiceProvider())
{
asymmetricProvider.FromXmlString(publicKey);
if(asymmetricProvider.PublicOnly)
thrownewException(“Thekeyprovidedisapublickeyanddoesnotcontainthe privatekeyelementsrequiredfordecryption”);
returnasymmetricProvider.Decrypt(data,true);
}
}
publicstaticstringEncryptString(stringvalue,stringpublicKey)
{
returnConvert.ToBase64String(EncryptData(Encoding.UTF8.GetBytes(value),publicKey));
}
publicstaticstringDecryptString(stringvalue,stringprivateKey)
{
returnEncoding.UTF8.GetString(EncryptData(Convert.FromBase64String(value),privateKey));
}
#endregion
#regionHybridFileEncryptionandDecription
publicstaticvoidEncryptFile(stringinputFilePath,stringoutputFilePath,stringpublicKey)
{
using(varsymmetricCypher=newAesManaged())
{
//GeneraterandomkeyandIVforsymmetricencryption
varkey=newbyte[symmetricCypher.KeySize/8];
variv=newbyte[symmetricCypher.BlockSize/8];
using(varrng=newRNGCryptoServiceProvider())
{
rng.GetBytes(key);
rng.GetBytes(iv);
}
//EncryptthesymmetrickeyandIV
varbuf=newbyte[key.Length+iv.Length];
Array.Copy(key,buf,key.Length);
Array.Copy(iv,0,buf,key.Length,iv.Length);
buf=EncryptData(buf,publicKey);
varbufLen=BitConverter.GetBytes(buf.Length);
andiv
//Symmetricallyencryptthedataandwriteittothefile,alongwiththeencryptedkey
using(varcypherKey=symmetricCypher.CreateEncryptor(key,iv)) using(varfsIn=newFileStream(inputFilePath,FileMode.Open)) using(varfsOut=newFileStream(outputFilePath,FileMode.Create))
using(varcs=newCryptoStream(fsOut,cypherKey,CryptoStreamMode.Write))
{
fsOut.Write(bufLen,0,bufLen.Length);
fsOut.Write(buf,0,buf.Length); fsIn.CopyTo(cs);
}
}
}
publicstaticvoidDecryptFile(stringinputFilePath,stringoutputFilePath,stringprivateKey)
{
using(varsymmetricCypher=newAesManaged())
using(varfsIn=newFileStream(inputFilePath,FileMode.Open))
{
//DeterminethelengthoftheencryptedkeyandIV
varbuf=newbyte[sizeof(int)];
fsIn.Read(buf,0,buf.Length);
varbufLen=BitConverter.ToInt32(buf,0);
//ReadtheencryptedkeyandIVdatafromthefileanddecryptusingtheasymmetric
algorithm
buf=newbyte[bufLen];
fsIn.Read(buf,0,buf.Length);
buf=DecryptData(buf,privateKey);
varkey=newbyte[symmetricCypher.KeySize/8];
variv=newbyte[symmetricCypher.BlockSize/8];
Array.Copy(buf,key,key.Length);Array.Copy(buf,key.
Length, iv,0,iv.Length);
//Decriptthefiledatausingthesymmetricalgorithm
using(varcypherKey=symmetricCypher.CreateDecryptor(key,iv)) using(varfsOut=newFileStream(outputFilePath,FileMode.Create))
using(varcs=newCryptoStream(fsOut,cypherKey,CryptoStreamMode.Write))
{
fsIn.CopyTo(cs);
}
}
}
#endregion
#regionKeyStorage
publicstaticvoidWritePublicKey(stringpublicKeyFilePath,stringpublicKey)
{
File.WriteAllText(publicKeyFilePath,publicKey);
}
publicstaticstringReadPublicKey(stringpublicKeyFilePath)
{
returnFile.ReadAllText(publicKeyFilePath);
}
privateconststringSymmetricSalt=”Stack_Overflow!”;//Changeme!
publicstaticstringReadPrivateKey(stringprivateKeyFilePath,stringpassword)
{
varsalt=Encoding.UTF8.GetBytes(SymmetricSalt);
varcypherText=File.ReadAllBytes(privateKeyFilePath);
using(varcypher=newAesManaged())
{
varpdb=newRfc2898DeriveBytes(password,salt);
varkey=pdb.GetBytes(cypher.KeySize/8);
variv=pdb.GetBytes(cypher.BlockSize/8);
using(vardecryptor=cypher.CreateDecryptor(key,iv)) using(varmsDecrypt=newMemoryStream(cypherText))
using(varcsDecrypt=newCryptoStream(msDecrypt,decryptor,CryptoStreamMode.Read)) using(varsrDecrypt=newStreamReader(csDecrypt))
{
returnsrDecrypt.ReadToEnd();
}
}
}
publicstaticvoidWritePrivateKey(stringprivateKeyFilePath,stringprivateKey,string password)
{
varsalt=Encoding.UTF8.GetBytes(SymmetricSalt);
using(varcypher=newAesManaged())
{
varpdb=newRfc2898DeriveBytes(password,salt);
varkey=pdb.GetBytes(cypher.KeySize/8);
variv=pdb.GetBytes(cypher.BlockSize/8);
using(varencryptor=cypher.CreateEncryptor(key,iv))
using(varfsEncrypt=newFileStream(privateKeyFilePath,FileMode.Create))
using(varcsEncrypt=newCryptoStream(fsEncrypt,encryptor,CryptoStreamMode.Write)) using(varswEncrypt=newStreamWriter(csEncrypt))
{
swEncrypt.Write(privateKey);
}
}
}
#endregion
}
Exampleofuse:
privatestaticvoidHybridCryptoTest(stringprivateKeyPath,stringprivateKeyPassword,string inputPath)
{
//Setupthetest
varpublicKeyPath=Path.ChangeExtension(privateKeyPath,”.public”); varoutputPath=Path.Combine(Path.ChangeExtension(inputPath,”.enc”)); vartestPath=Path.Combine(Path.ChangeExtension(inputPath,”.test”));
if(!File.Exists(privateKeyPath))
{
varkeys=AsymmetricProvider.GenerateNewKeyPair(2048); AsymmetricProvider.WritePublicKey(publicKeyPath,keys.PublicKey); AsymmetricProvider.WritePrivateKey(privateKeyPath,keys.PrivateKey,privateKeyPassword);
}
//Encryptthefile
varpublicKey=AsymmetricProvider.ReadPublicKey(publicKeyPath); AsymmetricProvider.EncryptFile(inputPath,outputPath,publicKey);
//Decryptitagaintocompareagainstthesourcefile
varprivateKey=AsymmetricProvider.ReadPrivateKey(privateKeyPath,privateKeyPassword); AsymmetricProvider.DecryptFile(outputPath,testPath,privateKey);
//Checkthatthetwofilesmatch
varsource=File.ReadAllBytes(inputPath);
vardest=File.ReadAllBytes(testPath);
if(source.Length!=dest.Length)
thrownewException(“Lengthdoesnotmatch”);
if(source.Where((t,i)=>t!=dest[i]).Any())
thrownewException(“Datamismatch”);
}
About us and this blog
We are a digital marketing company with a focus on helping our customers achieve great results across several key areas.
Request a free quote
We offer professional SEO services that help websites increase their organic search score drastically in order to compete for the highest rankings even when it comes to highly competitive keywords.
Subscribe to our newsletter!
More from our blog
See all postsRecent Posts
- Chapter161:ImportGoogleContacts 30/06/2024
- Chapter160:IncludingFontResources 29/06/2024
- Chapter159:CreatingOwnMessageBoxin WindowsFormApplication 28/06/2024