I just noticed that .NET Standard 2.1/.NET Core 3.0 finally added a class for AES-GCM encryption.
However, its API seems to be slightly different from the usual .NET crypto classes: Its Encrypt function asks for pre-allocated byte arrays for the cipher text and the tag, instead of providing them itself. Unfortunately there is no example in the docs showing proper usage of that class.
I know how to calculate the expected cipher text size for an AES encryption in theory, but I wonder whether it is really the intended approach to kind of "guess" a buffer size for the cipher text there. Usually crypto libraries provide functions that take care of those calculations.
Does someone have an example on how to properly encrypt a byte array using AesGcm?
I figured it out now.
I forgot that in GCM, the cipher text has the same length as the plain text; contrary to other encryption modes like CBC, no padding is required. The nonce and tag lengths are determined by the NonceByteSizes and TagByteSizes properties of AesGcm, respectively.
Using this, encryption can be done in the following way:
public string Encrypt(string plain)
{
// Get bytes of plaintext string
byte[] plainBytes = Encoding.UTF8.GetBytes(plain);
// Get parameter sizes
int nonceSize = AesGcm.NonceByteSizes.MaxSize;
int tagSize = AesGcm.TagByteSizes.MaxSize;
int cipherSize = plainBytes.Length;
// We write everything into one big array for easier encoding
int encryptedDataLength = 4 + nonceSize + 4 + tagSize + cipherSize;
Span<byte> encryptedData = encryptedDataLength < 1024
? stackalloc byte[encryptedDataLength]
: new byte[encryptedDataLength].AsSpan();
// Copy parameters
BinaryPrimitives.WriteInt32LittleEndian(encryptedData.Slice(0, 4), nonceSize);
BinaryPrimitives.WriteInt32LittleEndian(encryptedData.Slice(4 + nonceSize, 4), tagSize);
var nonce = encryptedData.Slice(4, nonceSize);
var tag = encryptedData.Slice(4 + nonceSize + 4, tagSize);
var cipherBytes = encryptedData.Slice(4 + nonceSize + 4 + tagSize, cipherSize);
// Generate secure nonce
RandomNumberGenerator.Fill(nonce);
// Encrypt
using var aes = new AesGcm(_key);
aes.Encrypt(nonce, plainBytes.AsSpan(), cipherBytes, tag);
// Encode for transmission
return Convert.ToBase64String(encryptedData);
}
Correspondingly, the decryption is done as follows:
public string Decrypt(string cipher)
{
// Decode
Span<byte> encryptedData = Convert.FromBase64String(cipher).AsSpan();
// Extract parameter sizes
int nonceSize = BinaryPrimitives.ReadInt32LittleEndian(encryptedData.Slice(0, 4));
int tagSize = BinaryPrimitives.ReadInt32LittleEndian(encryptedData.Slice(4 + nonceSize, 4));
int cipherSize = encryptedData.Length - 4 - nonceSize - 4 - tagSize;
// Extract parameters
var nonce = encryptedData.Slice(4, nonceSize);
var tag = encryptedData.Slice(4 + nonceSize + 4, tagSize);
var cipherBytes = encryptedData.Slice(4 + nonceSize + 4 + tagSize, cipherSize);
// Decrypt
Span<byte> plainBytes = cipherSize < 1024
? stackalloc byte[cipherSize]
: new byte[cipherSize];
using var aes = new AesGcm(_key);
aes.Decrypt(nonce, cipherBytes, tag, plainBytes);
// Convert plain bytes back into string
return Encoding.UTF8.GetString(plainBytes);
}
See dotnetfiddle for the full implementation and an example.
Note that I wrote this for network transmission, so everything is encoded into one, big base-64 string; alternatively, you can return nonce, tag and cipherBytes separately via out parameters.
The network setting is also the reason why I send the nonce and tag sizes: The class might be used by different applications with different runtime environments, which might have different supported parameter sizes.
Related
I trying to encrypt input bytes[] to AES, but final encryption buffer is null.
private byte[] Encrypt(byte[] data)
{
byte[] secretKey = new byte[] { 1, 2, 3 };
IBuffer key = Convert.FromBase64String(Convert.ToBase64String(secretKey.ToArray()).ToString()).AsBuffer();
Debug.WriteLine(key.Length);
SymmetricKeyAlgorithmProvider algorithmProvider = SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithmNames.AesCbc);
CryptographicKey cryptographicKey = algorithmProvider.CreateSymmetricKey(key);
IBuffer bufferEncrypt = CryptographicEngine.Encrypt(cryptographicKey, data.AsBuffer(), null);
return bufferEncrypt.ToArray();
}
Debugger show local variables as (Name, Value, Type):
+ this {Project.Auth} Project.Auth
+ data {byte[15]} byte[]
bufferEncrypt null Windows.Storage.Streams.IBuffer
+ cryptographicKey {Windows.Security.Cryptography.Core.CryptographicKey} Windows.Security.Cryptography.Core.CryptographicKey
+ key {System.Runtime.InteropServices.WindowsRuntime.WindowsRuntimeBuffer} Windows.Storage.Streams.IBuffer {System.Runtime.InteropServices.WindowsRuntime.WindowsRuntimeBuffer}
+ algorithmProvider {Windows.Security.Cryptography.Core.SymmetricKeyAlgorithmProvider} Windows.Security.Cryptography.Core.SymmetricKeyAlgorithmProvider
+ SecretKey Count = 16 System.Collections.Generic.List<byte>
Where is my fault?
I even cannot run your code snippet successfully on my side, exception System.ArgumentException: 'Value does not fall within the expected range. will be thrown when CreateSymmetricKey(key). Your key seems to be the wrong length, the key length should be a certain number of bits long based on the security you need. (256 bits for AES is common).
In additional, CBC algorithms require an initialization vector, you could assign a random number for the vector. More details please reference Symmetric keys
.
Please try to fix your issue and implement the encrypt feature by following the official sample or this example.
I'm tinkering with RSA signing of data.
I'm using a plaintext string, which i convert to byte array. i then generate private certificate, sign the byte array and then generate public key.
next i'm using the same byte array to verify the signature.
but i want to convert signature, in between steps, to the string - idea is to append it later on to the file that's being signed.
static void TestSigning(string privateKey)
{
string data = "TEST_TEST-TEST+test+TEst";
Console.WriteLine("==MESSAGE==");
Console.WriteLine(data);
byte[] dataByte = Encoding.Unicode.GetBytes(data);
using (var rsa = new RSACryptoServiceProvider())
{
rsa.FromXmlString(privateKey);
var publicKey = rsa.ToXmlString(false);
byte[] signature = rsa.SignData(dataByte, CryptoConfig.MapNameToOID("SHA512"));
string signatureString = Encoding.Unicode.GetString(signature);
byte[] roundtripSignature = Encoding.Unicode.GetBytes(signatureString);
Console.WriteLine("==TEST==");
Console.WriteLine(signature.Length.ToString());
Console.WriteLine(roundtripSignature.Length.ToString());
using (var checkRSA = new RSACryptoServiceProvider())
{
checkRSA.FromXmlString(publicKey);
bool verification = checkRSA.VerifyData(
dataByte,
CryptoConfig.MapNameToOID("SHA512"),
roundtripSignature);
Console.WriteLine("==Verification==");
Console.WriteLine(verification.ToString());
Console.ReadKey();
}
}
}
now here's the fun part
if i use UTF8 encoding i get byte arrays of different length
256 is the original size
484 is the roundtrip
UTF7 returns different sizes too
256 vs 679
both ASCII and Unicode return proper sizes 256 vs 256.
i've tried using
var sb = new StringBuilder();
for (int i = 0; i < signature.Length; i++)
{
sb.Append(signature[i].ToString("x2"));
}
to get the string. I'm then using Encoding.UTF8.GetBytes() method
this time i get the sizes of:
256 vs 512
if i remove the format from toString() i get:
256 vs 670
signature verification alwayas failed.
it works fine if i use 'signature' instead of roundtripSignature.
my question: Why, despite using same encoding type i get different byte arrays and strings? shouldn't this conversion be lossless?
Unicode isn't a good choice because, at minimum, \0, CR, LF, <delete>, <backspace> (and the rest of the control codes) can mess things up. (See an answer about this for Encrypt/Decrypt for more).
As #JamesKPolk said, you need to use a suitable binary-to-text encoding. Base64 and hex/Base16 are the most common, but there are plenty of other viable choices.
I'm still studying cryptography. I'm trying to create a simple static function in C# that encrypts string to DES (with a Base64 ouput). I learned that DES use 8-Byte as its key. I want the user to input string of any length, use it as the key to encrypt the message, then convert it to Base64. Example is in this site.
public static string EncryptDES(string phrase, string key)
{
string encrypted = "";
byte[] phraseBytes = System.Text.ASCIIEncoding.ASCII.GetBytes(phrase);
byte[] keyBytes = System.Text.Encoding.UTF8.GetBytes(key);
System.Security.Cryptography.MD5CryptoServiceProvider hashMD5Provider
= new System.Security.Cryptography.MD5CryptoServiceProvider();
System.Security.Cryptography.DESCryptoServiceProvider provider
= new System.Security.Cryptography.DESCryptoServiceProvider();
provider.Mode = System.Security.Cryptography.CipherMode.CBC;
System.Security.Cryptography.ICryptoTransform transform
= provider.CreateEncryptor(keyBytes, keyBytes);
System.Security.Cryptography.CryptoStreamMode mode
= System.Security.Cryptography.CryptoStreamMode.Write;
System.IO.MemoryStream memStream = new System.IO.MemoryStream();
System.Security.Cryptography.CryptoStream cryptoStream
= new System.Security.Cryptography.CryptoStream(memStream, transform, mode);
cryptoStream.Write(phraseBytes, 0, phraseBytes.Length);
cryptoStream.FlushFinalBlock();
byte[] encryptedMessageBytes = new byte[memStream.Length];
memStream.Position = 0;
memStream.Read(encryptedMessageBytes, 0, encryptedMessageBytes.Length);
encrypted = System.Convert.ToBase64String(encryptedMessageBytes);
return (encrypted);
} // private static string EncryptDES(string phrase, string key) { }
Then call it like this in Main:
SimpleEncryption.EncryptDES("A message regarding some secure 512-bit encryption", "AnUltimatelyVeryVeryLongPassword");
When a user inputs a random number of string length (whether greater than or less than 8 characters), a cryptographic exception always happens in this line:
System.Security.Cryptography.ICryptoTransform transform = provider.CreateEncryptor(keyBytes, keyBytes);
It says Specified key is not a valid size for this algorithm.
Removing parts of the key to fit in the length of 8 characters (with or without hashing) doesn't seems to be a secure solution (there might be a high rate of collision).
How can I implement DES (not 3DES) with a user input string?
You need to generate a hash from the user's password and take only 8 bytes to use as your key.
var fullHash = hashMD5Provider.ComputeHash(System.Text.Encoding.ASCII.GetBytes(key));
var keyBytes = new byte[8];
Array.Copy(fullHash , keyBytes, 8);
Your question expressed concern about hash collisions from throwing away part of the hash; yes, that certainly does increase the risk, but (assuming your hash algorithm is good) you're no worse off than if you just used a hash algorithm that only produced 8 bytes to begin with. A good hash algorithm should distribute the entropy evenly.
I am converting a classic asp application to C#, and would like to be able to decrypt strings in c# that were originally encrypted in classic asp. the classic asp code is here, and the c# code is here. The problem that i am facing is that the signatures of the Encrypt and Decrypt methods in asp vs C# are different. here is my asp code for decrypting, which wraps the decrypt code.
Function AESDecrypt(sCypher)
if sCypher <> "" then
Dim bytIn()
Dim bytOut
Dim bytPassword()
Dim lCount
Dim lLength
Dim sTemp
Dim sPassword
sPassword = "My_Password"
lLength = Len(sCypher)
ReDim bytIn(lLength/2-1)
For lCount = 0 To lLength/2-1
bytIn(lCount) = CByte("&H" & Mid(sCypher,lCount*2+1,2))
Next
lLength = Len(sPassword)
ReDim bytPassword(lLength-1)
For lCount = 1 To lLength
bytPassword(lCount-1) = CByte(AscB(Mid(sPassword,lCount,1)))
Next
bytOut = DecryptData(bytIn, bytPassword) //' this is the problem child
lLength = UBound(bytOut) + 1
sTemp = ""
For lCount = 0 To lLength - 1
sTemp = sTemp & Chr(bytOut(lCount))
Next
AESDecrypt = sTemp
End if
End Function
However, in c# i am struggling to convert this function because the c# equivalent of DecryptData has more params
public static byte[] DecryptData(byte[] message, byte[] password,
byte[] initialisationVector, BlockSize blockSize,
KeySize keySize, EncryptionMode cryptMode)
{...}
what values can i use for initialisationVector, blockSize, keySize, cryptMode so as to be able to decrypt the same way the classic asp code does.
Using Phil Fresle's C# Rijndael implementation, you can use the following code to have successfully decrypt a value that was encrypted with Phil's ASP/VBScript version.
You can read my answer about encrypting here: Password encryption/decryption between classic asp and ASP.NET
public string DecryptData(string encryptedMessage, string password)
{
if (encryptedMessage.Length % 2 == 1)
throw new Exception("The binary key cannot have an odd number of digits");
byte[] byteArr = new byte[encryptedMessage.Length / 2];
for (int index = 0; index < byteArr.Length; index++)
{
string byteValue = encryptedMessage.Substring(index * 2, 2);
byteArr[index] = byte.Parse(byteValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture);
}
byte[] result = Rijndael.DecryptData(
byteArr,
Encoding.ASCII.GetBytes(password),
new byte[] { }, // Initialization vector
Rijndael.BlockSize.Block256, // Typically 128 in most implementations
Rijndael.KeySize.Key256,
Rijndael.EncryptionMode.ModeECB // Rijndael.EncryptionMode.ModeCBC
);
return ASCIIEncoding.ASCII.GetString(result);
}
Most default implementations will use a key size of 128, 192, or 256 bits. A block size at 128 bits is standard. Although some implementations allow block sizes other than 128 bits, changing the block size will just add another item into the mix to cause confusion when trying to get data encrypted in one implementation to properly decrypt in another.
UPDATE
Turns out I was wrong about one piece here; the EncryptionMode should be set as EncryptionMode.ModeECB, not EncryptionMode.ModeCBC. "ECB" is less secure (https://crypto.stackexchange.com/questions/225/should-i-use-ecb-or-cbc-encryption-mode-for-my-block-cipher) because it doesn't cycle like CBC does, but that is how it was implemented in the VB version of the encryption.
Interestingly enough, using CBC on an ECB encrypted value WILL work for the first handful of bytes up until a certain point (i'd imagine this has to do with the block size) at which point the remainder of the value is mangled. You can see this particularly clearly when encrypting a long-ish string in the VB version and decrypting it with the code I posted above with a mode of EncryptionMode.ModeECB
I know very little about Encryption, but my goal is to essentially decrypt strings. I have been given the AES(128) key.
However, I must retrieve the IV from the Encrypted string, which is the first 16 bits.
Heres the doc for salesforce for more information (if what i explained was incorrect)
Encrypts the blob clearText using the specified algorithm and private
key. Use this method when you want Salesforce to generate the
initialization vector for you. It is stored as the first 128 bits (16
bytes) of the encrypted blob
http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_classes_restful_crypto.htm (encryptWithManagedIV)
For Retrieving the IV I've tried something like this (I don't believe it's right though):
public string retrieveIv()
{
string iv = "";
string input = "bwZ6nKpBEsuAKM8lDTYH1Yl69KkHN1i3XehALbfgUqY=";
byte[] bytesToEncode = Encoding.UTF8.GetBytes(input);
for(int i = 0; i <= 15; i++){
iv += bytesToEncode[i].ToString(); ;
}
return iv;
}
(Just ignore the fact that the input is hardcoded and not parameterized; easier for testing purposes)
Then use the Best answer from this question to decrypt the string
The IV shouldn't be expressed as a string - it should be as a byte array, as per the AesManaged.IV property.
Also, using Encoding.UTF8 is almost certainly wrong. I suspect you want:
public static byte[] RetrieveIv(string encryptedBase64)
{
// We don't need to base64-decode everything... just 16 bytes-worth
encryptedBase64 = encryptedBase64.Substring(0, 24);
// This will be 18 bytes long (4 characters per 3 bytes)
byte[] encryptedBinary = Convert.FromBase64String(encryptedBase64);
byte[] iv = new byte[16];
Array.Copy(encryptedBinary, 0, iv, 0, 16);
return iv;
}