I'm looking for straight-up .NET implementation of the OpenSSL EVP_BytesToKey function. The closest thing I've found is the System.Security.Cryptography.PasswordDeriveBytes class (and Rfc2898DeriveBytes) but it seems to be slightly different and doesn't generate the same key and iv as EVP_BytesToKey.
I also found this implementation which seems like a good start but doesn't take into account iteration count.
I realize there's OpenSSL.NET but it's just a wrapper around the native openssl DLLs not a "real" .NET implementation.
I found this pseudo-code explanation of the EVP_BytesToKey method (in /doc/ssleay.txt of the openssl source):
/* M[] is an array of message digests
* MD() is the message digest function */
M[0]=MD(data . salt);
for (i=1; i<count; i++) M[0]=MD(M[0]);
i=1
while (data still needed for key and iv)
{
M[i]=MD(M[i-1] . data . salt);
for (i=1; i<count; i++) M[i]=MD(M[i]);
i++;
}
If the salt is NULL, it is not used.
The digests are concatenated together.
M = M[0] . M[1] . M[2] .......
So based on that I was able to come up with this C# method (which seems to work for my purposes and assumes 32-byte key and 16-byte iv):
private static void DeriveKeyAndIV(byte[] data, byte[] salt, int count, out byte[] key, out byte[] iv)
{
List<byte> hashList = new List<byte>();
byte[] currentHash = new byte[0];
int preHashLength = data.Length + ((salt != null) ? salt.Length : 0);
byte[] preHash = new byte[preHashLength];
System.Buffer.BlockCopy(data, 0, preHash, 0, data.Length);
if (salt != null)
System.Buffer.BlockCopy(salt, 0, preHash, data.Length, salt.Length);
MD5 hash = MD5.Create();
currentHash = hash.ComputeHash(preHash);
for (int i = 1; i < count; i++)
{
currentHash = hash.ComputeHash(currentHash);
}
hashList.AddRange(currentHash);
while (hashList.Count < 48) // for 32-byte key and 16-byte iv
{
preHashLength = currentHash.Length + data.Length + ((salt != null) ? salt.Length : 0);
preHash = new byte[preHashLength];
System.Buffer.BlockCopy(currentHash, 0, preHash, 0, currentHash.Length);
System.Buffer.BlockCopy(data, 0, preHash, currentHash.Length, data.Length);
if (salt != null)
System.Buffer.BlockCopy(salt, 0, preHash, currentHash.Length + data.Length, salt.Length);
currentHash = hash.ComputeHash(preHash);
for (int i = 1; i < count; i++)
{
currentHash = hash.ComputeHash(currentHash);
}
hashList.AddRange(currentHash);
}
hash.Clear();
key = new byte[32];
iv = new byte[16];
hashList.CopyTo(0, key, 0, 32);
hashList.CopyTo(32, iv, 0, 16);
}
UPDATE: Here's more/less the same implementation but uses the .NET DeriveBytes interface: https://gist.github.com/1339719
OpenSSL 1.1.0c changed the digest algorithm used in some internal components. Formerly, MD5 was used, and 1.1.0 switched to SHA256. Be careful the change is not affecting you in both EVP_BytesToKey and commands like openssl enc.
Related
I'm trying to implement AES 128 CTR encryption in c#. I've found Bouncy Castle is very useful. Here is my code:
public class AESCrypto
{
private byte[] Key = new byte[16];
private byte[] IV = new byte[16];
private const int CHUNK_SIZE = 16;
private IBufferedCipher cipher = CipherUtilities.GetCipher("AES/CTR/NoPadding");
// Key and IV I get from client.
public AESCrypto(byte[] key, byte[] iv, bool forEncryption) {
Key = key;
IV = iv;
cipher.Init(forEncryption, new ParametersWithIV(new KeyParameter(Key), IV));
}
public byte[] PerformAES(byte[] incomingBytes)
{
int blockCount = incomingBytes.Length / CHUNK_SIZE; // Number of blocks
int blockRemaining = incomingBytes.Length % CHUNK_SIZE; // Remaining bytes of the last block
byte[] outcomingBytes = new byte[incomingBytes.Length];
for (var i = 0; i < blockCount; i++)
{
// Why do I need to re-init it again?
//cipher.Init(false, new ParametersWithIV(new KeyParameter(Key), IV));
byte[] temp = new byte[CHUNK_SIZE];
Array.Copy(incomingBytes, i * CHUNK_SIZE, temp, 0, CHUNK_SIZE);
byte[] decryptedChunk = cipher.ProcessBytes(temp);
Array.Copy(decryptedChunk, 0, outcomingBytes, i * CHUNK_SIZE, CHUNK_SIZE);
//Increase(IV); Why do I need to increse iv by hand?
}
if (blockRemaining != 0)
{
// Why do I need to re-init it again?
//cipher.Init(false, new ParametersWithIV(new KeyParameter(Key), IV));
byte[] temp = new byte[blockRemaining];
Array.Copy(incomingBytes, incomingBytes.Length - blockRemaining, temp, 0, blockRemaining);
byte[] decryptedChunk = cipher.DoFinal(temp);
Array.Copy(decryptedChunk, 0, outcomingBytes, incomingBytes.Length - blockRemaining, blockRemaining);
//Increase(IV); Why do I need to increse iv by hand?
}
return outcomingBytes;
}
private void Increase(byte[] iv)
{
for (var i = 0; i < iv.Length; i++)
{
iv[i]++;
if (iv[i] != 0)
{
break;
}
}
}
}
At first glance, this code should work fine. But it does not. Pay attention to commented-out-lines:
//cipher.Init(false, new ParametersWithIV(new KeyParameter(Key), IV));
and
//Increase(IV); Why do I need to increase iv by hand?
Only if I uncomment them my code works fine.
I'm wondering why I have to increase the counter manually? Or I made a mistake somewhere in set-up in the constructor? I'm not very familiar with Bouncy Castle.
P.S. I'm using BC 1.8.6.1 version from Nuget.
I want to migrate following python code into c#.
The entry point is the method encrypted_request
I have no real clue about aes/rsa in python or in c#.
Maybe someone could explain the different code sections and if possible give me a hint how to implement that in c#.
Especially the magic numbers used here and there I do not understand.
modulus = ('00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7'
'b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280'
'104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932'
'575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b'
'3ece0462db0a22b8e7')
nonce = '0CoJUm6Qyw8W8jud'
pubKey = '010001'
def encrypted_request(text):
text = json.dumps(text)
secKey = createSecretKey(16)
encText = aesEncrypt(aesEncrypt(text, nonce), secKey)
encSecKey = rsaEncrypt(secKey, pubKey, modulus)
data = {'params': encText, 'encSecKey': encSecKey}
return data
def aesEncrypt(text, secKey):
pad = 16 - len(text) % 16
text = text + chr(pad) * pad
encryptor = AES.new(secKey, 2, '0102030405060708')
ciphertext = encryptor.encrypt(text)
ciphertext = base64.b64encode(ciphertext).decode('u8')
return ciphertext
def rsaEncrypt(text, pubKey, modulus):
text = text[::-1]
rs = pow(int(binascii.hexlify(text), 16), int(pubKey, 16)) % int(modulus, 16)
return format(rs, 'x').zfill(256)
def createSecretKey(size):
return binascii.hexlify(os.urandom(size))[:16]
Source: https://github.com/darknessomi/musicbox/blob/master/NEMbox/api.py
My current state in c#:
private byte[] hex2Binary(string hex) {
byte[] binaryVal = new byte[hex.Length];
for (int i = 0; i < hex.Length; i++) {
string byteString = hex.Substring(i, 1);
byte b = Convert.ToByte(byteString, 16);
binaryVal[i] = b;
}
return binaryVal;
}
private string aesEncryptBase64(String plainText, string key) {
return aesEncryptBase64(plainText, hex2Binary(key));
}
private string aesEncryptBase64(String plainText, byte[] key) {
//pad = 16 - len(text) % 16
//text = text + chr(pad) * pad
int pad = 16 - plainText.Length % 16;
for (int i=0; i<pad; i++) {
plainText = plainText + ((char)pad);
}
byte[] plainBytes = null;
RijndaelManaged aes = new RijndaelManaged();
//aes.KeySize = 16;
aes.Mode = CipherMode.CBC;
aes.Key = key;
aes.IV = hex2Binary(client.neteaseFix.encryptInfo.iv);
MemoryStream ms = new MemoryStream();
CryptoStream cs = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write);
cs.Write(plainBytes, 0, plainBytes.Length);
cs.Close();
byte[] encryptedBytes = ms.ToArray();
return Convert.ToBase64String(encryptedBytes); //decode("u8")
}
Here are a couple of things I see right off the bat, but the question is a bit too open-ended:
In aesEncryptBase64 you are manually applying padding. The AES implementation in .NET does that for you. If you prefer to do it yourself you need to set aes.Padding = PaddingMode.None
In aesEncryptBase64 you create a RijndaelManaged object. Don't do that. You want AES, just use AES.Create(), which returns an AES object (not a Rijndael object).
.NET had support for the larger Rijndael algorithm before AES; and Rijndael with a block size of 128 bits is what got selected as AES, but Rijndael supports modes that AES does not, and you shouldn't really use them interchangeably (though many samples do).
In aesEncryptBase64 your aes, ms, and cs objects are all IDisposable, so you should have them in using statements.
The rsaEncrypt method in Python is doing raw RSA, which isn't supported in .NET (nor generally considered a good idea). Unless it's only called by routines which do the padding (and then it's just a pit of side-channel vulnerabilities).
If your rsaEncrypt (in Python) is only being called from routines which do the signature or encryption (or PSS or OAEP) padding then your .NET equivalent would be (using your method naming casing, instead of the normal ones in .NET)
private static rsaEncrypt(string text, string pubKey, string modulus)
{
RSAParameters rsaParams = new RSAParameters
{
Exponent = hex2Binary(pubKey),
Modulus = hex2Binary(modulus),
};
using (RSA rsa = RSA.Create())
{
rsa.ImportParameters(rsaParams);
return rsa.Encrypt(Encoding.ASCII.GetBytes(text), YOUNEEDTOPICKTHEPADDINGMODE);
}
}
It would be worlds better to improve all of the code around this, though, so that it doesn't have to do so much string re-parsing.
This code is working well in Windows Phone Silverlight project.
but this not working in Windows RT project.
its syay cryptographic and Aes and AesManaged classes missing etc.
please help me thanks.
i dont really need password and salt. its just simple take string and decrypt it.
public class DecryptionHelper
{
public static string Decrypt(string base64StringToDecrypt)
{
if (string.IsNullOrEmpty(base64StringToDecrypt))
return string.Empty;
//Set up the encryption objects
using (Aes acsp = GetProvider(Encoding.UTF8.GetBytes
(Constants.EncryptionKey)))
{
byte[] RawBytes = Convert.FromBase64String(base64StringToDecrypt);
ICryptoTransform ictD = acsp.CreateDecryptor();
//RawBytes now contains original byte array, still in Encrypted state
//Decrypt into stream
MemoryStream msD = new MemoryStream(RawBytes, 0, RawBytes.Length);
CryptoStream csD = new CryptoStream(msD, ictD, CryptoStreamMode.Read);
//csD now contains original byte array, fully decrypted
//return the content of msD as a regular string
return (new StreamReader(csD)).ReadToEnd();
}
}
private static Aes GetProvider(byte[] key)
{
Aes result = new AesManaged();
result.GenerateIV();
result.IV = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
byte[] RealKey = GetKey(key, result);
result.Key = RealKey;
return result;
}
private static byte[] GetKey(byte[] suggestedKey, SymmetricAlgorithm p)
{
byte[] kRaw = suggestedKey;
List<byte> kList = new List<byte>();
for (int i = 0; i < p.LegalKeySizes[0].MinSize; i += 8)
{
kList.Add(kRaw[(i / 8) % kRaw.Length]);
}
byte[] k = kList.ToArray();
return k;
}
}
I have the following code in Java doing key unwrap using bouncy castle provider:
private static byte[] unwrapKey(byte[] toUnwrap, String key) throws Exception {
byte[] decoded = Base64.decode(toUnwrap);
if (decoded == null || decoded.length <= 16) {
throw new RuntimeException("Bad input data.");
}
byte[] salt = new byte[16];
byte[] wrappedKey = new byte[decoded.length - 16];
System.arraycopy(decoded, 0, salt, 0, 16);
System.arraycopy(decoded, 16, wrappedKey, 0, decoded.length - 16);
PBEKeySpec pbeKeySpec = new PBEKeySpec(key.toCharArray());
SecretKey wrapperKey = SecretKeyFactory.getInstance("PBEWITHSHA256AND256BITAES-CBC-BC").generateSecret(pbeKeySpec);
PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, 10);
Cipher decCipher = Cipher.getInstance("AES/GCM/NoPadding", bcProvider);
decCipher.init(Cipher.UNWRAP_MODE, wrapperKey, parameterSpec);
return decCipher.unwrap(wrappedKey, "AES", Cipher.SECRET_KEY).getEncoded();
}
Now, I need to do the same in C#. The problem is that even though there's a port of BC to C#, I still can not get it working. Tried different things, and always get some exceptions.
For example, this code throws "pad block corrupted" exception at the second last line:
byte[] decoded = Convert.FromBase64String(toUnwrap);
if (decoded == null || decoded.Length <= 16) {
throw new System.ArgumentException("Bad input data", "toUnwrap");
}
byte[] salt = new byte[16];
byte[] wrappedKey = new byte[decoded.Length - 16];
Array.Copy(decoded, 0, salt, 0, 16);
Array.Copy(decoded, 16, wrappedKey, 0, decoded.Length - 16);
int iterationCount = 10;
String alg = "PBEWithSHA256And256BitAES-CBC-BC";
Asn1Encodable defParams = PbeUtilities.GenerateAlgorithmParameters(alg, salt, iterationCount);
char[] password = key.ToCharArray();
IWrapper wrapper = WrapperUtilities.GetWrapper(alg);
ICipherParameters parameters = PbeUtilities.GenerateCipherParameters(alg, password, defParams);
wrapper.Init(false, parameters);
byte[] pText = wrapper.Unwrap(wrappedKey, 0, wrappedKey.Length);
return pText.ToString();
I suspect that C# uses different type of padding by default, but no idea how to force "NoPadding" as in Java code.
I'm not sure, if JAVA code use rfc3994 is this case or not, because in RFC you need to provide IV, while here there's a salt, but no IV.
I wonder if anyone did it before and if so, what would be c# analogy.
I finally figured that out:
public static String unwrapKey(String toUnwrap, String key)
{
byte[] decoded = Convert.FromBase64String(toUnwrap);
if (decoded == null || decoded.Length <= 16)
{
throw new System.ArgumentException("Bad input data", "toUnwrap");
}
byte[] salt = new byte[16];
byte[] wrappedKey = new byte[decoded.Length - 16];
Array.Copy(decoded, 0, salt, 0, 16);
Array.Copy(decoded, 16, wrappedKey, 0, decoded.Length - 16);
int iterationCount = 10;
String algSpec = "AES/GCM/NoPadding";
String algName = "PBEWithSHA256And256BitAES-CBC-BC";
Asn1Encodable defParams = PbeUtilities.GenerateAlgorithmParameters(algName, salt, iterationCount);
char[] password = key.ToCharArray();
IWrapper wrapper = WrapperUtilities.GetWrapper(algSpec);
ICipherParameters parameters = PbeUtilities.GenerateCipherParameters(algName, password, defParams);
wrapper.Init(false, parameters);
byte[] keyText = wrapper.Unwrap(wrappedKey, 0, wrappedKey.Length);
return Convert.ToBase64String(keyText);
}
This will do exactly the same as the JAVA code above.
I am encrypting my data by using RSACryptoServiceProvider() class in c#. I want to decrypt the data in ubuntu, that was encrypted in c#. Can you suggest me which mechanism I need to follow in order to decrypt. Following function is for encryption:
public static void Encrypt(String PublicKey, String plainText, out String cipherText)
{
try
{
int dwKeySize = 1024;
// TODO: Add Proper Exception Handlers
RSACryptoServiceProvider rsaCryptoServiceProvider = new RSACryptoServiceProvider(dwKeySize);
rsaCryptoServiceProvider.FromXmlString(PublicKey);
int keySize = dwKeySize / 8;
byte[] bytes = Encoding.UTF32.GetBytes(plainText);
// The hash function in use by the .NET RSACryptoServiceProvider here is SHA1
// int maxLength = ( keySize ) - 2 - ( 2 * SHA1.Create().ComputeHash( rawBytes ).Length );
int maxLength = keySize - 42;
int dataLength = bytes.Length;
int iterations = dataLength / maxLength;
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i <= iterations; i++)
{
byte[] tempBytes = new byte[(dataLength - maxLength * i > maxLength) ? maxLength : dataLength - maxLength * i];
Buffer.BlockCopy(bytes, maxLength * i, tempBytes, 0, tempBytes.Length);
byte[] encryptedBytes = rsaCryptoServiceProvider.Encrypt(tempBytes, true);
// Be aware the RSACryptoServiceProvider reverses the order
// of encrypted bytes after encryption and before decryption.
// If you do not require compatibility with Microsoft Cryptographic API
// (CAPI) and/or other vendors.
// Comment out the next line and the corresponding one in the
// DecryptString function.
Array.Reverse(encryptedBytes);
// Why convert to base 64?
// Because it is the largest power-of-two base printable using only ASCII characters
stringBuilder.Append(Convert.ToBase64String(encryptedBytes));
}
cipherText = stringBuilder.ToString();
}
catch (Exception e)
{
cipherText = "ERROR_STRING";
Console.WriteLine("Exception in RSA Encrypt: " + e.Message);
//throw new Exception("Exception occured while RSA Encryption" + e.Message);
}
}
Don't use RSA like that. It's not meant to be used that way and it's way too slow.
The right way is to use a symmetric algorithm, e.g. AES, and encrypt the key you used with RSA. See my old blog entry for C# code doing just that.
You need the same mechanisms, but in reverse. Try first, ask later.