I am building a iPhone app which uses a c# web service. My iPhone app takes in some data and encrypts it and passes it to the web service. How do I decrypt the data in C#?
My iPhone app contains the following code:
NSString *pString = #"Some string to be encoded";
NSString *key = #"My encryption key";
NSData *pData = [pString dataUsingEncoding:NSUTF8StringEncoding];
pData = [pData AES256EncryptWithKey:key];
NSString *pID = [pData base64EncodedStringWithOptions:NSDataBase64Encoding76CharacterLineLength];
EDIT: The data is already stored in the web service so I can't readily change the encryption approach. The C# application is not on the server so there is no possibility of compromising the key.
I have tried the following C# code to decrypt the data:
static string DecryptString(string encryptedText, string key)
{
byte[] encryptedString = Convert.FromBase64String(encryptedText);
byte[] encryptionKey = Encoding.UTF8.GetBytes(key.Substring(0, 32));
using (var provider = new AesCryptoServiceProvider())
{
provider.Mode = CipherMode.CBC;
provider.Padding = PaddingMode.PKCS7;
provider.Key = encryptionKey;
using (var ms = new MemoryStream(encryptedString))
{
// Read the first 16 bytes which is the IV.
byte[] iv = new byte[16];
ms.Read(iv, 0, 16);
provider.IV = iv;
using (var decryptor = provider.CreateDecryptor())
{
using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
{
using (var sr = new StreamReader(cs))
{
return sr.ReadToEnd();
}
}
}
}
}
}
However, I get the following exception:
System.Security.Cryptography.CryptographicException was unhandled
HResult=-2146233296 Message=Padding is invalid and cannot be
removed.
The encryptedText received by DecryptString is 80 bytes in length.
The sample ObjC code uses by default CBC modem, PKCS#7 padding and a default iv of 16 0x00 bytes.
The C# also uses CBC mode and PKCS#7 padding. The decryption code expects a 16-byte iv pre-pended to the encrypted data and that does not exist.
byte[] iv = new byte[16];
ms.Read(iv, 0, 16);
provider.IV = iv;
This needs to be changed so that iv is set to an array of 16 0x00 bytes and the ms.Read(iv, 0, 16) statement needs to be deleted so the decrypt function gets all of the encrypted data.
Notes:
Using a devault anything in encryption is a bad idea, always provide the correect length data.
Authentication of the encrypted data needs should be added so that it can be determined if there an incorrect key or the data has been tampered with.
There really should be a version number and a random IV used and prepended to the encrypted so you should really consider correcting this. This demonstrates why a version number generally needs to be provided and used.
RNCryptor covers the above issues.
The handling of the encryption key also needs to be considered so that is is as secure as necessary.
You need to first decode the base-64 encoded string to a byte[] - see Convert.FromBase64String(). Then you need to use the Aes class to decrypt it - there's an example on its documentation page.
Related
I try to encrypt a string (json) with Rijndael in C# and come up with a string, which I can offer to a PHP web service. This web service in turn decodes the string using the IV and masterkey (known to them). I have to write the C# code that can talk to the PHP service, I do not control/own the PHP service.
The PHP code for encrypting is as follows:
function encrypt($plaintext) {
$masterkey = 'masterKeyOfLength29Characters';
$td = mcrypt_module_open(MCRYPT_RIJNDAEL_256, '', MCRYPT_MODE_CBC, '');
$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
mcrypt_generic_init($td, $masterkey, $iv);
$crypttext = mcrypt_generic($td, $plaintext);
mcrypt_generic_deinit($td);
return base64_encode($iv.$crypttext);
}
$param = array("key" => "value");
$encryptedString = rawurlencode(encrypt(json_encode($param)))
The code above I'll have to convert to C#, so I can encrypt my JSON and offer it to the PHP web service.
There are two problems. The first was with the masterkey length, the second (might be related) is with the rawurlencode of the encrypted data (hard for me to test at this point).
var masterkey = "masterKeyOfLength29Characters";
var data = EncryptData(json, masterkey);
// Some code to URL Encode the data, I haven't gotten as far to test this
// since I can't encrypt with the key used in PHP, so I can't call the service
// to test the encoded string from my C# code.
data = HttpUtility.UrlEncode(data);
data = data.Replace("+", "%20");
public static string EncryptData(string json, string encryptionKey) {
Rijndael rj = Rijndael.Create();
rj.Mode = CipherMode.CBC;
rj.Padding = PaddingMode.PKCS7;
rj.BlockSize = 256;
rj.KeySize = 256;
rj.Key = Encoding.UTF8.GetBytes(encryptionKey); // ERROR here
rj.GenerateIV();
var encryptedJSON = EncryptStringToBytes(json, rj.Key, rj.IV);
var r1 = Convert.ToBase64String(rj.IV);
var r2 = Convert.ToBase64String(encryptedJSON);
return r1 + r2;
}
The EncryptStringToBytes does some checks and uses this code (plucked from the many examples on the internet):
using (Rijndael rijAlg = Rijndael.Create()) {
// Basically I do the same here as above, and I could also generate
// the IV here, but then I'd had to return it too. I know I can clean this
// code up quite a bit, but I'd rather focus on getting it to work first ;)
rijAlg.Mode = CipherMode.CBC;
rijAlg.Padding = PaddingMode.PKCS7;
rijAlg.BlockSize = 256;
rijAlg.KeySize = 256;
rijAlg.Key = Key;
rijAlg.IV = IV;
ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);
using (MemoryStream msEncrypt = new MemoryStream()) {
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) {
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt)) {
swEncrypt.Write(plainText);
}
encrypted = msEncrypt.ToArray();
}
}
}
The error I'll get:
Specified key is not a valid size for this algorithm.
So, the problems in short:
1) How come the PHP code accepts the key of length 29 in the Rijndael 256 (CBC mode), and my C# doesn't? I've played around with the Mode, added the Padding later, set the KeySize (was 256 default already), and I just can't see what I'm doing wrong here.
2) When I use a key of length 32, this one is accepted and my code works. I can also decrypt it in C# (but can't test this in PHP). I would like to solve problem 1, and then continue on problem 2, but maybe someone can give me some understanding here. The encrypted string contains 1 '=' in the IV, and 2x '==' (at the end) in the encrypted json. I've read about padding and such, but I was wondering why no '=' signs are visible in the PHP examples I received. Again, maybe after fixing problem 1 this won't be an issue.
Many thanks for reading and I hope I'm not being too stupid here. After a day of trying yesterday I kind of get the feeling I've tried many different approaches and non seem to work.
Just thought I'd add a tiny bit to what #artjom-b has said.
Firstly, it does work :-)
But in addition you need to change your
rj.Padding = PaddingMode.PKCS7
to use
rj.Padding = PaddingMode.Zeros
Also, technically, your two functions aren't returning the same thing. The PHP returns base 64 of two concatenated bits of binary data whereas the C# returns a concatenation of separate b64 strings. The result will be different in the second half of the returned string.
EDIT: The rough and ready decryption routine:
public string DecryptRijndael(byte[] cipherText, string password, byte[] iv)
{
var key = new byte[32];
Encoding.UTF8.GetBytes(password).CopyTo(key, 0);
var cipher = new RijndaelManaged();
cipher.Mode = CipherMode.CBC;
cipher.Padding = PaddingMode.None;
cipher.KeySize = 256;
cipher.BlockSize = 256;
cipher.Key = key;
cipher.IV = iv;
byte[] plain;
using (var decryptor = cipher.CreateDecryptor())
{
using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Write))
{
cs.Write(cipherText, 0, cipherText.Length);
cs.FlushFinalBlock();
plain = ms.ToArray();
}
}
}
return Encoding.UTF8.GetString(plain);
}
NB: All the caveats and warnings from Artjom B still apply.
You're using an old version of PHP which happily accepts keys that have an invalid length. Rijndael supports key sizes of 16, 24 and 32 bytes and nothing inbetween. The mcrypt extension in PHP silently pads the key with 0x00 bytes up to the next valid key size which is 32 bytes. You will have to do the same thing in C#:
byte[] key = new byte[32];
byte[] password = Encoding.UTF8.GetBytes(encryptionKey);
Array.Copy(password, key, password.Length);
rj.Key = key;
Keep in mind that in order to provide some security a key must have high entropy. A password is not a key and therefore doesn't provide much entropy, because of the limited character set and possible use words. Always derive a key from the password with available derivation functions such as Argon2, scrypt, bcrypt or PBKDF2 with a high cost factor/iteration count and a random salt.
You should also add authentication to your ciphertexts. Otherwise, an attacker might change the ciphertext without you knowing it. This either done by using an authenticated mode like GCM/EAX or running HMAC over the ciphertext to produce the authentication tag.
I am using aes for encryption/decryption of the text but sometime its giving me exact value after decryption while some times i am getting error. I referred to different answers over but didn't get the root cause of my problem .
private static string DecryptStringFromBytes(byte[] cipherText, byte[] key, byte[] iv)
{
// Declare the string used to hold the decrypted text.
string plaintext = null;
// Create an RijndaelManaged object
// with the specified key and IV.
using (var rijAlg = new System.Security.Cryptography.RijndaelManaged())
{
//Settings
rijAlg.Mode = System.Security.Cryptography.CipherMode.CBC;
rijAlg.Padding = System.Security.Cryptography.PaddingMode.PKCS7;
rijAlg.FeedbackSize = 128;
rijAlg.Key = key;
rijAlg.IV = iv;
// Create a decrytor to perform the stream transform.
var decryptor = rijAlg.CreateDecryptor(rijAlg.Key, rijAlg.IV);
try
{
// Create the streams used for decryption.
using (var msDecrypt = new System.IO.MemoryStream(cipherText))
{
using (var csDecrypt = new System.Security.Cryptography.CryptoStream(msDecrypt, decryptor, System.Security.Cryptography.CryptoStreamMode.Read))
{
using (var srDecrypt = new System.IO.StreamReader(csDecrypt))
{
// Read the decrypted bytes from the decrypting stream
// and place them in a string.
plaintext = srDecrypt.ReadToEnd();
}
}
}
}
catch
{
plaintext = "keyError";
}
}
return plaintext;
}
It throws error "Padding is invalid and cannot be removed"
I seen some suggestion like to remove padding but it didn't seems proper solution.
I am not able to find the cause behind this as sometimes it runs perfectly without throwing error .
Any help or suggestion is really appreciated.
For Encryption - The encryption is being done on to client side in js and passing encryped text to server.
var key = CryptoJS.enc.Utf8.parse("16 digit number here");
var iv = CryptoJS.enc.Utf8.parse("16 digit number here");
var EncryptedString = CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse("entered string to encrypt"), key,
{ keySize: 128 / 8, iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });
By using a similar encryption routine in .NET to the decryption function you give I was able to successfully round-trip plaintext to ciphertext and back to plaintext, so it seems that the decryption function itself is ok. It therefore seems very likely that the key and/or IV you're using to encrypt does not match byte-for-byte with the values you're using when decrypting.
Given that your encryption code is using the UTF-8 encoded version of string values to form the key and IV, it would be worth doing the same in your decryption code (using Encoding.UTF8.GetBytes()).
However, it would be worth noting that whilst this might resolve the immediate issue, it is in itself a bad practice to use string values directly for keys without some form of key-derivation process (e.g. Rfc2898DeriveBytes), and IVs should be generated randomly for every application of the encryption function. Those are just a few issues with your use of cryptography (and are independent of whether the code works or not).
Here is the code used to encrypt in coldfusion
<cfset strBase64Value = encrypt(strValue,24 character key,AES) />
It is generating encrypted values like 714FEA9A9A2184769CA49D5133F08580 which seems odd to me considering it is only uppercase and numbers.
What C# library should I use to properly decrypt it ?
Also looking at this information, it seems that by default it uses the UUEncode algorithm to encode.
Should I ask the encrypter to use Base64 as encoding parameter ?
It is generating encrypted values like 714FEA9A9A2184769CA49D5133F08580
Then they are using "Hex", not the default "UUEncode". Either "hex" or "base64" is fine. As long as you both agree upon the encoding, it does not really matter.
You can use RijndaelManaged to decrypt the strings. However, the default encryption settings for ColdFusion and C# differ slightly. With the encrypt function:
"AES" is short for "AES/ECB/PKCS5Padding"
"ECB" mode does not use an IV
Key strings are always base64 encoded
NB: Despite the name difference, for the SUN provider, PKCS5Padding (CF/Java) corresponds to PaddingMode.PKCS7 (C#). As mentioned in this thread, the "... SUN provider in Java indicate[s] PKCS#5 where PKCS#7 should be used - "PKCS5Padding" should have been "PKCS7Padding". This is a legacy from the time that only 8 byte block ciphers such as (triple) DES symmetric cipher were available."
So you need to ensure your C# settings are adjusted to match. With that in mind, just decode the encrypted text from hex and the key string from base64. Using the slightly ugly example in the API, just adjust the algorithm settings to match those used by the encrypt() function:
Encrypt with ColdFusion
<cfscript>
plainText = "Nothing to see";
// 128 bit key base64 encoded
keyInBase64 = "Y25Aju8H2P5DR8mY6B0ezg==";
// "AES" is short for "AES/ECB/PKCS5Padding"
encryptedText = encrypt(plainText, keyInBase64, "AES", "hex");
WriteDump( encryptedText );
// result: 8889EDF02F181158AAD902AB86C63951
</cfscript>
Decrypt with C#
byte[] bytes = SomeMethodToConvertHexToBytes( encryptedText );
byte[] key = Convert.FromBase64String( keyInBase64 );
string decryptedText = null;
using (RijndaelManaged algorithm = new RijndaelManaged())
{
// initialize settings to match those used by CF
algorithm.Mode = CipherMode.ECB;
algorithm.Padding = PaddingMode.PKCS7;
algorithm.BlockSize = 128;
algorithm.KeySize = 128;
algorithm.Key = key;
ICryptoTransform decryptor = algorithm.CreateDecryptor();
using (MemoryStream msDecrypt = new MemoryStream(bytes))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
{
decryptedText = srDecrypt.ReadToEnd();
}
}
}
}
Console.WriteLine("Encrypted String: {0}", encryptedText);
Console.WriteLine("Decrypted String: {0}", decryptedText);
Keep in mind you can (and probably should) adjust the settings, such as using the more secure CBC mode instead of ECB. You just need to coordinate those changes with the CF developer.
If anyone had similar problem with JAVA I just implemented encryption and decryption of string previously encrypted/decrypted in coldfusion with "Hex" and "tripledes". Here is my code:
private static final String PADDING = "DESede/ECB/PKCS5Padding";
private static final String UTF_F8 = "UTF-8";
private static final String DE_SEDE = "DESede";
private String secretKey;
public String encrypt(String message) throws Exception {
secretKey = getSecretKey();
final byte[] secretBase64Key = Base64.decodeBase64(secretKey);
final SecretKey key = new SecretKeySpec(secretBase64Key, DE_SEDE);
final Cipher cipher = Cipher.getInstance(PADDING);
cipher.init(Cipher.ENCRYPT_MODE, key);
final byte[] plainTextBytes = message.getBytes();
final byte[] cipherText = cipher.doFinal(plainTextBytes);
return Hex.encodeHexString(cipherText);
}
public String decrypt(String keyToDecrypt) throws Exception {
secretKey = getSecretKey();
byte[] message = DatatypeConverter.parseHexBinary(keyToDecrypt);
final byte[] secretBase64Key = Base64.decodeBase64(secretKey);
final SecretKey key = new SecretKeySpec(secretBase64Key, DE_SEDE);
final Cipher decipher = Cipher.getInstance(PADDING);
decipher.init(Cipher.DECRYPT_MODE, key);
final byte[] plainText = decipher.doFinal(message);
return new String(plainText, UTF_F8);
}
Somebody asked me how I would decrypt a given AES 256-bit encrypted string if I knew the secret key. I'm not very familiar with encryption, so I sat down to look into the problem.
I found this example on MSDN, and tried to modify it to do only the Decrypt:
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
internal class AesExample
{
public static void Main()
{
var encryptedString = "U2FsdGVkX1/cHT8XuHCfpw0AV4jpaO8JfLqUeCRJqjY=";
var secret = "SPARKY";
// I know this is not the correct way to get my input byte arrays...
// Just illustrating that I DO need byte arrays.
var encryptedBytes = Encoding.UTF8.GetBytes(encryptedString);
var secretBytes = Encoding.UTF8.GetBytes(secret);
try
{
using (var aes = new AesManaged())
{
aes.Key = secretBytes;
// Decrypt the bytes to a string.
var decryptedString = Decrypt(encryptedBytes, aes.Key, aes.IV);
//Display the original data and the decrypted data.
Console.WriteLine("Encrypted: {0}", encryptedString);
Console.WriteLine("Decrypted: {0}", decryptedString);
}
}
catch (Exception e)
{
Console.WriteLine("Error: {0}", e.Message);
}
}
private static string Decrypt(byte[] cipherText, byte[] key, byte[] iv)
{
// Declare the string used to hold
// the decrypted text.
string plaintext;
// Create an AesManaged object
// with the specified key and IV.
using (var aes = new AesManaged())
{
aes.Key = key;
aes.IV = iv;
// Create a decrytor to perform the stream transform.
var decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
// Create the streams used for decryption.
using (var msDecrypt = new MemoryStream(cipherText))
{
using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (var srDecrypt = new StreamReader(csDecrypt))
{
// Read the decrypted bytes from the decrypting stream
// and place them in a string.
plaintext = srDecrypt.ReadToEnd();
}
}
}
}
return plaintext;
}
}
Of course as soon as I hit the following line, a CryptographicExcetion is thrown with the message "Specified key is not a valid size for this algorithm."
==> aes.Key = secretBytes
Someone suggested taking a SHA1 hash of the secret and trimming that to 20 byes. I tried that, and I started getting a new CryptographicException with the message "Length of the data to decrypt is invalid."
So, I have a few questions:
1) Is this even possible given only the encrypted text and secret key?
2) If so, are them some base assumptions one would need to make, like the CipherMode? I was reading that the ECB mode doesn't have a initialization vector. That's why I ask.
3) What would I need to do to put the inputs (encrypted text and secret key) into the correct Byte[] format for the decryption to work?
Thanks!
You probably need more information to make this work. To answer your specific questions:
Yes, except that you don't have the secret key. "SPARKY" is not a valid AES key, as DavidH mentions, though passwords are routinely used to derive secret keys through what are called key derivation functions. You could try running your password through Rfc2898DeriveBytes (a popular KDF in .NET) to derive different AES keys that might work, but it too takes parameters that you apparently don't have. You could also try various SHA hash digests of your password, though again 20 bytes is not a valid AES key - you need a 16, 24 or 32 byte key.
If you don't have an IV, then yes, you'll have to assume the encryption uses ECB. (But note that in general you should never use ECB mode.)
Your encrypted string appears to be encoded using base64. Converting it to a byte array is simple enough in .NET using Convert.FromBase64String(encryptedString);.
This sounds like a fun exercise, but you're probably just going to end up frustrated without a bit more information.
AES key lengths are 128, 192, and 256 bit depending on the cipher you want to use. You must ensure that your string is the appropriate length of bytes.
C#
string keystr = "0123456789abcdef0123456789abcdef";
string plainText = "www.bouncycastle.org";
RijndaelManaged crypto = new RijndaelManaged();
crypto.KeySize = 128;
crypto.Mode = CipherMode.CBC;
crypto.Padding = PaddingMode.PKCS7;
crypto.Key = keystr.ToCharArray().Select(c=>(byte)c).ToArray();
// get the IV and key for writing to a file
byte[] iv = crypto.IV;
byte[] key = crypto.Key;
// turn the message into bytes
// use UTF8 encoding to ensure that Java can read in the file properly
byte[] plainBytes = Encoding.UTF8.GetBytes(plainText.ToCharArray());
// Encrypt the Text Message using AES (Rijndael) (Symmetric algorithm)
ICryptoTransform sse = crypto.CreateEncryptor();
MemoryStream encryptedFs = new MemoryStream();
CryptoStream cs = new CryptoStream(encryptedFs, sse, CryptoStreamMode.Write);
try
{
cs.Write(plainBytes, 0, plainBytes.Length);
cs.FlushFinalBlock();
encryptedFs.Position = 0;
string result = string.Empty;
for (int i = 0; i < encryptedFs.Length; i++)
{
int read = encryptedFs.ReadByte();
result += read.ToString("x2");
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
finally
{
encryptedFs.Close();
cs.Close();
}
}
Java:
private String key = "0123456789abcdef0123456789abcdef";
private String plainText = "www.bouncycastle.org";
cipherText = performEncrypt(Hex.decode(key.getBytes()), plainText);
private byte[] performEncrypt(byte[] key, String plainText)
{
byte[] ptBytes = plainText.getBytes();
final RijndaelEngine rijndaelEngine = new RijndaelEngine();
cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(rijndaelEngine));
String name = cipher.getUnderlyingCipher().getAlgorithmName();
message("Using " + name);
byte[]iv = new byte[16];
final KeyParameter keyParameter = new KeyParameter(key);
cipher.init(true, keyParameter);
byte[] rv = new byte[cipher.getOutputSize(ptBytes.length)];
int oLen = cipher.processBytes(ptBytes, 0, ptBytes.length, rv, 0);
try
{
cipher.doFinal(rv, oLen);
}
catch (CryptoException ce)
{
message("Ooops, encrypt exception");
status(ce.toString());
}
return rv;
}
C# produces: ff53bc51c0caf5de53ba850f7ba08b58345a89a51356d0e030ce1367606c5f08
java produces: 375c52fd202696dba679e57f612ee95e707ccb05aff368b62b2802d5fb685403
Can somebody help me to fix my code?
In the Java code, you do not use the IV.
I am not savvy enough in C# to help you directly, but I can give some information.
Rijndael, aka "the AES", encrypts blocks of 16 bytes. To encrypt a long message (e.g. your test message, when encoding, is 20 bytes long), Rijndael must be invoked several times, with some way to chain the invocations together (also, there is some "padding" to make sure that the input length is a multiple of 16). The CBC mode performs such chaining.
In CBC, each block of data is combined (bitwise XOR) with the previous encrypted block prior to being itself encrypted. Since the first block of data has no previous block, we add a new conventional "zero-th block" called the IV. The IV should be chosen as 16 random bytes. The decrypting party will need the IV. The IV needs not be secret (that's the difference between the IV and the key) so it is often transmitted along the message.
In your Java code, you do not specify the IV, you just create a variable called iv and do not use it. So the Rijndael implementation is on its own for that. Chances are that it generated a random IV. Similarly, you do not give an IV to the Rijndael implementation in the C# code. So it is quite plausible that there again a random IV was selected. But not the same than the one in the Java code, hence the distinct results.
(Note: you 20-byte input string is padded to 32 bytes. You give two "results" in hexadecimal, of length 32 bytes each. This is coherent but means that those results do not include the IV -- otherwise they would be 48-byte long.)
I think the algorithm is built in slighty different way and/or the salt key is interpered in different way.