I have a problem with AES Rijndael Managed encryption in C#
I have a list of around 110,000 image filenames that I want to encrypt individually. I have written an algorithm that encrypts them all together and then a method that decrypts a single image.
The encryption method is as follows:
var saltBytes = Encoding.UTF8.GetBytes(salt);
// The key size for encrypting the images
int KEY_SIZE = 256;
// The initialization vector for encrypting the images
byte[] _initialisationVectorBytes = Encoding.ASCII.GetBytes("EAzjVfNrCzOoE7AI");
//Derives the key from the phrase and the salt
using(var password = new Rfc2898DeriveBytes(password, saltBytes))
{
//use the AES Rijndael algorithm
using(var symmetricKey = new RijndaelManaged())
{
//set the mode
symmetricKey.Mode = CipherMode.CBC;
//create an encryption object
using(var encryptor = symmetricKey.CreateEncryptor(password.GetBytes(KEY_SIZE / 8), _initialisationVectorBytes))
{
//loop through the images
Parallel.ForEach(_sourceImages, image =>
{
//set the plain text bytes and the salt bytes
var plainTextBytes = Encoding.UTF8.GetBytes(image.ImageID.ToString());
var cipherText = string.Empty;
if(_encryptImageFilenames)
{
//create a memory stream
using(var cryptoMemoryStream = new MemoryStream())
{
//create a cryptographic stream
using(var cryptoStream = new CryptoStream(cryptoMemoryStream, encryptor, CryptoStreamMode.Write))
{
//encrypt the image id
cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
cryptoStream.FlushFinalBlock();
cipherText = Convert.ToBase64String(cryptoMemoryStream.ToArray());
}
}
}
else
cipherText = image.ImageID.ToString();
}
}
}
}
This works as far as I can tell really well. However when I run the following decryption method, The vast majority of images decrypt correctly however, some images decrypt incorrectly with weird characters, and some give a "Padding is invalid and cannot be removed" error.
// The size of the key
const int KeySize = 256;
// Use the Rijndael Managed algorithm
RijndaelManaged SymmetricKey = new RijndaelManaged();
// The initialisation vector
byte[] initialisationVectorBytes = Encoding.ASCII.GetBytes("EAzjVfNrCzOoE7AI");
try
{
//get the cipher text bytes and the salt bytes
var cipherTextBytes = Convert.FromBase64String(cipherText);
//Derives the key from the phrase and the salt
using(var password = new Rfc2898DeriveBytes(passPhrase, Encoding.UTF8.GetBytes(salt)))
{
//set the mode
SymmetricKey.Mode = CipherMode.CBC;
//create an decryption object
using(var decryptor = SymmetricKey.CreateDecryptor(password.GetBytes(KeySize / 8), initialisationVectorBytes))
{
//create a memory stream
using(var memoryStream = new MemoryStream(cipherTextBytes))
{
//create a crypto stream
using(var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
{
//decrypt the string
var plainTextBytes = new byte[cipherTextBytes.Length];
var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
}
}
}
}
}
catch(Exception ex)
{
return string.Empty;
}
So with 110000 images I get a succesful decrypt for all but around 20-30 images, with the failures being one of the two errors earlier.
I cannot for the life of me see why as surely if there was an error in the encrypt method it would affect all images that have been encrypted?
some examples:
Plaintext = 128784
Ciphertext = J2W/5Y1/nvnrpvaWIZhl6g==
Decrypted = 128784
Plaintext = 122875
Ciphertext = N+heUx57427Lk8/Ew10rsA==
Decrypted = �\u0001\u0017275
Plaintext = 121693
Ciphertext = Zf70jcYCbHQqhd23NqD6yA==
Decrypted = 121693
Plaintext = 133456
Ciphertext = wzBgoDaTnGBEyQokI+l6Uw==
Decrypted = "Padding is invalid and cannot be removed"
The password and salt used in these examples are:
Password = "!XbLg`p/0.9nyF?;jGf#nA;e19'TtVk?Ik.l*2=zy.9MYH:7Jmj[|*0N!"
Salt = "alkfdslkaj:H6D£rWe!N,K;?sdoidnalks34334234&(*£';"
Related
I am tried to convert the php basic two way encryption code to C# code.the php code can be check with this site -> https://www.the-art-of-web.com/php/two-way-encryption/. I am not sure with the IV generate in my c# code is correct or not .The token which i have get from C# and PHP are in same format but the C# token shows invalid.please check my C# code that I need to change any thing.
PHP CODE:
<?php
$encrypted ="";
function encryptToken($token)
{
$cipher_method = 'aes-128-ctr';
$enc_key = openssl_digest('**********************', 'SHA256', TRUE);
$enc_iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($cipher_method));
$crypted_token = openssl_encrypt($token, $cipher_method, $enc_key, 0, $enc_iv) . "::" .
bin2hex($enc_iv);
unset($token, $cipher_method, $enc_key, $enc_iv);
return $crypted_token;
}
function createAccessToken(){
$now = date("YmdHis");
$secret = '###################';
$plainText = $now."::".$secret;
$encrypted = encryptToken($plainText);
return $encrypted;
}
$encrypted = createAccessToken();
?>
C# CODE
public string GenerateToken()
{
var Date = DateTime.Now.ToString("yyyyMMddHHmmss");
var secret = "#############################";
string plainText = Date + "::" + secret;
var accessToken = EncryptString(plainText);
return accessToken;
}
public string EncryptString(string plainText)
{
try
{
string password = "************************";
// Create sha256 hash
SHA256 mySHA256 = SHA256Managed.Create();
byte[] key = mySHA256.ComputeHash(Encoding.ASCII.GetBytes(password));
// Instantiate a new Aes object to perform string symmetric encryption
Aes encryptor = Aes.Create();
encryptor.Mode = CipherMode.ECB;
encryptor.Padding = PaddingMode.None;
encryptor.BlockSize = 128;
// Create secret IV
var iv = generateIV();
// Set key and IV
byte[] aesKey = new byte[32];
Array.Copy(key, 0, aesKey, 0, 32);
encryptor.Key = aesKey;
encryptor.IV = iv;
// Instantiate a new MemoryStream object to contain the encrypted bytes
MemoryStream memoryStream = new MemoryStream();
// Instantiate a new encryptor from our Aes object
ICryptoTransform aesEncryptor = encryptor.CreateEncryptor();
// Instantiate a new CryptoStream object to process the data and write it to the
// memory stream
CryptoStream cryptoStream = new CryptoStream(memoryStream, aesEncryptor, CryptoStreamMode.Write);
// Convert the plainText string into a byte array
byte[] plainBytes = Encoding.ASCII.GetBytes(plainText);
// Encrypt the input plaintext string
cryptoStream.Write(plainBytes, 0, plainBytes.Length);
// Complete the encryption process
cryptoStream.FlushFinalBlock();
// Convert the encrypted data from a MemoryStream to a byte array
byte[] cipherBytes = memoryStream.ToArray();
// Close both the MemoryStream and the CryptoStream
memoryStream.Close();
cryptoStream.Close();
// Convert the encrypted byte array to a base64 encoded string
string cipherText = Convert.ToBase64String(cipherBytes, 0, cipherBytes.Length) + "::" + ByteArrayToString(iv);
// Return the encrypted data as a string
return cipherText;
}
catch (Exception)
{
throw;
}
}
private static byte[] generateIV()
{
using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
{
byte[] nonce = new byte[IV_LENGTH];
rng.GetBytes(nonce);
return nonce;
}
}
Received Tokens
PHP Token
s9kMVUTBLvvjDJNean2kYyEHisYsEQHLQ54+7wV1zHdV1jRsSBFc6PNU0lyZ48VoCjckpm94xEgxKpTRCCXEX8CS/7PYbxZqNBFIZBtZZ3mXnkfA4rvkVEc6XuNXqLGdU3dFxbtWhikAMkHiiUPnPP5hR9UCyj2mAzJqHAwQ1Cn5VkyYWwJEHeyzQR4cwBVr::2e7d77b69ab1185e3d44af142aa6f358
C# token
qFSf2qQ+UHcqAoGUxj43wTO9fLhxfhwf+hYiRKq12amdcICJ6swXvSlV4P1/VYQm6ezNqF+x6LkjMfsxgG1Oyo71+T+mtSs0j5Bmu7eaZr5bDgAMMnZ8WrDKde2fGOgB81Gkj67L/Ka+dT+Ki0j/zsXMN454vqCzdUl0pw91TpwB8UHYni7sMA8JyLgto3Q4::418c68da838e2be51b0e84def5266024
When I decrypt an encrypted username using AES encryption it shows a bunch of "\0\0\0" at the end of the decrypted username. Is there a way to not add these when encrypting?
The example code of the encryption and decryption:
string ky = xx_Convert.Base64ToString("RllhSzNjM09PZFAwT2RYMkdxOFI2cG9HYjVmWVIybnQ=");
string iv = xx_Convert.Base64ToString("Y2pZQmJsdVlqdlBYM0RtcXVleDJkNGNTa0FIYjhYQ2Y=");
string username = "test#mail.com";
string encriptedUsername = xx_Crypto.EncryptAES(ky, iv, username);
string encriptedPassword = xx_Crypto.EncryptAES(ky, iv, "password");
string decryptedUsername = xx_Crypto.DecryptAES(ky, iv, encriptedUsername);
string decryptedPassword = xx_Crypto.DecryptAES(ky, iv, encriptedPassword);
This gives the following result for decryptedUsername when inspecting the variable: "test#mail.com\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
This is likely a result of the Padding property of the encryption function.
In the Encryption function the Padding is set to Padding = PaddingMode.Zeros but when I remove this line it still pads the result but with other invisible characters.
Encryption function:
public static string EncryptAES(string keyString, string ivString, string
text)
{
RijndaelManaged myRijndael = new RijndaelManaged()
{
Padding = PaddingMode.Zeros,
Mode = CipherMode.CBC,
KeySize = 256,
BlockSize = 256
};
byte[] key = Encoding.UTF8.GetBytes(keyString);
byte[] IV = Encoding.UTF8.GetBytes(ivString);
ICryptoTransform encryptor = myRijndael.CreateEncryptor(key, IV);
byte[] encrypted = null;
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor,
CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
//Write all data to the stream.
swEncrypt.Write(text);
}
encrypted = msEncrypt.ToArray();
}
}
return (Convert.ToBase64String(encrypted));
}
Edit: As requested here is the Decryption function:
public static string DecryptAES(string keyString, string ivString, string text)
{
var myRijndael = new RijndaelManaged()
{
Padding = PaddingMode.Zeros,
Mode = CipherMode.CBC,
KeySize = 256,
BlockSize = 256
};
byte[] key = Encoding.UTF8.GetBytes(keyString);
byte[] IV = Encoding.UTF8.GetBytes(ivString);
ICryptoTransform decryptor = myRijndael.CreateDecryptor(key, IV);
byte[] sEncrypted = Convert.FromBase64String(text);
byte[] fromEncrypt = new byte[sEncrypted.Length];
MemoryStream msDecrypt = new MemoryStream(sEncrypted);
CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read);
csDecrypt.Read(fromEncrypt, 0, fromEncrypt.Length);
return (Encoding.UTF8.GetString(fromEncrypt));
}
So is it possible to prevent this in the encryption function?
P.S: I did not use the same keys as in the original code
So is it possible to prevent this in the encryption function?
Yes, just use:
read = csDecrypt.Read(fromEncrypt, 0, fromEncrypt.Length);
return (Encoding.UTF8.GetString(fromEncrypt, 0, read));
You are currently encoding the entire buffer. The padding bytes are removed but the bytes in the buffer are preset to zero during initialization when you created the buffer.
Note that zero padding isn't used much because if your plaintext ends with one or more zero bytes, those bytes may be removed. So some messages cannot be decrypted successfully this way (and cryptographers want to encrypt arbitrary messages, not just strings).
I have been trying to implement proper IV practice in methods to encrypt and decrypt a UTF-8 string with AES which is then returned as a Base64 string. Using this question as a reference, I have prepended the generated IV to the byte array before the Base64 conversion. I'm having an issue where the decrypt method returns the UTF-8 string with exactly fifty characters of random junk (encryption artifacts?). I don't believe the issue is with the encryption because the decrypt method does consistently return the encrypted string. I think the problem is with one of the other conversion steps but I'm having trouble seeing where this might be coming from. Any help would be wildly appreciated.
Encrypt method
public static string EncryptString(string input, string key)
{
using (var aes = new AesCryptoServiceProvider())
{
aes.Key = System.Convert.FromBase64String(key);
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
byte[] rawData = Encoding.UTF8.GetBytes(input);
// IV is the 16 byte AES Initialization Vector
aes.GenerateIV();
using (var encryptor = aes.CreateEncryptor(aes.Key, aes.IV))
{
using (var ms = new MemoryStream())
{
ms.Write(aes.IV, 0, aes.IV.Length); // aes.IV.Length should be 16
using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
{
cs.Write(rawData, 0, rawData.Length);
cs.FlushFinalBlock();
}
byte[] encryptedData = ms.ToArray();
// this will hold the IV prepended to the encrypted data
byte[] output = new byte[aes.IV.Length + encryptedData.Length];
Array.Copy(aes.IV, output, aes.IV.Length); // save the iv
Array.Copy(encryptedData, 0, output, aes.IV.Length, encryptedData.Length); // save the data
// now encode the whole thing as base 64
return System.Convert.ToBase64String(output);
}
}
}
}
Decrypt method
public static string DecryptString(string input, string key)
{
using (var aes = new AesCryptoServiceProvider())
{
aes.Key = Convert.FromBase64String(key);
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
byte[] rawData = Convert.FromBase64String(input);
byte[] IV = new byte[16]; // aes.IV.Length should be 16
Array.Copy(rawData, IV, IV.Length);
using (var ms = new MemoryStream())
{
using (var cs = new CryptoStream(ms, aes.CreateDecryptor(aes.Key, IV), CryptoStreamMode.Write))
{
using (var binaryWriter = new BinaryWriter(cs))
{
binaryWriter.Write(rawData,IV.Length ,rawData.Length - IV.Length);
}
}
return Encoding.UTF8.GetString(ms.ToArray());
}
}
}
My test
static void Main(string[] args)
{
string payload = "My super secret string";
string key = "tR4mPn7mBQ8G6HWusyFnGk/gqdd/enWiUTr7YbhNrJg=";
Console.WriteLine(payload);
Console.WriteLine(key);
Console.WriteLine("");
string encrypted = EncryptString(payload, key);
Console.WriteLine(encrypted);
Console.WriteLine("");
string decrypted = DecryptString(encrypted, key);
Console.WriteLine(decrypted);
Console.WriteLine(decrypted.Length.ToString() + " " + encrypted.Length.ToString());
Console.ReadKey();
}
Edit to add - this is an example of the output:
�XQ��=F�]�D�?�My super secret string
You are writing the IV to the output twice in EncryptString. First you have:
ms.Write(aes.IV, 0, aes.IV.Length); // aes.IV.Length should be 16
which is the start of encryptedData. You then copy the IV and encryptedData (which already includes the IV) into a new byte array:
// this will hold the IV prepended to the encrypted data
byte[] output = new byte[aes.IV.Length + encryptedData.Length];
Array.Copy(aes.IV, output, aes.IV.Length); // save the iv
Array.Copy(encryptedData, 0, output, aes.IV.Length, encryptedData.Length); // save the data
This doubling of the IV is what is causing the extra bytes.
You don’t need to do the second copying. Just convert encryptedData to base 64 directly and return that:
return System.Convert.ToBase64String(encryptedData);
I am interested, why encryption/decryption only works with small, 0 bytes size on the disk files, but stop working with larger files, where I get errors The input data is not a complete block and Index was outside the bounds of the array.
I use ECDiffieHellmanCng for generating the same symmetric key on both sides.
Exchange of keys on encryption side:
using (ECDiffieHellmanCng sendingMode = new ECDiffieHellmanCng())
{
sendingMode.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;
sendingMode.HashAlgorithm = CngAlgorithm.Sha256;
sendersPublicKey = sendingMode.PublicKey.ToByteArray();
CngKey secretKey = CngKey.Import(receiversPublicKey, CngKeyBlobFormat.EccPublicBlob);
sendersKey = sendingMode.DeriveKeyMaterial(CngKey.Import(receiversPublicKey, CngKeyBlobFormat.EccPublicBlob));
byte[] encryptedFile = null;
byte[] ivFile = null;
byte[] fileBytes = File.ReadAllBytes(fileToSendPath);
Encryption(sendersKey, fileBytes, out encryptedFile, out ivFile);
}
Exchange on receiving side:
using (ECDiffieHellmanCng receivingMode = new ECDiffieHellmanCng())
{
receivingMode.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;
receivingMode.HashAlgorithm = CngAlgorithm.Sha256;
receiversPublicKey = receivingMode.PublicKey.ToByteArray();
CngKey secretKey = CngKey.Import(sendersPublicKey, CngKeyBlobFormat.EccPublicBlob);
receiversKey = receivingMode.DeriveKeyMaterial(CngKey.Import(sendersPublicKey, CngKeyBlobFormat.EccPublicBlob));
byte[] decryptedFile = new byte[50000000];
Decryption(encryptedFile, ivFile, out decryptedFile);
}
Encrypt/decrypt methods:
private void Encryption(byte[] key, byte[] unencryptedMessage,out byte[] encryptedMessage, out byte[] iv)
{
using (Aes aes = new AesCryptoServiceProvider())
{
aes.Key = key;
iv = aes.IV;
// Encrypt the message
using (MemoryStream ciphertext = new MemoryStream())
using (CryptoStream cs = new CryptoStream(ciphertext, aes.CreateEncryptor(), CryptoStreamMode.Write))
{
cs.Write(unencryptedMessage, 0, unencryptedMessage.Length);
cs.Close();
encryptedMessage = ciphertext.ToArray();
}
}
}
private void Decryption(byte[] encryptedMessage, byte[] iv, out byte[] decryptedMessage)
{
using (Aes aes = new AesCryptoServiceProvider())
{
aes.Key = receiversKey;
aes.IV = iv;
// Decrypt the message
using (MemoryStream decryptedBytes = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream(decryptedBytes, aes.CreateDecryptor(), CryptoStreamMode.Write))
{
cs.Write(encryptedMessage, 0, encryptedMessage.Length);
cs.Close();
decryptedMessage = decryptedBytes.ToArray();
}
}
}
}
AES is a block cipher requiring input to be in block size multiples, 16-bytes for AES. The simple solution is to use PKCS#7 (née PKCS#5) padding option and the padding will be transparently added on encryption and removed on decryption.
I've been trying to encrypt and decrypt on both iOS and .NET but I haven't been very successful. I've used this question but I get the error:
Specified initialisation vector (IV) does not match the block size for this algorithm.
Here's my encryption code for Swift using CryptoSwift:
let encrypt = try! "oauth_token".AES_encrypt("my key here (is 32 characters long)", iv: "1234567890123456")
func AES_encrypt(key: String, iv: String) throws -> String {
let data = self.dataUsingEncoding(NSUTF8StringEncoding)
let enc = try AES(key: key, iv: iv, blockMode:.CBC).encrypt(data!.arrayOfBytes(), padding: PKCS7())
let encData = NSData(bytes: enc, length: Int(enc.count))
let base64String: String = encData.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0));
let result = String(base64String)
return result
}
And my decryption code for .NET:
public static byte[] AES_Decrypt(byte[] bytesToBeDecrypted, byte[] key, byte[] iv)
{
byte[] decryptedBytes = null;
using (MemoryStream ms = new MemoryStream())
{
using (RijndaelManaged AES = new RijndaelManaged())
{
AES.KeySize = 256;
AES.BlockSize = 128;
AES.Key = key;
AES.IV = iv;
AES.Mode = CipherMode.CBC;
using (var cs = new CryptoStream(ms, AES.CreateDecryptor(), CryptoStreamMode.Write))
{
cs.Write(bytesToBeDecrypted, 0, bytesToBeDecrypted.Length);
cs.Close();
}
decryptedBytes = ms.ToArray();
}
}
return decryptedBytes;
}
byte[] encrypted_text = Convert.FromBase64String("secret token");
byte[] key = Convert.FromBase64String("32 character key");
byte[] iv = Convert.FromBase64String("0123456789012345");
string plaintext = Convert.ToBase64String(AES_Decrypt(encrypted_text, key, iv));
The block size is 16 bytes (AES.blockSize). Either you're using old version or your AES_encrypt() have some problem (AES_encrypt is not part of CryptoSwift).
Simple example from README:
let input: NSData // data to encrypt
let encrypted = try? input.encrypt(AES(key: "secret0key000000", iv:"0123456789012345"))
or this
// Encrypt string and get Base64 representation of result
let base64: String = try? "my secret string".encrypt(AES(key: "secret0key000000", iv: "0123456789012345"))