Generating PBKDF2 keys in C# and NodeJS - c#

I'm trying to encrypt a byte array in C# using AES192 and a PBKDF2 password/salt based key and decrypt the same data in NodeJS. However my key generation produces different results in both NodeJS and C#.
The C# code is as follows:
private void getKeyAndIVFromPasswordAndSalt(string password, byte[] salt, SymmetricAlgorithm symmetricAlgorithm, ref byte[] key, ref byte[] iv)
{
Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, salt);
key = rfc2898DeriveBytes.GetBytes(symmetricAlgorithm.KeySize / 8);
iv = rfc2898DeriveBytes.GetBytes(symmetricAlgorithm.BlockSize / 8);
}
private byte[] encrypt(byte[] unencryptedBytes, string password, int keySize)
{
RijndaelManaged aesEncryption = new RijndaelManaged();
aesEncryption.KeySize = keySize;
aesEncryption.BlockSize = 128;
byte[] key = new byte[keySize];
byte[] iv = new byte[128];
getKeyAndIVFromPasswordAndSalt(password, Encoding.ASCII.GetBytes("$391Ge3%£2gfR"), aesEncryption, ref key, ref iv);
aesEncryption.Key = key;
aesEncryption.IV = iv;
Console.WriteLine("iv: {0}", Convert.ToBase64String(aesEncryption.IV));
Console.WriteLine("key: {0}", Convert.ToBase64String(aesEncryption.Key));
ICryptoTransform crypto = aesEncryption.CreateEncryptor();
// The result of the encryption and decryption
return crypto.TransformFinalBlock(unencryptedBytes, 0, unencryptedBytes.Length);
}
The NodeJS code reads like this:
crypto.pbkdf2("Test", "$391Ge3%£2gfR", 1000, 192/8, (err, key) => {
var binkey = new Buffer(key, 'ascii');
var biniv = new Buffer("R6taODpFa1/A7WhTZVszvA==", 'base64');
var decipher = crypto.createDecipheriv('aes192', binkey, biniv);
console.log("KEY: " + binkey.toString("base64"));
var decodedLIL = decipher.update(decryptedBuffer);
console.log(decodedLIL);
return;
});
The IV is hardcoded as I can't figure out how to calculate that using pbkdf2. I've looked through the nodeJS docs for more help but I'm at a loss as to what's going on here.
Any assistance would be greatly appreciated.

One of the issues I see is the encoding of the pound sign (£). crypto.pbkdf2 encodes the password and salt to a binary array by default, where each character is truncated to the lowest 8 bits (meaning the pound sign becomes the byte 0xA3).
However, your C# code converts the salt to ASCII, where each character is truncated to the lowest 7 bits (meaning the pound sign becomes the byte 0x23). Also it uses the Rfc2898DeriveBytes constructor that takes a String for the password. Unfortunately, the documentation doesn't say what encoding is used to convert the string to bytes. Fortunately, Rfc2898DeriveBytes does have another constructor that takes a byte array for the password and also takes an iteration count parameter, here 1000.
Accordingly, you should convert the password and salt strings to byte arrays by truncating each character to 8 bits, just like Node.js does by default. Here is an example:
var bytes=new byte[password.Length];
for(var i=0;i<bytes.Length;i++){
bytes[i]=(byte)(password[i]&0xFF);
}

Related

Need PHP 8 version of C#/.NET encryption code

An encryption C# code that has been in use for many years now needs to be converted to PHP 8.
I came close, and there's one remaining issue as described below:
For example, the secret below is longer than 71 characters and it is not encrypted correctly:
secret = "id=jsmith12&timestamp=2022-07-06t11:10:43&expiration=2022-07-06t11:15:43"; //71 chars-long
However, these secrets will be encrypted correctly, since they are less than 71 chars long:
secret = "id=jsmith&timestamp=2022-07-06t11:10:43&expiration=2022-07-06t11:15:43"; // 69 chars-long
secret = "id=jsmith1&timestamp=2022-07-06t11:10:43&expiration=2022-07-06t11:15:43"; // 70 chars-long
There is an online page where you can test if the generated token is correct: https://www.mybudgetpak.com/SSOTest/
You can evaluate the token by providing the generated token, the key, and the encryption method (Rijndael or Triple DES).
If the evaluation (decryption of the token) is successful, the test page will diplay the id, timestamp and expiration values
used in the secret.
C# Code:
The secret, a concatenated query string values, what needs to be encrypted:
string secret = "id=jsmith123&timestamp=2022-07-06t11:10:43&expiration=2022-07-06t11:15:43";
The key:
string key = "C000000000000000"; //16 character-long
ASCII encoded secret and key converted to byte array:
System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
byte[] encodedSecret = encoding.GetBytes(secret);
byte[] encodedKey = encoding.GetBytes(key);
Option 1: Rijndael
// Call the generate token method:
string token = GenerateRijndaelSecureToken(encodedSecret, encodedKey);
private string GenerateRijndaelSecureToken(byte[] encodedSecret, byte[] encodedKey)
{
Rijndael rijndael = Rijndael.Create();
// the encodedKey must be a valid length so we pad it until it is (it checks // number of bits)
while (encodedKey.Length * 8 < rijndael.KeySize)
{
byte[] tmp = new byte[encodedKey.Length + 1];
encodedKey.CopyTo(tmp, 0);
tmp[tmp.Length - 1] = (byte)'\0';
encodedKey = tmp;
}
rijndael.Key = encodedKey;
rijndael.Mode = CipherMode.ECB;
rijndael.Padding = PaddingMode.Zeros;
ICryptoTransform ict = rijndael.CreateEncryptor();
byte[] result = ict.TransformFinalBlock(encodedSecret, 0, encodedSecret.Length);
// convert the encodedSecret to a Base64 string to return
return Convert.ToBase64String(result);
}
Option 2: Triple DES
// Call the generate token method:
string token = GenerateSecureTripleDesToken(encodedSecret, encodedKey);
private string generateSecureTripleDesToken(byte[] encodedSecret, byte[] encodedKey)
{
// Generate the secure token (this implementation uses 3DES)
TripleDESCryptoServiceProvider tdes = new TripleDESCryptoServiceProvider();
// the encodedKey must be a valid length so we pad it until it is (it checks // number of bits)
while (encodedKey.Length * 8 < tdes.KeySize)
{
byte[] tmp = new byte[encodedKey.Length + 1];
encodedKey.CopyTo(tmp, 0);
tmp[tmp.Length - 1] = (byte) '\0';
encodedKey = tmp;
}
tdes.Key = encodedKey;
tdes.Mode = CipherMode.ECB;
tdes.Padding = PaddingMode.Zeros;
ICryptoTransform ict = tdes.CreateEncryptor();
byte[] result = ict.TransformFinalBlock(encodedSecret, 0, encodedSecret.Length);
// convert the encodedSecret to a Base64 string to return
return Convert.ToBase64String(result);
}
PHP 8 code:
public $cipher_method = "AES-256-ECB";
// Will not work:
//$secret = "id=jsmith12&timestamp=2022-07-06t11:10:43&expiration=2022-07-06t11:15:43";
// Will work:
//$secret = "id=jsmith&timestamp=2022-07-06t11:10:43&expiration=2022-07-06t11:15:43";
$key = "C000000000000000";
$token = openssl_encrypt($secret, $cipher_method, $key);
There are two things to be aware of:
The C# code pads the key with 0x00 values to the required length, i.e. 256 bits for AES-256 and 192 bits for 3DES. Since PHP/OpenSSL automatically pads keys that are too short with 0x00 values, this does not need to be implemented explicitly in the PHP code (although it would be more transparent).
The C# code uses Zero padding. PHP/OpenSSL on the other hand applies PKCS#7 padding. Since PHP/OpenSSL does not support Zero padding, the default PKCS#7 padding must be disabled with OPENSSL_ZERO_PADDING (note: this does not enable Zero padding, the name of the flag is poorly chosen) and Zero padding must be explicitly implemented, e.g. with:
function zeropad($data, $bs) {
$length = ($bs - strlen($data) % $bs) % $bs;
return $data . str_repeat("\0", $length);
}
Here $bs is the block size (16 bytes for AES and 8 bytes for DES/3DES).
Further changes are not necessary! A possible implementation is:
$cipher_method = "aes-256-ecb"; // for AES (32 bytes key)
//$cipher_method = "des-ede3"; // for 3DES (24 bytes key)
// Zero pad plaintext (explicitly)
$bs = 16; // for AES
//$bs = 8; // for 3DES
$secret = zeropad($secret, $bs);
// Zero pad key (implicitly)
$key = "C000000000000000";
$token = openssl_encrypt($secret, $cipher_method, $key, OPENSSL_ZERO_PADDING); // disable PKCS#7 default padding, Base64 encode (implicitly)
print($token . PHP_EOL);
The ciphertexts generated in this way can be decrypted using the linked website (regardless of their length).
The wrong padding causes decryption to fail on the web site (at least to not succeed reliably). However, the logic is not correct that decryption fails only if the plaintext is larger than 71 bytes (even if only the range between 65 and 79 bytes is considered). For example, decryption fails also with 66 bytes. The page source provides a bit more information than the GUI:
Could not read \u0027expiration\u0027 as a date: 2022-07-06t11:15:43\u000e\u000e\u000e\u000e\u000e\u000e\u000e\u000e\u000e\u000e\u000e\u000e\u000e\u000e
The problem is (as expected) the PKCS#7 padding bytes at the end: 14 0x0e values for 66 bytes.
Why decryption works for some padding bytes and not for others can only be reliably answered if the decryption logic of the web site were known. In the end, however, the exact reason doesn't matter.
Note that the applied key expansion is insecure. Also, ECB is insecure, 3DES is outdated, and Zero padding is unreliable.

AES 128 bit with ECB ciphermode algorithm decrypts correctly with an invalid key?

Hi i am learning Encryption / Decryption part. I have created two methods for Encryption / Decryption using AES 128 bit with ECB cipher mode and PKCS7 padding.
Below is the code.
public class EncClass
{
public string Encrypt(string text)
{
byte[] src = Encoding.UTF8.GetBytes(text);
byte[] key = Encoding.ASCII.GetBytes("contactcentre");
RijndaelManaged aes = new RijndaelManaged();
aes.Mode = CipherMode.ECB;
aes.Padding = PaddingMode.PKCS7;
aes.KeySize = 128;
using (ICryptoTransform encrypt = aes.CreateEncryptor(key, null))
{
byte[] dest = encrypt.TransformFinalBlock(src, 0, src.Length);
encrypt.Dispose();
return Convert.ToBase64String(dest);
}
}
public string Decrypt(string text)
{
byte[] src = Convert.FromBase64String(text);
RijndaelManaged aes = new RijndaelManaged();
byte[] key = Encoding.ASCII.GetBytes("contactcentrT");
aes.KeySize = 128;
aes.Padding = PaddingMode.PKCS7;
aes.Mode = CipherMode.ECB;
using (ICryptoTransform decrypt = aes.CreateDecryptor(key, null))
{
byte[] dest = decrypt.TransformFinalBlock(src, 0, src.Length);
decrypt.Dispose();
return Encoding.UTF8.GetString(dest);
}
}
}
Notice that in Encryption i have passed contactcentre key and in decryption I have passed contactcentrT. It is doing proper encryption and decryption in that case.
var encString = encClass.Encrypt(#"manoj");
var decString = encClass.Decrypt(encString);
Though my both keys are not matching, still it is working properly. Just wanted to know how this could happen?
You are passing invalid key to aes.CreateEncryptor (and CreateDecryptor). Valid key sizes for AES are 128, 192 and 256, and your key is 13*8=104 bits. If you try to assign it to aes.Key - that will throw exception. However, aes.CreateEncryptor has a bug and it does not correctly validate key size if key size is less than block size (less than 128 bits), despite stating explicitly in documentation that "The key size must be 128, 192, or 256 bits". This bug is fixed in .NET Core by the way (at least in version 2) where it correctly throws exception for your code.
So, since you are passing invalid key and CreateEncryptor by mistake allowed it - you are not really encrypting with AES and anything can happen. For example if you pass key of 1 byte (or 2, or 7) - index out of range exception will be thrown. My assumption (by looking at source code) is algorithm implementation assumes key size in bytes is divisable by 4, and uses those 4-byte blocks. First 12 characters of your key are the same, and the rest (be it 1,2 or 3 characters) are not used, because number of 4-byte blocks is calculated as keySize / 4 (and 13 / 4 = 3).
Anyway, any assumptions of why this happens does not matter much, because algorithm is executed with invalid input and any results produced by doing that are irrelevant.
Given that info - never pass key directly to aes.CreateEncryptor but assign it to aes.Key first and then pass that (aes.CreateEncryptor(aes.Key, iv))

Why does PasswordDerivedBytes produces different key everytime if I use HMACSha1 as hashing algorithm?

I am using same passphrase and salt, yet it produces different keys everytime I run it when using PasswordDerivedBytes. It produces same key everytime if I use Sha1, however. Why is that?
And why does Rfc2898DerivedBytes produce same key everytime if I use that same passphrase, salt, initvector combination, knowing that it uses HMACSha1?
Code snippet added below-
string passPhrase = "passPhrase";
byte[] saltBytes = Encoding.ASCII.GetBytes("saltValue");
int iterations = 2;
int keySize = 32;
string hashAlgo = "HMACSHA1";
Rfc2898DeriveBytes derivedBytes = new Rfc2898DeriveBytes(passPhrase, saltBytes, iterations);
byte[] keyBytes = derivedBytes.GetBytes(keySize);
PasswordDeriveBytes password = new PasswordDeriveBytes(passPhrase, saltBytes, hashAlgo, iterations);
byte[] keyBytes2 = password.GetBytes(keySize);
Rfc2898DerivedBytes uses PBKDF2 with HMAC-SHA-1 as PRF (A PRF is essentially a keyed hash). PBKDF2 expects a PRF and uses the key for the password and the salt/chaining value as message.
PasswordDeriveBytes uses PBKDF1 with a user specified hash algorithm. This hash is expected to be unkeyed. But you passed in "HMACSHA1" which is keyed. When creating an instance of HMACSHA1, .NET fills in a random key. Since PasswordDeriveBytes is not key aware (it expects an unkeyed hash), it ends up with a differently hash function each time and thus produces different results each time.

Two-key triple DES encryption solution needed

Is this possible in C#? How would I accomplish this?
Two-key triple DES is where we encrypt with K1, then decrypt with K2 and finally encrypt again with K1. The keyspace is thus 2 x 56 = 112 bits.
For example, with K1=0x0123456789ABCDEF and K2=0xFEDCBA9876543210 you would set the triple DES key to be 0x0123456789ABCDEFFEDCBA98765432100123456789ABCDEF.
0123456789ABCDEF FEDCBA9876543210 0123456789ABCDEF
|<------K1------>|<------K2------>|<------K3------>|
It accepts A9993E364706816A and the 2 keys that it must use is K1 = 0123456789ABCDEF and K2 = FEDCBA9876543210. The end result must be: 6E5271A3F3F5C418 which I am not getting.
UPDATE:
I am trying to create the concatenated key that I need to use. The 2 keys used above is converted to a byte array and seems to have a length of 16 each. And when the 2 are concatenated then the length is 32. Then my code bombs out. The key has to have a length of 16 or 24. What do I need to do in this case?
UTF8Encoding characterEncoding = new UTF8Encoding();
byte[] accessKey1ByteArray = characterEncoding.GetBytes(accessKey1);
byte[] accessKey2ByteArray = characterEncoding.GetBytes(accessKey2);
byte[] accessKeysArray = accessKey1ByteArray.Concat(accessKey2ByteArray).ToArray();
Here is where I try to set my values:
public byte[] ComputeTripleDesEncryption(byte[] plainText, byte[] key)
{
TripleDESCryptoServiceProvider des = new TripleDESCryptoServiceProvider();
des.Key = key;
des.GenerateIV();
des.Mode = CipherMode.ECB;
des.Padding = PaddingMode.None;
ICryptoTransform ic = des.CreateEncryptor();
byte[] enc = ic.TransformFinalBlock(plainText, 0, plainText.Length);
return enc;
}
UPDATE 2
Do I need to set the size? The byte array key that I am sending through is K1 + K2 + K1.
The text that I am sending through, do I need convert this to bytes like what you recommended, or can the following also do the trick?
UTF8Encoding characterEncoding = new UTF8Encoding();
byte[] block1ByteArray = characterEncoding.GetBytes(block1);
The value of block1 is: A9993E364706816A.
How I got A9993E364706816A was from my SHA-1 hashed result. The first 16 characters of this hashed result of my string that I want to encode.
This sounds like you just want to set a 128 bit key for the triple des key.
I believe in this case if you provide a 128 bit key it splits it into two 64 bit keys and uses the first as K1 and K3 and the second as K2 which is exactly what you want.
Unfortunately I can't find a source to quote on this but I did a lot of reading on the subject recently when implementing some crypto stuff myself and finding out about key lengths and this is what I discovered.
If you have K1 and K2 as byte arrays already then you should be able to just use a nice little linq extension method and do:
SymmetricAlgorithm cryptoService = new TripleDESCryptoServiceProvider();
byte[] myKey = K1.Concat(K2).ToArray();
cryptoService.Key = mKey;
That will then do as you want.
In response to your updated part of the question the two keys you have are hexdecimal representations of a sequence of bytes. 0x0123456789ABCDEF is 16 characters of hexdecimal but this is equivalent to 8 bytes of information since it is 4 bits in each character - two making up a byte.
To convert that string to a byte array the following function can be used:
public static byte[] StringToByteArray(String hex)
{
if (hex.Substring(0,2)=="0x")
hex = hex.Substring(2);
int NumberChars = hex.Length;
byte[] bytes = new byte[NumberChars / 2];
for (int i = 0; i < NumberChars; i += 2)
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
return bytes;
}
(From How do you convert Byte Array to Hexadecimal String, and vice versa?)
This will then be used like this:
string K1="0x0123456789ABCDEF";
string K2="0xFEDCBA9876543210";
byte[] key = StringToByteArray(K1).Concat(StringToByteArray(K2)).ToArray();
When implementing TDES you will need to agree a key, Block Cipher mode, Padding method and in most Block modes you will need an initialisation Vector. You'll possibly also want to use a Message Authentication Code.
To get an initialisation vector you'll want to do something like:
cryptoService.GenerateIV();
byte[] iv = cryptoService.IV;
I strongly advise reading pages on encryption to understand better what the various things you are doing actually are rather than just writing the code. It will make you more confident in your security and make you sound more confident while dealing with others. I've taken the liberty of including some links, most of which can be found by just googling.
Useful links:
http://en.wikipedia.org/wiki/Initialization_vector - all about initialisation vectors
http://en.wikipedia.org/wiki/Triple_DES - on the TDES algorithm
http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation - How consecutive blocks of data interact with each other.
http://en.wikipedia.org/wiki/Padding_%28cryptography%29 - Not massively important except there are different ways of padding and both sides need to be using the same one (of course).
http://chargen.matasano.com/chargen/2009/7/22/if-youre-typing-the-letters-a-e-s-into-your-code-youre-doing.html - An excellent and amusing commentary on the use of encryption and where there are weaknesses and what encryption can and cannot do.
http://en.wikipedia.org/wiki/Message_authentication_code - How to confirm that your message hasn't been tampered with
To encrypt/decrypt data with the TripleDES algorithm, you can use the TripleDESCryptoServiceProvider Class. The algorithm supports key lengths from 128 bits to 192 bits in increments of 64 bits.
If you have two 64-bit keys
byte[] k1 = new byte[] { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF };
byte[] k2 = new byte[] { 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10 };
and want to concatenate k1, k2 and k1 again to a 192-bit key, you can do this as follows:
byte[] key = new byte[K1.Length + K2.Length + K1.Length];
Buffer.BlockCopy(k1, 0, result, 0, 8);
Buffer.BlockCopy(k2, 0, result, 8, 8);
Buffer.BlockCopy(k1, 0, result, 16, 8);
Note that, in addition to the key, you also need an initialization vector:
byte[] iv = // ...
Example:
byte[] data = new byte[] { 0xA9, 0x99, 0x3E, 0x36, 0x47, 0x06, 0x81, 0x6A };
using (var csp = new TripleDESCryptoServiceProvider())
using (var enc = csp.CreateEncryptor(key, iv))
using (var stream = new MemoryStream())
using (var crypto = new CryptoStream(stream, enc, CryptoStreamMode.Write))
{
crypto.Write(data, 0, data.Length);
}

Why RSA encryption can return different results with C# and Java?

I using:
c#: RSACryptoServiceProvider
JAVA: KeyFactory.getInstance("RSA")+Cipher
I sending public key (exponent + modulus) as byte array from java to c#. It's ok, there is the same bytes. But when i try to encrypt some data with one key in Java and c# - there is different results.
Java Key Generation:
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize( Config.CRYPTO_KEY_NUM_BITS );
m_KeyPair = keyGen.genKeyPair();
m_PublicKey = KeyFactory.getInstance("RSA").generatePublic(
newX509EncodedKeySpec(m_KeyPair.getPublic().getEncoded()));
byte[] exponent = m_PublicKey.getPublicExponent().toByteArray();
byte[] modulus = m_PublicKey.getModulus().toByteArray(); // then sending...
C# Key Recieve:
// Recieved...
m_ExternKey = new RSAParameters();
m_ExternKey.Exponent = exponent;
m_ExternKey.Modulus = modulus;
m_RsaExtern = new RSACryptoServiceProvider();
m_RsaExtern.ImportParameters(m_ExternKey);
byte[] test = m_RsaExtern.Encrypt(bytesToEncrypt, true);
and problem is that encrypted bytes is different.
Thank you.
RSA encryption is randomized. For a given public key and a given message, each attempt at encryption yields a distinct sequence of bytes. This is normal and expected; random bytes are injected as part of the padding phase, and not injecting random bytes would result in a weak encryption system. During decryption, the padding bytes are located and removed, and the original message is recovered unscathed.
Hence it is expected that you will get distinct encrypted messages with Java and C#, but also if you run your Java or C# code twice.
RSA Encription mustn't return diffferent values with simular keys - its standardized algorithm. Check your keys.
RSA Parameters contains more parameters than modulus and exponent if i remember correctly. You need fully initialized rsa parameters to get the encryption correct (in .net).
Moreover, your private and private key is not even set in .net
i hope this is helpful , in C# lough code
byte[] rsaExp = rsaParameters.Exponent.ToByteArray();
byte[] Modulus = rsaParameters.Modulus.ToByteArray();
// Microsoft RSAParameters modulo wants leading zero's removed so create new array with leading zero's removed
int Pos = 0;
for (int i = 0; i < Modulus.Length; i++)
{
if (Modulus[i] == 0)
{
Pos++;
}
else
{
break;
}
}
byte[] rsaMod = new byte[Modulus.Length - Pos];
Array.Copy(Modulus, Pos, rsaMod, 0, Modulus.Length - Pos);

Categories