I have following code that uses AesCryptoServiceProvider for encrypting and decrypting. The iv and key used are same for both encryption and decryption. Still the decrypted value differ from the source string.
What need to be corrected to get the original value after decrypt?
This code is working when inputValue = valid128BitString. But when the inputString = “Test” I am getting the following exception Padding is invalid and cannot be removed.. How can we correct it?
UPDATED QUESTION
The following will do the trick based on #jbtule answer.
encyptedValue.IV = result.IV;
The IV value from encryption result changes. Suppose encryption is done in a separate process, how can we know the IV for decryption? Is there a way to make it constant or known?
Answer: Your other option is pass a IV in to Encrypt and assign it before you begin your crypto transform, instead of letting aesProvider generate a random one for you. – #Scott Chamberlain
aesProvider.IV = Convert.FromBase64String("4uy34C9sqOC9rbV4GD8jrA==");
Update: Refer How to apply padding for Base64. We can use UTF8 for encoding the source input and result output. The key and IV may remain in Base64.
Using Base64 for source input will cause issues with some values, for example, "MyTest" where length of string is not a multiple of 4
Relevant points:
To decrypt data that was encrypted using one of the SymmetricAlgorithm classes, you must set the Key property and IV property to the same values that were used for encryption.
SymmetricAlgorithm.IV Property: Information from the previous block is mixed into the process of encrypting the next block. Thus, the output of two identical plain text blocks is different. Because this technique uses the previous block to encrypt the next block, an initialization vector is needed to encrypt the first block of data. (As per SymmetricAlgorithm.IV Property MSDN article)
The valid Key sizes are: 128, 192, 256 bits (as per How many characters to create a byte array for my AES method?)
Main Program
class Program
{
static void Main(string[] args)
{
string valid128BitString = "AAECAwQFBgcICQoLDA0ODw==";
string inputValue = valid128BitString;
string keyValue = valid128BitString;
string iv = valid128BitString;
byte[] byteValForString = Convert.FromBase64String(inputValue);
EncryptResult result = Aes128Utility.EncryptData(byteValForString, keyValue);
EncryptResult encyptedValue = new EncryptResult();
encyptedValue.IV = iv;
encyptedValue.EncryptedMsg = result.EncryptedMsg;
string finalResult = Convert.ToBase64String(Aes128Utility.DecryptData(encyptedValue, keyValue));
Console.WriteLine(finalResult);
if (String.Equals(inputValue, finalResult))
{
Console.WriteLine("Match");
}
else
{
Console.WriteLine("Differ");
}
Console.ReadLine();
}
}
AES Utility
public static class Aes128Utility
{
private static byte[] key;
public static EncryptResult EncryptData(byte[] rawData, string strKey)
{
EncryptResult result = null;
if (key == null)
{
if (!String.IsNullOrEmpty(strKey))
{
key = Convert.FromBase64String((strKey));
result = Encrypt(rawData);
}
}
else
{
result = Encrypt(rawData);
}
return result;
}
public static byte[] DecryptData(EncryptResult encryptResult, string strKey)
{
byte[] origData = null;
if (key == null)
{
if (!String.IsNullOrEmpty(strKey))
{
key = Convert.FromBase64String(strKey);
origData = Decrypt(Convert.FromBase64String(encryptResult.EncryptedMsg), Convert.FromBase64String(encryptResult.IV));
}
}
else
{
origData = Decrypt(Convert.FromBase64String(encryptResult.EncryptedMsg), Convert.FromBase64String(encryptResult.IV));
}
return origData;
}
private static EncryptResult Encrypt(byte[] rawData)
{
using (AesCryptoServiceProvider aesProvider = new AesCryptoServiceProvider())
{
aesProvider.Key = key;
aesProvider.Mode = CipherMode.CBC;
aesProvider.Padding = PaddingMode.PKCS7;
using (MemoryStream memStream = new MemoryStream())
{
CryptoStream encStream = new CryptoStream(memStream, aesProvider.CreateEncryptor(), CryptoStreamMode.Write);
encStream.Write(rawData, 0, rawData.Length);
encStream.FlushFinalBlock();
EncryptResult encResult = new EncryptResult();
encResult.EncryptedMsg = Convert.ToBase64String(memStream.ToArray());
encResult.IV = Convert.ToBase64String(aesProvider.IV);
return encResult;
}
}
}
private static byte[] Decrypt(byte[] encryptedMsg, byte[] iv)
{
using (AesCryptoServiceProvider aesProvider = new AesCryptoServiceProvider())
{
aesProvider.Key = key;
aesProvider.IV = iv;
aesProvider.Mode = CipherMode.CBC;
aesProvider.Padding = PaddingMode.PKCS7;
using (MemoryStream memStream = new MemoryStream())
{
CryptoStream decStream = new CryptoStream(memStream, aesProvider.CreateDecryptor(), CryptoStreamMode.Write);
decStream.Write(encryptedMsg, 0, encryptedMsg.Length);
decStream.FlushFinalBlock();
return memStream.ToArray();
}
}
}
}
DTO Class
public class EncryptResult
{
public string EncryptedMsg { get; set; }
public string IV { get; set; }
}
References
How many characters to create a byte array for my AES method?
Specified key is not a valid size for this algorithm
Encryption with AES-256 and the Initialization Vector
Invalid length for a Base-64 char array
What's the difference between UTF8/UTF16 and Base64 in terms of encoding
It is easy to make implementation mistakes with cryptographic primitives, people do it all the time, it's best to use a high level library if you can.
I have a snippet that I try to keep reviewed and up to date, that works pretty close to what you're doing. It also does authentication on the cipher text, which I would recommend if there is anyway an adversary could send chosen ciphertext to your decryption implementation, there are a lot of side channel attacks related to modifying the ciphertext.
However, the problem you're having does not have any thing to do with padding, if your ciphertext doesn't matchup to your key and iv, and you didn't authenticate your iv and ciphertext, you'll typically get a padding error (if this is bubbled up a client it's called a padding oracle). You need to change your main statement to:
string valid128BitString = "AAECAwQFBgcICQoLDA0ODw==";
string inputValue = "Test";
string keyValue = valid128BitString;
byte[] byteValForString = Encoding.UTF8.GetBytes(inputValue);
EncryptResult result = Aes128Utility.EncryptData(byteValForString, keyValue);
EncryptResult encyptedValue = new EncryptResult();
encyptedValue.IV = result.IV; //<--Very Important
encyptedValue.EncryptedMsg = result.EncryptedMsg;
string finalResult =Encoding.UTF8.GetString(Aes128Utility.DecryptData(encyptedValue, keyValue));
So you use the same IV to decrypt as you did to encrypt.
Related
I want encryption in my wcf service. For that I am writing a class to encrypt and decrypt strings. The encryption seems to work fine and produces a encrypted string but while doing decryption it was giving error of double escape not allowed or error 401. I have add in webconfig the following
<security>
<requestFiltering allowDoubleEscaping="true" />
</security>
Now it is giving error of either the length of the string is not correct or for shorter strings Bad String. The code is
To Encrypt
static string hash = "mypass#mysitec0m";
public static string Encrypt(string decrypted)
{
byte[] data = UTF8Encoding.UTF8.GetBytes(decrypted);
using (MD5CryptoServiceProvider mds = new MD5CryptoServiceProvider())
{
byte[] keys = mds.ComputeHash(UTF8Encoding.UTF8.GetBytes(hash));
using (TripleDESCryptoServiceProvider tripDes = new TripleDESCryptoServiceProvider())
{
ICryptoTransform transform = tripDes.CreateEncryptor();
byte[] result = transform.TransformFinalBlock(data, 0, data.Length);
return Convert.ToBase64String(result);
}
}
}
and to decrypt
public static string decrypt(string encrypted)
{
byte[] data = Convert.FromBase64String(encrypted);
using (MD5CryptoServiceProvider mds = new MD5CryptoServiceProvider())
{
byte[] keys = mds.ComputeHash(UTF8Encoding.UTF8.GetBytes(hash));
using (TripleDESCryptoServiceProvider tripDes = new TripleDESCryptoServiceProvider())
{
ICryptoTransform transform = tripDes.CreateDecryptor();
byte[] result = transform.TransformFinalBlock(data, 0, data.Length);
return UTF8Encoding.UTF8.GetString(result);
}
}
}
Why is the error there and how can I fix it.
You never initialized the cipher with your key, thus you are using one random key for the encryptor and a different random key with your decryptor.
Use the CreateEncryptor(Byte[], Byte[]) method instead, and similarly for the decryptor.
CreateEncryptor(Byte[], Byte[])...
creates a symmetric encryptor object with the specified Key property
and initialization vector (IV).
This has nothing to do with WCF, more like a question about TripleDESCryptoServiceProvider.There is an error in your encryption and decryption code. If IV is not set, the encryption mode should use ECB. The default is CBC.CBC needs to set IV.
This is my modified code:
To Encrypt
public static string Encrypt(string decrypted)
{
byte[] data = UTF8Encoding.UTF8.GetBytes(decrypted);
using (MD5CryptoServiceProvider mds = new MD5CryptoServiceProvider())
{
byte[] keys = mds.ComputeHash(UTF8Encoding.UTF8.GetBytes(hash));
using (TripleDESCryptoServiceProvider tripDes = new TripleDESCryptoServiceProvider() {
Key=keys,
Mode=CipherMode.ECB
})
{
ICryptoTransform transform = tripDes.CreateEncryptor();
byte[] result = transform.TransformFinalBlock(data, 0, data.Length);
return Convert.ToBase64String(result);
}
}
}
To decrypt
public static string decrypt(string encrypted)
{
byte[] data = Convert.FromBase64String(encrypted);
using (MD5CryptoServiceProvider mds = new MD5CryptoServiceProvider())
{
byte[] keys = mds.ComputeHash(UTF8Encoding.UTF8.GetBytes(hash));
using (TripleDESCryptoServiceProvider tripDes = new TripleDESCryptoServiceProvider()
{
Key = keys,
Mode = CipherMode.ECB
})
{
ICryptoTransform transform = tripDes.CreateDecryptor();
byte[] result = transform.TransformFinalBlock(data, 0, data.Length);
return UTF8Encoding.UTF8.GetString(result);
}
}
}
I would suggest you use POST in place of GET. Because encrypted string might be long and will have many special characters as you have mentioned in the question
Below is the sample.
[OperationContract(Name = "Decrypt")]
[WebInvoke(Method = "POST",
UriTemplate = "Decrypt")]
string Decrypt(string data);
So I have this piece of php code that I'm not allowed to modify for now, mainly because it's old and works properly.
Warning! Very bad code overal. the IV is not being randomized neither stored with the output. I'm not asking this because I want to,
I'm asking because I need to. I'm also planning on refactoring when I get this working and completing my C# code with actually reliable cyphering code.
function encrypt($string)
{
$output = false;
$encrypt_method = "AES-256-CBC";
$param1 = 'ASasd564D564aAS64ads564dsfg54er8G74s54hjds346gf445gkG7';
$param2 = '654dsfg54er8ASG74sdfg54hjdas346gf34kjdDJF56hfs2345gkFG';
$ky = hash('sha256', $param1); // hash
$iv = substr(hash('sha256', $param2), 0, 16);
$output = openssl_encrypt($string, $encrypt_method, $ky, 0, $iv);
$output = base64_encode($output);
return $output;
}
I want to do the same in C# because I'm getting an entity with all its fields encrypted with that code.
I want to be able to encrypt that data so I can query my entity list whithout having to decrypt all the entities. And I want to decrypt some properties of the filtered entities so they can actually be useful.
Now, for that matter I created a CryptoHelper that will do this, except it doesn't.
I try to calculate the Key and IV in the constructor:
public readonly byte[] Key;
public readonly byte[] IV;
public CryptoHelper()
{
Key = GetByteArraySha256Hash("ASasd564D564aAS64ads564dsfg54er8G74s54hjds346gf445gkG7", false);
IV = GetByteArraySha256Hash("654dsfg54er8ASG74sdfg54hjdas346gf34kjdDJF56hfs2345gkFG", true);
}
private byte[] GetByteArraySha256Hash(string source, bool salt)
{
byte[] result;
try
{
using (SHA256 sha256Hash = SHA256.Create())
{
result = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(source));
}
}
catch (Exception)
{
throw;
}
if (salt)
{
return result.Take(16).ToArray();
}
return result;
}
And then use a Encrypt and Decrypt methods that are working pretty well when I test them with a test string. The only problem is that the string have some padding at the end, but it's kind of a minor problem considering that any string encrypted with the php method results in gibberish.
private string Encrypt(string source)
{
try
{
string result = "";
using (var aes = new AesManaged { Key = Key, IV = IV, Mode = CipherMode.CBC, Padding = PaddingMode.Zeros })
{
byte[] sourceByteArray = Encoding.UTF8.GetBytes(source);
using (var encryptor = aes.CreateEncryptor(aes.Key, aes.IV))
{
byte[] encriptedSource = encryptor.TransformFinalBlock(sourceByteArray, 0, sourceByteArray.Length);
result = Convert.ToBase64String(encriptedSource);
result = Convert.ToBase64String(Encoding.UTF8.GetBytes(result));
}
}
return result;
}
catch (Exception ex)
{
throw;
}
}
private string Decrypt(string source)
{
try
{
string result = "";
//Double Base64 conversion, as it's done in the php code.
byte[] sourceByte = Convert.FromBase64String(source);
byte[] sourceFreeOfBase64 = Convert.FromBase64String(Encoding.UTF8.GetString(sourceByte));
byte[] resultByte;
int decryptedByteCount = 0;
using (var aes = new AesManaged { Key = Key, IV = IV, Mode = CipherMode.CBC, Padding = PaddingMode.Zeros })
{
using (ICryptoTransform AESDecrypt = aes.CreateDecryptor(aes.Key, aes.IV))
{
using (MemoryStream memoryStream = new MemoryStream(sourceFreeOfBase64))
{
using (CryptoStream cs = new CryptoStream(memoryStream, AESDecrypt, CryptoStreamMode.Read))
{
resultByte = new byte[sourceFreeOfBase64.Length];
decryptedByteCount = cs.Read(resultByte, 0, resultByte.Length);
}
}
}
//This returns the encoded string with a set of "\0" at the end.
result = Encoding.UTF8.GetString(resultByte);
result = result.Replace("\0", "");
}
return result;
}
catch (Exception ex)
{
throw;
}
}
I'm pretty sure that the main problem here lies in the php line $iv = substr(hash('sha256', $param2), 0, 16);. I checked the results of both hash functions in php and C# and are exactly the same.
From what I've been reading php treats strings as byte arrays (correct me if I'm wrong) so a 16 char string should be enough to get a 16 byte array and a 128 block. But in C#, when I get the 16 byte array and convert it to a string I get a 32 char string that is the same as if I did $iv = substr(hash('sha256', $param2), 0, 32);.
So my question is, how do I get the same byte array result in C# that I get in this line $iv = substr(hash('sha256', $param2), 0, 16); of php? Is this even possible?
The hash function will return the same number of bytes whatever the input, so I suspect it is a difference in how you convert the resulting byte[] back to a string in C# compared to the PHP implementation.
The PHP docs say that the hash function output the result in lower case hexits. This is absolutely not the same as the UTF8 encoding that you are returning.
There isn't a built in framework way to do this, but check out this SO question for several different methods.
Also worth noting is that you do not specify the Padding value in your C# code. AES-CBC is a block cipher and will need to use some padding scheme. You may well get a padding exception. I think that it will need Zero padding (docs)
aes.Padding = PaddingMode.Zeros
but I'm not 100%
Well, I managed to solve this in a not so bad manner.
Following #ste-fu advice I tried to get rid of every piece of encoding that I could find.
But I still wasn't anywhere close to getting the Key and IV right. So I did some testing with php. I made a var_dump of the IV and got a neat 16 length array with bytes shown as integers.
var_dump result array starts allways in [1]. Be advised.
$iv = substr(hash('sha256', $param2), 0, 16);
$byte_array = unpack('C*', $iv);
var_dump($byte_array);
That peaked my interest, thinking that if I had the hex string right I should be able to convert each char in the string to it's equivalent byte. Lo and behold, I made this function in C#:
private byte[] StringToByteArray(string hex)
{
IList<byte> resultList = new List<byte>();
foreach (char c in hex)
{
resultList.Add(Convert.ToByte(c));
}
return resultList.ToArray();
}
And this worked very well for the IV. Now I just had to do the same thing for the key. And so I did, just to find that I had a 64 length byte array. That's weird, but ok. More testing in php.
Since it does make sense that the php Key behaves the same as the IV I didn't get how the openssl encryption functions allowed a 64 length Key. So I tryed to encrypt and decrypt the same data with a Key made from the first 32 chars. $ky = substr(hash('sha256', $param1), 0, 32);
And it gave me the same result as with the full Key. So, my educated guess is that openssl just takes the bytes necesary for the encoding to work. In fact it will take anything since I tested with substrings of 1, 16, 20, 32, 33 and 50 length. If the length of the string is bigger than 32 the function itself will cut it.
Anyway, i just had to get the first 32 chars of the Key hex and use my new function to convert them into a byte array and I got my Key.
So, the main C# code right now looks like this:
public CryptoHelper(string keyFilePath, string ivFilePath)
{
//Reading bytes from txt file encoded in UTF8.
byte[] key = File.ReadAllBytes(keyFilePath);
byte[] iv = File.ReadAllBytes(ivFilePath);
IV = StringToByteArray(GetStringHexSha256Hash(iv).Substring(0, 16));
Key = StringToByteArray(GetStringHexSha256Hash(key).Substring(0, 32));
//Tests
var st = Encrypt("abcdefg");
var en = Decrypt(st);
}
//Convert each char into a byte
private byte[] StringToByteArray(string hex)
{
IList<byte> resultList = new List<byte>();
foreach (char c in hex)
{
resultList.Add(Convert.ToByte(c));
}
return resultList.ToArray();
}
private string GetStringHexSha256Hash(byte[] source)
{
string result = "";
try
{
using (SHA256 sha256Hash = SHA256.Create("SHA256"))
{
//Get rid of Encoding!
byte[] hashedBytes = sha256Hash.ComputeHash(source);
for (int i = 0; i < hashedBytes.Length; i++)
{
result = string.Format("{0}{1}",
result,
hashedBytes[i].ToString("x2"));
}
}
}
catch (Exception)
{
throw;
}
return result;
}
private string Encrypt(string source)
{
try
{
string result = "";
using (var aes = new AesManaged { Key = Key, IV = IV, Mode = CipherMode.CBC, Padding = PaddingMode.PKCS7 })
{
byte[] sourceByteArray = Encoding.UTF8.GetBytes(source);
using (var encryptor = aes.CreateEncryptor(aes.Key, aes.IV))
{
byte[] encriptedSource = encryptor.TransformFinalBlock(sourceByteArray, 0, sourceByteArray.Length);
result = Convert.ToBase64String(encriptedSource);
//Nothing to see here, move along.
result = Convert.ToBase64String(Encoding.UTF8.GetBytes(result));
}
}
return result;
}
catch (Exception ex)
{
throw;
}
}
private string Decrypt(string source)
{
try
{
string result = "";
byte[] sourceByte = Convert.FromBase64String(source);
byte[] sourceFreeOfBase64 = Convert.FromBase64String(Encoding.UTF8.GetString(sourceByte));
byte[] resultByte;
int decryptedByteCount = 0;
using (var aes = new AesManaged { Key = Key, IV = IV, Mode = CipherMode.CBC, Padding = PaddingMode.PKCS7 })
{
using (ICryptoTransform AESDecrypt = aes.CreateDecryptor(aes.Key, aes.IV))
{
using (MemoryStream memoryStream = new MemoryStream(sourceFreeOfBase64))
{
using (CryptoStream cs = new CryptoStream(memoryStream, AESDecrypt, CryptoStreamMode.Read))
{
resultByte = new byte[sourceFreeOfBase64.Length];
//Now that everything works as expected I actually get the number of bytes decrypted!
decryptedByteCount = cs.Read(resultByte, 0, resultByte.Length);
}
}
}
//Nothing to see here, move along.
result = Encoding.UTF8.GetString(resultByte);
//Use that byte count to get the actual data and discard the padding.
result = result.Substring(0, decryptedByteCount);
}
return result;
}
catch (Exception ex)
{
throw;
}
}
I still need to clean all the code from my class from all the testing I did, but this is all that's needed to make it work.
I hope this helps anybody with the same problem that I faced.
Cheers.
I'm currently working on a class that encrypts large amounts of text with a randomly generated encryption key encrypted by a X509 certificate from a smart card, using a RSACryptoServiceProvider to perform the master key encryption and decryption operations. However, when I have the fOEAP padding option set to true, I have the "Error while decoding OAEP padding" error on decryption every time. I've checked the key size and it is within acceptable limits. And I've gone through breakpoints to make sure that the Base64 string that gets returned from the encryption function is the exact same as the encrypted Base64 string that gets passed back to the decryption function when the file gets loaded again.
The key pair is definitely correct, since it works fine without OAEP. And I've checked the text encoding too.
EDIT: It turns out this could be a smart card specific issue, when I tried decryption with a local X509 certificate the decryption succeeded.
EDIT: This is the decryption code that fails:
string TestString = "Hello World!";
X509Certificate2 cert = DRXEncrypter.GetCertificate("Select a test certificate", "Select a certificate to use for this test from the local store.");
string key = DRXEncrypter.GenerateEncryptionKey(214);
Console.WriteLine("Encryption Key: " + key);
string encrypted = DRXEncrypter.EncryptBody(TestString, key);
Console.WriteLine("Encrypted Body: " + encrypted);
string cryptokey = DRXEncrypter.EncryptWithCert(cert, key);
Console.WriteLine("Encrypted Decryption Key: " + cryptokey);
string decrypted = DRXEncrypter.DecryptBody(encrypted, cryptokey, cert);
Console.WriteLine("Decrypted Body: " + decrypted);
Console.WriteLine("Output String: " + decrypted + ".");
Here is the code from the crypto provider class I've written. I've been stuck on this issue for hours, so it would be great if someone could help me out.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.IO;
namespace CoreDRXEditor
{
public class DRXEncrypter
{
private byte[] Salt = Encoding.ASCII.GetBytes("81PO9j8I1a94j");
private string EncryptionKey;
private const bool UseOAEP = true;
public DRXEncrypter(string EncryptionKey)
{
this.EncryptionKey = EncryptionKey;
}
public static string EncryptBody(string body, string encryptionkey)
{
// Use the plaintext master key to encrypt the body.
DRXEncrypter enc = new DRXEncrypter(encryptionkey);
// Encrypt the body.
return enc.Encrypt(body);
}
public static int GetMaxKeySize(X509Certificate2 cert)
{
RSACryptoServiceProvider csp = cert.PublicKey.Key as RSACryptoServiceProvider;
return csp.KeySize;
}
public static string DecryptBody(string body, string encryptionkey, X509Certificate2 cert)
{
// Decrypt the encrypted encryption key with the certificate.
string DecryptedKey = Convert.ToBase64String(DecryptWithCert(cert, encryptionkey));
// Create a new DRXEncrypter using the decrypted encryption key to decrypt the body.
DRXEncrypter enc = new DRXEncrypter(DecryptedKey);
// Return the decrypted body.
return enc.Decrypt(body);
}
public static string GenerateEncryptionKey(int KeyLength)
{
using (RandomNumberGenerator rng = new RNGCryptoServiceProvider())
{
byte[] CryptoBytes = new byte[KeyLength];
rng.GetBytes(CryptoBytes);
return Convert.ToBase64String(CryptoBytes);
}
}
public static X509Certificate2 GetCertificate(string title, string message)
{
X509Store cstore = new X509Store(StoreLocation.CurrentUser);
cstore.Open(OpenFlags.ReadOnly);
X509CertificateCollection certs = X509Certificate2UI.SelectFromCollection(cstore.Certificates, title, message, X509SelectionFlag.SingleSelection);
if (certs.Count == 1)
{
X509Certificate2 mcert = certs[0] as X509Certificate2;
return mcert;
}
else
{
return null;
}
}
public static string EncryptWithCert(X509Certificate2 cert, string PlainText)
{
RSACryptoServiceProvider csp = cert.PublicKey.Key as RSACryptoServiceProvider;
byte[] PlainBytes = Convert.FromBase64String(PlainText);
// This converts the plain text into a byte array and then encrypts the raw bytes.
byte[] CryptoBytes = csp.Encrypt(PlainBytes, UseOAEP);
// This converts the encrypted bytes into a Base64 string.
string ReturnString = Convert.ToBase64String(CryptoBytes);
return ReturnString;
}
public static byte[] DecryptWithCert(X509Certificate2 cert, string EncryptedText)
{
RSACryptoServiceProvider csp = cert.PrivateKey as RSACryptoServiceProvider;
//CspParameters csps = new CspParameters();
byte[] EncryptedBytes = Convert.FromBase64String(EncryptedText);
// This converts the encrypted, Base64 encoded byte array from EncryptWithCert() to a byte[] and decrypts it.
byte[] CryptoBytes = csp.Decrypt(EncryptedBytes, UseOAEP);
return CryptoBytes;
}
public string Encrypt(string PlainText)
{
RijndaelManaged Algorithm = null;
string Output = null;
try
{
Rfc2898DeriveBytes PrivateKey = new Rfc2898DeriveBytes(this.EncryptionKey, this.Salt);
Algorithm = new RijndaelManaged();
Algorithm.Key = PrivateKey.GetBytes(Algorithm.KeySize / 8);
Algorithm.Padding = PaddingMode.PKCS7;
ICryptoTransform Encryption = Algorithm.CreateEncryptor(Algorithm.Key, Algorithm.IV);
using (MemoryStream msa = new MemoryStream())
{
msa.Write(BitConverter.GetBytes(Algorithm.IV.Length), 0, sizeof(int));
msa.Write(Algorithm.IV, 0, Algorithm.IV.Length);
using (CryptoStream csa = new CryptoStream(msa, Encryption, CryptoStreamMode.Write))
{
using (StreamWriter swa = new StreamWriter(csa))
{
swa.Write(PlainText);
}
}
Output = Convert.ToBase64String(msa.ToArray());
}
}
finally
{
if (Algorithm != null)
{
Algorithm.Clear();
}
}
return Output;
}
public string Decrypt(string EncryptedText)
{
RijndaelManaged Algorithm = null;
string Output = null;
try
{
Rfc2898DeriveBytes PrivateKey = new Rfc2898DeriveBytes(this.EncryptionKey, this.Salt);
byte[] KeyBytes = Convert.FromBase64String(EncryptedText);
using (MemoryStream msb = new MemoryStream(KeyBytes))
{
Algorithm = new RijndaelManaged();
Algorithm.Key = PrivateKey.GetBytes(Algorithm.KeySize / 8);
Algorithm.IV = ReadByteArray(msb);
Algorithm.Padding = PaddingMode.PKCS7;
ICryptoTransform Decryption = Algorithm.CreateDecryptor(Algorithm.Key, Algorithm.IV);
using (CryptoStream csb = new CryptoStream(msb, Decryption, CryptoStreamMode.Read))
{
using (StreamReader srb = new StreamReader(csb))
{
Output = srb.ReadToEnd();
}
}
}
}
finally
{
if (Algorithm != null)
{
Algorithm.Clear();
}
}
return Output;
}
public static string Sha512(string ToHash)
{
using (SHA512 SHA = new SHA512Managed())
{
byte[] HashByte = Encoding.UTF8.GetBytes(ToHash);
byte[] HashBytes = SHA.ComputeHash(HashByte);
string Hash = System.Text.Encoding.UTF8.GetString(HashBytes, 0, HashBytes.Length);
return Hash;
}
}
public static string Base64Encode(string data)
{
byte[] str = Encoding.UTF8.GetBytes(data);
return Convert.ToBase64String(str);
}
public static string Base64Decode(string data)
{
byte[] str = Convert.FromBase64String(data);
return Encoding.UTF8.GetString(str);
}
private byte[] ReadByteArray(Stream st)
{
byte[] Length = new byte[sizeof(int)];
st.Read(Length, 0, Length.Length);
byte[] Buffer = new byte[BitConverter.ToInt32(Length, 0)];
st.Read(Buffer, 0, Buffer.Length);
return Buffer;
}
}
}
I've been arguing with this today with smartcards (or more accurately, a Yubikey Neo with the smartcard PIV applet enabled); using this code:
var encryptor = (RSACryptoServiceProvider)c.PublicKey.Key;
var decryptor = (RSACryptoServiceProvider)c.PrivateKey;
var encrypt = encryptor.Encrypt(bytes, RSAEncryptionPadding.Pkcs1);
var decrypt = decryptor.Decrypt(encrypt, RSAEncryptionPadding.Pkcs1);
I've found that it matters what padding algo I use. If I use PKCS1 padding, everything works. If I use OaepSHA1, I get the Error while decoding [...] error. If I use anything else (e.g., OaepSHA256) I get a Not supported error.
I can only conclude that my smartcard doesn't properly support OAEP SHA1, but padding with PKCS#1 everything is good.
Even if this answers what you already know, it may be useful as another datapoint for anyone else coming along using smartcards.
Make sure your key size is not too small or too large.
See comments from MSDN
The RSACryptoServiceProvider supports key sizes from 384 bits to 16384
bits in increments of 8 bits if you have the Microsoft Enhanced
Cryptographic Provider installed. It supports key sizes from 384 bits
to 512 bits in increments of 8 bits if you have the Microsoft Base
Cryptographic Provider installed.
So you might need to pad short key strings with some bytes to get the minimum key length
Ok, I managed to check this and from what I can see, I have problems with some certificates. I am not sure why some certificates work while others don't. It would be good to know why some certificates fail in this case?
Anyway, I created a new self signed certificate using windows "Manage File Encryption Certificates" and used this certificate, and all seems to work.
The out put from your code.
Encryption Key: aUc/GXWDoh2LktaEGeCJfju1dHP118yD/fzfT0iJLuhOq2QeyGpG6m3aBHaxvdH0ufeXRHbMjmlmPgIL/bhABzkT2C5Oa6ZhY3IFXb5t7JXZ3AtUunvtNAnRyFJ7MzklrSZGgQ
vF67DSNfIVE17doKt6j6mkCpSco56ooZCrOs2Mp3vSXqNjvjiwMEfQbk41aYUNVNVNlBGhdNQCIZIAKezQCUpWqzn2II27FIDfqDIEW4ieyzpXC05GzUlGXDxFOiFUPk3n0Y94vgeF8AlCD74eyZtz
WQ==
Encrypted Body: EAAAANS/W7+GGRbT1q5NCYvZlDZYtxaA8g55HzUqP5qxhenn
Encrypted Decryption Key: vc/tcsApmY1503BFi7oSu/RDvZivA1Ed58KJuLoEC6eE8q0BIa6ye2JvtXyxkVbzzL0MA51pZ2ZhMIsfCnBsEDjCgy+JLTZTGM1Mv+em9frFUKb0zHbICnPUa/3H
yd1yOWsdn5ws19QN2dzC6eau+ExhT2T/vyZO4Nf9NdHKnB8n2yB1rrQ/T+N2EYCNH/AVPDAsme6JG7k9Od2XIipBXMyCgXgWYZmQusq+JQjA9d3c4CrQYcg/ERF+K3oZv/gPicBkAR5taxwSxAajGg
bpkJNsbhTMHTN9bOn333qZ6ojlo5e882baZXuZWPr9qtj1b7ONoOyuSx/OvGKjt93BQg==
Decrypted Body: Hello World!
Output String: Hello World!.
Hope that helps
I am trying to solve an encryption issue I am having between php and c#.
I have encrypted data using the following php and openssl operation.
$encrypt_method = "AES-256-CBC";
$secret_key = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
$secret_iv = 'XXXXXXXXXXXXXXXX';
$key = hash ('sha256', $secret_key);
$iv = substr (hash ('sha256', $secret_iv), 0, 16);
$output = openssl_encrypt ($string, $encrypt_method, $key, 0, $iv);
$output = base64_encode ($output);
I have tried a couple of methods in C# to decrypt but this is what I am trying now.
public string Encrypt_Decrypt(string action, string value) {
string secretKey = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
string secretIV = "XXXXXXXXXXXXXXXX";
string key = Hash(secretKey);
string iv = Hash(secretIV).Substring(0,16);
string retValue = "";
if (action == "encrypt") {
retValue = EncryptString(value, key, iv);
}
else if (action == "decrypt") {
retValue = DecryptString(value, key, iv);
}
}
// Hash to match php hash function
public static string Hash(string unhashedString) {
return BitConverter.ToString(new SHA256CryptoServiceProvider().ComputeHash(Encoding.Default.GetBytes(unhashedString))).Replace("-", String.Empty).ToLower();
}
public static string DecryptString(string cipherData, string keyString, string ivString) {
byte[] key = Encoding.UTF8.GetBytes(keyString);
Console.WriteLine(key.Length);
byte[] iv = Encoding.UTF8.GetBytes(ivString);
Console.WriteLine(iv.Length);
byte[] cipherCrypt = Convert.FromBase64String(cipherData);
for (int i = 0; i < cipherCrypt.Length; i++) {
Console.Write(cipherCrypt[i] + " ");
}
try {
RijndaelManaged crypto = new RijndaelManaged();
crypto.Key = key;
crypto.IV = iv;
crypto.Mode = CipherMode.CBC;
crypto.KeySize = 256;
crypto.BlockSize = 128;
crypto.Padding = PaddingMode.None;
ICryptoTransform decryptor = crypto.CreateDecryptor(crypto.Key, crypto.IV);
using (MemoryStream memStream = new MemoryStream(cipherCrypt)) {
using (CryptoStream cryptoStream = new CryptoStream(memStream, decryptor, CryptoStreamMode.Read)) {
using (StreamReader streamReader = new StreamReader(cryptoStream)) {
return streamReader.ReadToEnd();
}
}
}
}
catch (CryptographicException e) {
Console.WriteLine("A Cryptographic error occurred: {0}", e.Message);
return null;
}
}
I have tried a couple different encoding types when getting the byte[] for the operation.
I keep getting the following error:
Specified key is not a valid size for this algorithm.
Not sure what I am missing. Any help is appreciated.
Also, I already read through this and tried what the solution suggestion recommended. I got the same resulting error.
UPDATE - 01
I have updated the code here to reflect the code I have changed.
The key length is 32,
The iv length is 16,
The data coming in at "cipherData" is length 32,
When "cipherData" goes through "FromBase64String(cipherData)" it comes out as a 24 byte array. This is causing an issue for the decryptor which wants a 32 byte array.
There are obviously problems with the key size. The code between PHP and C# seem to match. The problem seems to be that the code is wrong in both cases.
Let's see how long the key actually is:
Start with a 32 byte key (non-encoded).
Hash the key with SHA-256: 32 bytes (non-encoded).
Encode to hex (integrated into PHP's hash() function by default): 64 bytes.
AES only supports the following key sizes: 16, 24 and 32 bytes. openssl_encrypt() will only use the first 32 bytes of the hex key silently. So, you need to use the first 32 bytes in C#.
Note that openssl_encrypt() takes an options argument which denotes that the output is Base64 when OPENSSL_RAW_DATA is not set. It means that the PHP output was encoded twice with Base64. So you need to decode it twice in C#.
I am trying to encrypt the same data using C# and Java. If the data is more than 7 bytes then Java and C#'s encrypted value are not identical.
Input 1: a
java output: FrOzOp/2Io8=
C# output: FrOzOp/2Io8=
Input 2: abc
j : H9A/ahl8K7I=
c#: H9A/ahl8K7I=
Input 3: aaaaaaaa (Problem)
j : Gxl7e0aWPd7j6l7uIEuMxA==
c#: Gxl7e0aWPd7sf1xR6hK4VQ==
Here is the implementation of C# and Java methods.
C# code:
public String saltTxt = "12345678";
public String Encrypt(String txt)
{
byte[] data = Encrypt(Encoding.UTF8.GetBytes(txt));
DESCryptoServiceProvider alg = new DESCryptoServiceProvider();
alg.Key = Encoding.UTF8.GetBytes(saltTxt.ToCharArray(), 0, cprovider.KeySize / 8);
alg.IV = new byte[8];
MemoryStream ms = new MemoryStream();
CryptoStream stem = new CryptoStream( ms, cprovider.CreateEncryptor(),CryptoStreamMode.Write);
stem.Write(txt, 0, txt.Length);
stem.FlushFinalBlock();
data = ms.ToArray();
return Convert.ToBase64String(data);
}
Java Code:
public String saltTxt = "12345678";
public String Encrypt(String str) {
try {
KeySpec myKey = new DESKeySpec(saltTxt.getBytes("UTF8"));
SecretKey key = SecretKeyFactory.getInstance("DES").generateSecret(myKey);
Cipher ecipher = Cipher.getInstance("DES");
ecipher.init(Cipher.ENCRYPT_MODE, key);
byte[] data = str.getBytes("UTF8");
byte[] crypt = ecipher.doFinal(data);
return new BASE64Encoder().encode(crypt);
} catch (Exception ex) {
}
return null;
}
Any idea why it's not working as expected?
The problem was in mode of encryption.
SunJCE provider uses ECB as the default mode, and PKCS5Padding as the default padding scheme for DES, DES-EDE and Blowfish ciphers. (JCA Doc)
and
In .Net, The default operation mode for the symmetric algorithm is CipherMode.CBC and default padding is PaddingMode.PKCS7. (msdn..SymmetricAlgorithm)
The following changes resolve the problem.
// in C#
DESCryptoServiceProvider alg = new DESCryptoServiceProvider();
alg.Mode = CipherMode.ECB; // specified
or
// in java
chiper = Cipher.getInstance("DES/CBC/PKCS5Padding");
don't change in both sides.
You're probably seeing ISO 10126 padding, which appends random bytes to the plaintext to fill it up t oa multiple of the block size.
This behavior is by design.
The code (Java/Android) bellow worke for me.
I used the same approach on C#.
public static String Cripto(String Password)
{
String PasswordCripto = "";
try
{
String encryptionKey = "anyEncryptionString";
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
messageDigest.update(encryptionKey.getBytes("UTF-8"), 0, encryptionKey.length());
byte[] encryptionKeyBytes = messageDigest.digest();
SecretKeySpec Key = new SecretKeySpec(encryptionKeyBytes,"DESede");
Cipher cipher = Cipher.getInstance("DESEDE/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, Key);
byte[] encryptedBytes = cipher.doFinal(Password.getBytes("UTF-8"));
PasswordCripto = new String(Base64.encode(encryptedBytes, Base64.DEFAULT), "UTF-8");
} catch(Exception e) { }
return PasswordCripto ;
}