Once again I'm tasked with converting ColdFusion code used for a single sign-on to C# and am running short on time. This one is completely different than my question that was answered here, so I'm back to being in over my head.
The original ColdFusion code was executed in a <cfscript> tag. I've replaced the src and pwd variables with abbreviated placeholders just to obscure their actual values:
//create a key to be used
src="xxx";
pwd="abc";
// Base64 Decoding the key
base64Decoder = createObject("java", "sun.misc.BASE64Decoder");
desKeyData = base64Decoder.decodeBuffer(pwd);
// Initialize the constructor of DESedeKeySpec with private key
KeySpec=createObject("java", "javax.crypto.spec.DESedeKeySpec");
KeySpec=KeySpec.init(desKeyData);
// Generate the secret key using SecretKeyFactory
keyFac=createObject("java", "javax.crypto.SecretKeyFactory").getInstance("DESede");
secretKey =keyFac.generateSecret(KeySpec);
// Get CIPHER OBJ ready to use
decodecipher = createObject("java", "javax.crypto.Cipher").getInstance("DESede/ECB/PKCS5Padding");
decodecipher.init(2, secretKey);
encodecipher = createObject("java", "javax.crypto.Cipher").getInstance("DESede/ECB/PKCS5Padding");
encodecipher.init(1, secretKey);
stringBytes = toString(src).getBytes("UTF8");
raw = encodecipher.doFinal(stringBytes);
// Base64Encoding of generated cipher
cipherText=ToBase64(raw);
I also have a document from the other party that outlines the steps for creating the single sign-on as follows:
Creating the encrypted token
Create the plain text (this corresponds to the variable src above, and that part I've done successfully in C#)
Pad the plain text
Decode the key (the key corresponds to the variable pwd above, and must be base 64 decoded; I think I've successfully gotten up to this point as well.)
Perform the encryption (use the decoded key obtained above and the plain text to do the encryption)
Encode the cipher text (url encoded)
I have the BouncyCastle libraries installed and am trying to make use of those, but I'm stuck on the actual encryption step. So far the beginning of my C# conversion looks like this (once again the token and key have abbreviated placeholders to obscure the actual values):
//steps omitted here to create src string
string token = "xxx";
string key = "abc";
byte[] decodedKeyBytes = Convert.FromBase64String(key);
I know that's not a whole lot to go on, but I've tried so many things that haven't worked that I've lost track. Eventually when I get to the piece where I'm initializing the cipher, I assume I need something like this:
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new DesEdeEngine());
Thanks very much for any suggestions/examples.
Update:
Thanks to the very helpful answer below, I was able to get this working using the following code:
string token = "xxx";
string key = "abc";
byte[] base64DecodedKeyBytes = Convert.FromBase64String(key);
byte[] inputBytesToken = System.Text.Encoding.UTF8.GetBytes(token);
// initialize for EBC mode and PKCS5/PKCS7 padding
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new DesEdeEngine());
KeyParameter param = new KeyParameter(base64DecodedKeyBytes);
cipher.Init(true, param);
// encrypt and encode as base64
byte[] encryptedBytesToken = cipher.DoFinal(inputBytesToken);
string tokenBase64 = System.Convert.ToBase64String(encryptedBytesToken);
This one is completely different
Not so much ;-) You already answered your own question.
Do not let the java code throw you. Ignoring some unused variables, it is doing exactly the same thing as encrypt() on your other thread - except with "TripleDES" instead of "Blowfish". encrypt() hides a lot of the complexity, but internally it is does the same thing - using those same java classes FWIW. That means you can use the same C# code. As you already guessed, you just need to swap out the crypto engine:
....
// initialize for EBC mode and PKCS5/PKCS7 padding
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new DesEdeEngine());
...
Update:
Just to elaborate a bit, when you use encrypt( someUTF8String, base64Key, algorithm, encoding), CF performs the same steps as your java code internally:
Decodes the key from base64, and creates a KeySpec object for the given algorithm, ie
// Base64 Decoding the key
// CF may use a different decoder, but the overall process is the same
base64Decoder = createObject("java", "sun.misc.BASE64Decoder");
....
secretKey =keyFac.generateSecret(KeySpec);
Next it extracts the UTF-8 bytes of the plain text, ie
stringBytes = toString(src).getBytes("UTF8");
CF then creates a cipher, which pads and encrypts the plain text, ie:
encodeCipher = createObject("java", "javax.crypto.Cipher").getInstance(algorithm);
encodeCipher.init(1, secretKey); // 1 - ENCRYPT_MODE
raw = encodeCipher.doFinal(stringBytes);
Finally, CF encodes the encrypted bytes as base64, ie:
cipherText=ToBase64(raw);
So as you can see, the java code and encrypt do exactly the same thing.
Related
I am developing an integration in C# which syncs Office365 distribution lists to Sendy (sendy.co), written in PHP.
In the PHP application some ID's are being encrypted and I want to achieve the same in C# so that I can communicate using their API without having to look up the 'secret' ID.
This is the code in PHP (I replaced the password) in their application that calculates these ID's:
$encrypted = openssl_encrypt($in, 'AES-256-CBC', 'API_KEY', 0, 'SECRET_PASSWORD');
$encrypted = str_replace('/', '892', $encrypted);
$encrypted = str_replace('+', '763', $encrypted);
$encrypted = str_replace('=', '', $encrypted);
I overcame this 'issue' by hosting the PHP script somewhere and calling it from my C# application, but I want to make it opensource so I would like this to be integrated in the application.
I suppose I would have to start with .NET's AesCryptoServiceProvider, but I don't seem to be able to get it right (I get exceptions about the key length and stuff).
So far I tried this:
public static string Execute()
{
// openssl_encrypt ( string $data , string $method , string $password [, int $options = 0 [, string $iv = "" ]] )
var aes = new AesCryptoServiceProvider();
aes.KeySize = 256;
// Fixed password in code
aes.Key = Encoding.UTF8.GetBytes("FIXED PASSWORD");
// API = IV
aes.IV = Encoding.UTF8.GetBytes("SENDY API KEY");
aes.Mode = CipherMode.CBC;
// Trying to encrypt "36" in this case
byte[] src = Encoding.Unicode.GetBytes("36");
// Actual encryption
using (var encrypt = aes.CreateEncryptor())
{
byte[] dest = encrypt.TransformFinalBlock(src, 0, src.Length);
// Convert byte array to Base64 strings
return Convert.ToBase64String(dest);
}
}
However this throws an exception saying the IV doesn't match the block size of the algorithm.
I suppose the openssl_encrypt method in PHP derivatives the actual IV from the given API KEY in the sample (so the $password parameter), but I can't find much documentation on it to be able to achieve the same in C#.
The size of the iv must be equal to the block size which for AES is 16-bytes, "SENDY API KEY" is only 13-bytes.
The iv should be a different random value for each encryption, just prepend it to the encrypted data for use during decryption.
Next you have specified a key size of 256-bits but the key "FIXED PASSWORD" is only 14-bytes, make them the same, 128, 192 or 256 bits (16, 24 or 32 bytes). There is no need to use a key over 128-bits.
A password should not be used directly for a key, the key should be derived from the password with a function such as PBKFD2.
AES is a block cipher and it's. Input needs to be a multiple of the block size in length. To accomplish this padding of the input is needed. Typically PKCS#7 (née PKCS#5) padding is used, it is probably the default for OpenSSL. The encryption/decryptions should automatically add/remove this padding. But this does mean that the encrypted data length will be from 1-byte to 16-bytes longer than the input. e.g. If you encrypt "36" the output length will be 16-bytes.
Getting all this together correctly can be difficult to get correct, consider using an overall solution such as RNCryptor-php or defuse
I have difficulties decrypting a blowfish encrypted string in a .net environment, that was encrypted by the mcrypt php library.
Here is the script I use to encrypt some data
<?php
function encrypt_blowfish($data, $key) {
$iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$crypttext = mcrypt_encrypt(MCRYPT_BLOWFISH, $key, $data, MCRYPT_MODE_CBC, $iv);
echo 'IV: ' . bin2hex($iv) . "\n";
echo 'DATA: ' . bin2hex($crypttext) ."\n" ;
}
$secretKey = 'somekey';
$data = 'Hello World this is an encryptiontest!';
encrypt_blowfish($data, $secretKey);
I decided to use the bouncingcastle library since it seemed to be the default choice for encryption and they had a PCL version (which I need). For testing purpose I just copy/pasted the echo'd values into my C# code.
var ivString = "34c33fed0386dda1";
var iv = Hex.Decode (ivString);
var dataString = "ced4ed218d7a1fd228f8c43ca6b83f097648811661d5510678a26953729ceccdf6d78a7695cbfe43";
var data = Hex.Decode (dataString);
var keyString = "somekey";
var key = System.Text.Encoding.UTF8.GetBytes (keyString);
var engine = new BlowfishEngine();
var cipher =new PaddedBufferedBlockCipher(new CbcBlockCipher(engine));
var keyParam = new KeyParameter(key);
cipher.Init (false, keyParam);
var outBytes = new byte[data.Length];
var len = cipher.ProcessBytes (data, 0, data.Length, outBytes, 0);
cipher.DoFinal(outBytes, len);
Console.WriteLine(System.Text.Encoding.UTF8.GetString(outBytes));
When I run this code DoFinal explodes with a "Corrupt padding block" exception. So I read about pcks7 padding which essentially fills the bytes of the original string. I calculated that for my input string and the blowfish cbc algorithm block size of 8, I would need two bytes of padding so I added "22" at the end of the string. This however yielded the same result.
Also, I don't see any point where I can insert the IV into the blowfish decryption. It feels like I am completely lacking/not understanding a vital point here. Any1 any ideas on what goes wrong here? Also if possible I would like to skip on the padding part in my php and simply decrypt with iv/passphrase in c#, is that even possible?
Cheers and thanks
Tom
I ended up using a simpler library which supperts cbc mode in a very simple fashion.
http://jaryl-lan.blogspot.de/2014/07/openfire-blowfish-encryptiondecryption.html
#Tom, your decryption problem was probably due to the '\0' padding that the PHP mcrypt library uses when it needs to make a block of text congruent to its fixed block length (64 bits, or 8 bytes). I hear .Net uses something different? Moreover, does mcrypt_create_iv() produce a binary data? Out of curisoity, why would you use bin2hex() on the IV.
The way to send the IV with with the cipher text is simply $cipherText = $iv . $cipherText. Then, they say, you can use substr() (possibly, mb_substr()) to recover the IV and the true cipher text from the composite. Use the recovered IV and your key to decrypt the cipher text.
The last step is to remove any padding that may have been added to the plain text by mcrypt.
I created a Blowfish diagnostic page here. I am on PHP 5.6.20, and I cannot get mcrypt to decrypt my data because I have a strange IV problem. The same technique I described to you does not seem to work. I posted the text of the diagnostic script on this question:
Blowfish IV Recovery Problem
Here is a working on-line version on my domain:
Blowfish Diganositc Webpage
I guess it is a good thing you tried something else. Ecryption with Blowfish seems to work fine, but even when using native PHP, there appears to be an issue with the decryption process. But, it could just be me! ;-)
A bit more background info as suggested:
I'm finsihing of an Intranet CMS web app where I have to use the products API (ASP.NET based). Because of time constraints and issues with Windows authen' I need another way to ensure staff do not need to re login everytime they visit the site to view personalised content. The way it works is that once a user logs in (username/password), a Session ID storing a new different Security context value is generated that is used to display the personalised content. The API login method called uses the username and password as parameters. The only way I can think of automatically logging in the next time the staff visits the site is by storing the password in a enrypted cookie and checking of its existing when the site is visited and then calling the API login method using the username and decrypted password cookie values.
Any other ideas as an alternative welcomed.
Mo
Hi,
I'm using some code found on the web to encrypt and decrypt a password string. It encrypts fine but when it calls the code below to decrypt the string it throws the error "Length of the data to decrypt is invalid" How can I resolve this?
Thanks in advance.
Mo
System.Text.Encoding enc = System.Text.Encoding.ASCII;
byte[] myByteArray = enc.GetBytes(_pword);
SymmetricAlgorithm sa = DES.Create();
MemoryStream msDecrypt = new MemoryStream(myByteArray);
CryptoStream csDecrypt = new CryptoStream(msDecrypt, sa.CreateDecryptor(), CryptoStreamMode.Read);
byte[] decryptedTextBytes = new Byte[myByteArray.Length];
csDecrypt.Read(decryptedTextBytes, 0, myByteArray.Length);
csDecrypt.Close();
msDecrypt.Close();
string decryptedTextString = (new UnicodeEncoding()).GetString(decryptedTextBytes);
A couple of things here...
You shouldn't encrypt passwords usually. You should hash them.
If you decide to continue down the road of encryption..
You are using the DES algorithm. This is considered insecure and flawed. I'd recommend looking at the AES algorithm.
Depending on how much data you are working with, the CryptoStream might be overkill.
Using the ASCII encoding can cause loss of data that isn't ASCII, like Cyrillic letters. The recommended fix is to use something else, like UTF8.
Here is an example:
string text = "Hello";
using (var aes = new AesManaged())
{
var bytes = System.Text.Encoding.UTF8.GetBytes(text);
byte[] encryptedBytes;
using (var encrypt = aes.CreateEncryptor())
{
encryptedBytes = encrypt.TransformFinalBlock(bytes, 0, bytes.Length);
}
byte[] decryptedBytes;
using (var decrypt = aes.CreateDecryptor())
{
decryptedBytes = decrypt.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length);
}
var decryptedText = System.Text.Encoding.UTF8.GetString(decryptedBytes);
Console.Out.WriteLine("decryptedText = {0}", decryptedText);
}
This will use a random key every time. It is likely that you will need to encrypt some data, then decrypt it at a later time. When you create the AesManaged object, you can store the Key and IV property. You can re-use the same Key if you'd like, but different data should always be encrypted with a different IV (Initialization Vector). Where you store that key, is up to you. That's why hashing might be a better alternative: there is no key, and no need to worry about storing the key safely.
If you want to go down the hashing route, here is a small example:
var textToHash = "hello";
using (SHA1 sha = new SHA1Managed())
{
var bytesToHash = System.Text.Encoding.UTF8.GetBytes(textToHash);
var hash = sha.ComputeHash(bytesToHash);
string base64hash = Convert.ToBase64String(hash);
}
This uses the SHA1 algorithm, which should work fine for passwords, however you may want to consider SHA256.
The concept is simple: a hash will produce a (mostly) unique output for an input, however the output cannot be converted back to the input - it's destructive. Whenever you want to check if a user should be authenticated, check hash the password they gave you, and check it against the hash of the correct password. That way you aren't storing anything sensitive.
I've actually had this error before and it took me 3 days to figure out the solution. The issue will be the fact that the machine key you need for descryption needs to be registered on your machine itself.
Read fully up on DES encryption, it works by an application key, and a machine-level key. The error you're getting is likely because of the machine key missing.
Compare the bytes used to create the _pword string (in the encryption method) to the bytes retrieved with GetBytes. Probably you will notice a change in the data there.
To store the encrypted bytes, I think you should use Convert.ToBase64String and Convert.FromBase64String turn the encrypted password to/from a string.
I also do not see the code where you set the Key and IV. So I guess you are using a different key to encrypt and decrypt the password.
If the current Key property is null,
the GenerateKey method is called to
create a new random Key. If the
current IV property is null, the
GenerateIV method is called to create
a new random IV.
DES is a block based cipher - only certain lengths of buffers are valid. If I remember correctly, the block size for DES is 64 bits, so you need to ensure that your byte array is a multiple of 8 bytes long.
(That should fix your immediate problem, but I'd reference other peoples advice here - you really ought not to be using DES for any new code, and for passwords it's usually more appropriate to hash than to encrypt).
I have the following code to encrypt a value (listed below). Now I would like to write a bool isEncrypted() method. Is there a fool proof and reliable way to check if a value has been encrypted using this function. I have the decrypt routine and can control the pass phrase, but not sure if that will help.
The reason is - when the app first runs, values in a configuration file are not encrypted, in this case the app should auto encrypt these values. On 2nd run I don't want to encrypt again because obviously that would cause havoc. Lastly I don't want to have to add an isEncrypted attribute to the config value. I want it to work and look as dynamic as possible.
So far I am leaning towards using the len (128) as deciding factor, but there is always a remote chance of the unencrypted value also being this length.
Thanks in advance.
public static string encrypt(string text)
{
// Locals
var passphrase = "5ab394ed-3920-4932-8d70-9c1b08f4ba4e";
byte[] results;
var utf8 = new UTF8Encoding();
// Step 1. We hash the passphrase using MD5
// We use the MD5 hash generator as the result is a 128 bit byte array
// which is a valid length for the TripleDES encoder we use below
var hashProvider = new MD5CryptoServiceProvider();
var tdesKey = hashProvider.ComputeHash(utf8.GetBytes(passphrase));
// Step 2. Create a new TripleDESCryptoServiceProvider object
// Step 3. Setup the encoder
var tdesAlgorithm = new TripleDESCryptoServiceProvider
{
Key = tdesKey,
Mode = CipherMode.ECB,
Padding = PaddingMode.PKCS7
};
// Step 4. Convert the input string to a byte[]
var dataToEncrypt = utf8.GetBytes(text);
// Step 5. Attempt to encrypt the string
try
{
var encryptor = tdesAlgorithm.CreateEncryptor();
results = encryptor.TransformFinalBlock(dataToEncrypt, 0, dataToEncrypt.Length);
}
finally
{
// Clear the TripleDes and Hashprovider services of any sensitive information
tdesAlgorithm.Clear();
hashProvider.Clear();
}
// Step 6. Return the encrypted string as a base64 encoded string
return Convert.ToBase64String(results);
}
What you could do in the isEncrypted method is to try to decrypt the message.
Since you are using PKCS7 padding most likely an unencrypted message will fail to decrypt since the padding does not conform to the set padding mode.
The decryption will throw an exception and you'll have to catch this and return false in this case.
There is a remote chance that the decryption will go through (when the message is not encrypted) if the data conforms to the padding mode. This is however most unlikely.
What I would do in this case would be to add some kind of flag in the encrypted data or append some data to encrypted message since I can then remove it in the decryption. This would be the most foolproof way.
First, as a serious issue, it's an exceedingly poor idea to use cryptographic primitives on your own. You've chosen to use the Electronic Codebook mode of encryption, which has the property that identical plaintext blocks produce identical cyphertext blocks. Check out the example at Wikipedia.
That said, a simple solution is to prepend a token such as 'ENC:' to the encrypted password. If you need to worry about malicious tampering with the config file, you should proceed to use a message authentication code, such as HMAC.
As your function returns a string there's no reason you can't add a plaintext code to the beginning of the encrypted data that the IsEncrypted function can look for, say "MD5ENC"+ [ciphertext].
The disadvantage of this is that it will let anyone who has the raw string know what algorithm was used for encryption. But as we keep getting reminded security through obscurity is no security at all. Anyone should be allowed to know how something was encrypted and have no easy way of breaking that encryption.
Note my use of the word should.
Anyhow, to return to my original suggestion. The advantage of this is that the longer your introductory code on the string the more vanishingly tiny the chances of it being generated by accident in another unrelated Base64 encrypted string becomes.
Should the ciphertext need decrypting just snip off your standard length encryption ident code and away you go...
Currently I am receiving the following error when using Java to decrypt a Base64 encoded RSA encrypted string that was made in C#:
javax.crypto.BadPaddingException: Not PKCS#1 block type 2 or Zero padding
The setup process between the exchange from .NET and Java is done by creating a private key in the .NET key store then from the PEM file extracted, created use keytool to create a JKS version with the private key. Java loads the already created JKS and decodes the Base64 string into a byte array and then uses the private key to decrypt.
Here is the code that I have in C# that creates the encrypted string:
public string Encrypt(string value) {
byte[] baIn = null;
byte[] baRet = null;
string keyContainerName = "test";
CspParameters cp = new CspParameters();
cp.Flags = CspProviderFlags.UseMachineKeyStore;
cp.KeyContainerName = keyContainerName;
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(cp);
// Convert the input string to a byte array
baIn = UnicodeEncoding.Unicode.GetBytes(value);
// Encrypt
baRet = rsa.Encrypt(baIn, false);
// Convert the encrypted byte array to a base64 string
return Convert.ToBase64String(baRet);
}
Here is the code that I have in Java that decrypts the inputted string:
public void decrypt(String base64String) {
String keyStorePath = "C:\Key.keystore";
String storepass = "1234";
String keypass = "abcd";
byte[] data = Base64.decode(base64String);
byte[] cipherData = null;
keystore = KeyStore.getInstance("JKS");
keystore.load(new FileInputStream(keyStorePath), storepass.toCharArray());
RSAPrivateKey privateRSAKey = (RSAPrivateKey) keystore.getKey(alias, keypass.toCharArray());
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateRSAKey);
cipherData = cipher.doFinal(data);
System.out.println(new String(cipherData));
}
Does anyone see a step missing or where the padding or item needs to be changed? I have done hours of reading on this site and others but haven't really found a concrete solution.
You're help is vastly appreciated.
Thanks. -Matt
I had exactely the same problem and I finally find the solution!
I was stubborn using PKCS1Padding but I didn't manage to make it work.
The best result I got using "rsa.Encrypt(baIn, false)" on the C# side and "RSA/NONE/NoPadding" on the Java side was this kind of string : "☻?o+_>??5?l0Q*???*?R▲???♀7..." followed by my decrypted string. So in a way it got decrypted but since there is no padding specified, the data is shifted. So I tried all the paddings available in bouncycastle but I would alway get errors such as "block incorrect size" or "data hash wrong".
So I decided to start trying OAEP paddings and I finally managed to get it working by using "rsa.Encrypt(baIn, true)" on the C# side and "RSA/NONE/OAEPWithSHA1AndMGF1Padding" on the java side!
It worked for me, I hope it will work for you too! If it doesn't work make sure you're using the right key, very often the problem comes from the key.
Check that you have correctly exchanged the key.
Trying to decrypt with an incorrect key is indistinguishable from decrypting badly padded data.
I'm working through a similar problem operating between .Net and iPhone stuff in Objective - C, and I think the answer lies in this little gem from the RSACryptoServiceProvider documentation:
Unlike the RSA implementation in unmanaged CAPI, the RSACryptoServiceProvider class reverses the order of an encrypted array of bytes after encryption and before decryption. By default, data encrypted by the RSACryptoServiceProvider class cannot be decrypted by the CAPI CryptDecrypt function and data encrypted by the CAPI CryptEncrypt method cannot be decrypted by the RSACryptoServiceProvider class.
See here for more details: http://msdn.microsoft.com/en-us/library/s575f7e2(v=VS.90).aspx
I had the same problem when using Bouncy Castle 1.48 but it wasn't key-related. Instead, I found that I had to set the following system property:
-Dorg.bouncycastle.pkcs1.strict=false