I have to implement a digital envelope using AES and RSA, but I am having problems with the .NET implementation of the RSA algorithm.
I have managed to encrypt the data (AES) with the random symmetric key, but now I have to encrypt the key with RSA.
The key is an array of bytes (byte[]) and the public key I have tells me only the modulus and the public exponent, both arrays of bytes (byte[]).
Using only those two parameters, how can I encrypt my AES generated key with RSA?
The following code retrieves the message from file and encrypts it with AES.
Afterwards, the public key is read from the public key file and the modulus and the exponent are in their appropriate byte arrays. How would I continue to encrypt the symmetricKey with RSA?
String msgString = Systematic.GetFileContents(messagePath);
Byte[] initVector = new byte[] { 50, 60, 70, 80, 90, 40, 50, 60, 70, 80, 90, 40, 60, 80, 70, 90 };
Byte[] symetricKey = AesCrypt.GenerateRandomKey();
Byte[] encryptedMessage = AesCrypt.Encrypt(msgString, symetricKey, initVector, mode);
Byte[] modulus = null;
Byte[] publicExp = null;
DataFormatHelper.ReadPublicKey(publicKeyPath, "RSA", ref modulus, ref publicExp);
P.S. In reply to the answer mentioning rsa.ImportParameters:
I've tried with the rsa.ImportParameters(keyInfo) but it throws a CryptographicException ("Bad Data"). What about array sizes?
Currently, the modulus is 128 bytes and the exponent 64 bytes.
Using RSACryptoServiceProvider
static public byte[] RSAEncrypt(byte[] data,
RSAParameters keyInfo,
bool doOAEPPadding)
{
byte[] encryptedData;
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
//Import the RSA Key information. This only needs
//toinclude the public key information.
rsa.ImportParameters(keyInfo);
//Encrypt the passed byte array and specify OAEP padding.
//OAEP padding is only available on Microsoft Windows XP or later.
encryptedData = rsa.Encrypt(data, doOAEPPadding);
}
return encryptedData;
}
So what you need are the RSAParameters but all you need to set are the Modulus and the Exponent to encrypt.
Related
thanks in advance for reading.
The goal was to encrypt from command line and decrypt from c# while being as simple as possible. I'm starting from the docs: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.aes?view=net-5.0
I have a file encrypted with this OpenSSL command (no salt):
openssl enc -nosalt -aes-128-cbc -in my_file.txt -out my_file.txt.enc -p -pass pass:hello_this_is_pass
and that outputs the Key and IV
key=2F77B7A1D3BBAA3304E53D791819958A
iv =9DD22E07DD38AF129D42E8CF3689EADD
Once in VS these were read into byte arrays with:
byte[] key = Encoding.ASCII.GetBytes("2F77B7A1D3BBAA3304E53D791819958A");
byte[] iv = Encoding.ASCII.GetBytes("9DD22E07DD38AF129D42E8CF3689EADD");
var encodedBytes = File.ReadAllBytes("myEncFile.txt.enc");
The file is read into a byte array with:
This is passed to the reading method from the docs:
static string DecryptStringFromBytes_Aes(byte[] cipherText, byte[] Key, byte[] IV)
{
// Declare the string used to hold
// the decrypted text.
string plaintext = null;
// Create an Aes object
// with the specified key and IV.
using (Aes aesAlg = Aes.Create())
{
aesAlg.Key = Key;
aesAlg.IV = IV; // **This is the line that errors**
// Create a decryptor to perform the stream transform.
ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
// Create the streams used for decryption.
using (MemoryStream msDecrypt = new MemoryStream(cipherText))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
{
// Read the decrypted bytes from the decrypting stream
// and place them in a string.
plaintext = srDecrypt.ReadToEnd();
}
}
}
}
return plaintext;
}
Setting aesAlg.IV throws the following error:
System.Security.Cryptography.CryptographicException: 'Specified initialization vector (IV) does not match the block size for this algorithm.'
It looks like the default value of aesAlg.IV is byte[16]. I understand that this means the IV I'm providing is twice as big as it should be, so I assumed I must be using the wrong decryptor settings
The Key size is byte[32], and I've tried configuring the instance prior to assigning aesAlg.IV, but these changes seemed to have no effect:
aesAlg.Mode = CipherMode.CBC;
aesAlg.IVSize = 128;
aesAlg.BlockSize = 128;
aesAlg.FeedbackSize = 128;
aesAlg.Padding = PaddingMode.None;
I feel like there is something very obvious I'm missing. The similar questions on here deal with encryption, or the fact that openssl will auto add "Salt__" unless -nosalt is specified. There is also a similary named question "How I could re-encrypt in using C# so that I'd be able to decrypt with OpenSSL CLI tools?" but this does not address the error I am encountering (at least as far as I can tell). I feel like I've stuck so close to the docs that I can't be the only person who would run into this?
If anyone in the future finds this, #Topaco posted the answer in the comments. The solution is to change
byte[] key = Encoding.ASCII.GetBytes("2F77B7A1D3BBAA3304E53D791819958A");
byte[] iv = Encoding.ASCII.GetBytes("9DD22E07DD38AF129D42E8CF3689EADD");
to
byte[] key = StringToByteArrayFastest("2F77B7A1D3BBAA3304E53D791819958A");
byte[] iv = StringToByteArrayFastest("9DD22E07DD38AF129D42E8CF3689EADD");
Using StringToByteArrayFastest() from How can I convert a hex string to a byte array?
public static byte[] StringToByteArrayFastest(string hex)
{
if (hex.Length % 2 == 1)
throw new Exception("The binary key cannot have an odd number of digits");
byte[] arr = new byte[hex.Length >> 1];
for (int i = 0; i < hex.Length >> 1; ++i)
{
arr[i] = (byte)((GetHexVal(hex[i << 1]) << 4) + (GetHexVal(hex[(i << 1) + 1])));
}
return arr;
}
public static int GetHexVal(char hex)
{
int val = (int)hex;
//For uppercase A-F letters:
//return val - (val < 58 ? 48 : 55);
//For lowercase a-f letters:
//return val - (val < 58 ? 48 : 87);
//Or the two combined, but a bit slower:
return val - (val < 58 ? 48 : (val < 97 ? 55 : 87));
}
I'm using VS to encrypt some data using this function:
public static byte[] AES_Encrypt(byte[] bytesToBeEncrypted, byte[] passwordBytes)
{
byte[] encryptedBytes = null;
// Set your salt here, change it to meet your flavor:
// The salt bytes must be at least 8 bytes.
byte[] saltBytes = new byte[] { 6, 99, 26, 12, 68, 22, 89, 12, 49, 55, 92, 82, 87, 86, 10, 75, 98, 122, 73 };
using (MemoryStream ms = new MemoryStream())
{
using (RijndaelManaged AES = new RijndaelManaged())
{
AES.KeySize = 256;
AES.BlockSize = 256;
var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 1000);
AES.Key = key.GetBytes(AES.KeySize / 8);
AES.IV = key.GetBytes(AES.BlockSize / 8);
AES.Mode = CipherMode.CBC;
using (var cs = new CryptoStream(ms, AES.CreateEncryptor(), CryptoStreamMode.Write))
{
cs.Write(bytesToBeEncrypted, 0, bytesToBeEncrypted.Length);
cs.Close();
}
encryptedBytes = ms.ToArray();
}
}
return encryptedBytes;
}
My question is why when I put plain text to encrypt the output only contains readable characters that can be viewed with any text processor like notepad, but, if the data to encrypt is from a file containing special characters the output now also contains special characters that can't be viewed from text processor... why???
Example of encrypt "hello"
"fMIiLZzIKME2gTAarpQqP7y8kOjQvDS12lSOOBtaCbI="
Example of encrypt binary data:
"b!,˜à4ovƒº1úlÔÊ jô†õ ;>²Ñ)j¦¹‘åüLÖN—nU+5"
Because AES doens't deal with chars, or strings, only with bytes.
If you do want to be able to look at the result in a text editor, you'll have to encode the outputted bytes.
One of such encoders would be the base64 one.
Which would output a human "readable" string.
I found the probem... I'm an idiot... I have a Convert.ToBase64String function called when the input is plain text... I didn't see this.
Sorry.
Thans
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.
http://aes.online-domain-tools.com/
I am trying to replicate this encryption with c# using CBC but not getting the same results no matter what I try.
So far my code:
private byte[] hex2bytes(string s)
{
return Enumerable.Range(0, s.Length)
.Where(x => x % 2 == 0)
.Select(x => Convert.ToByte(s.Substring(x, 2), 16))
.ToArray();
}
private AesCryptoServiceProvider GetProvider(byte[] key)
{
AesCryptoServiceProvider result = new AesCryptoServiceProvider();
result.BlockSize = 128;
result.KeySize = 128;
result.Mode = CipherMode.CBC;
result.Padding = PaddingMode.PKCS7;
result.GenerateIV();
//result.IV = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
result.IV = hex2bytes("ad77d666311839f5665aeb2e42f64542");
byte[] RealKey = GetKey(key, result);
_key = Encoding.ASCII.GetString(RealKey);
result.Key = RealKey;
// result.IV = RealKey;
return result;
}
private 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;
}
/// <summary>
/// Encrpyts the sourceString, returns this result as an Aes encrpyted, BASE64 encoded string
/// </summary>
/// <param name="plainSourceStringToEncrypt">a plain, Framework string (ASCII, null terminated)</param>
/// <param name="passPhrase">The pass phrase.</param>
/// <returns>
/// returns an Aes encrypted, BASE64 encoded string
/// </returns>
public string EncryptString(string plainSourceStringToEncrypt, string passPhrase)
{
//Set up the encryption objects
using (AesCryptoServiceProvider acsp = GetProvider(Encoding.UTF8.GetBytes(passPhrase)))
{
byte[] sourceBytes = Encoding.UTF8.GetBytes(plainSourceStringToEncrypt);
ICryptoTransform ictE = acsp.CreateEncryptor();
//Set up stream to contain the encryption
MemoryStream msS = new MemoryStream();
//Perform the encrpytion, storing output into the stream
CryptoStream csS = new CryptoStream(msS, ictE, CryptoStreamMode.Write);
csS.Write(sourceBytes, 0, sourceBytes.Length);
csS.FlushFinalBlock();
//sourceBytes are now encrypted as an array of secure bytes
byte[] encryptedBytes = msS.ToArray(); //.ToArray() is important, don't mess with the buffer
var x = BitConverter.ToString(encryptedBytes);
//return the encrypted bytes as a BASE64 encoded string
return Convert.ToBase64String(encryptedBytes);
}
}
/// <summary>
/// Decrypts a BASE64 encoded string of encrypted data, returns a plain string
/// </summary>
/// <param name="base64StringToDecrypt">an Aes encrypted AND base64 encoded string</param>
/// <param name="passphrase">The passphrase.</param>
/// <returns>returns a plain string</returns>
public string DecryptString(string base64StringToDecrypt, string passphrase)
{
//Set up the encryption objects
using (AesCryptoServiceProvider acsp = GetProvider(Encoding.UTF8.GetBytes(passphrase)))
{
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();
}
}
OK, so according to the description the key is padded with zero bytes. You are actually repeating key bytes. Both methods are of course completely insecure, a key should just consist of random bytes.
The IV seems to be calculated over the key bytes before it is padded, using SHA-1. I don't see anything about that in your code. Note that the IV should change each time the same key is used, and using the SHA-1 over the key is therefore insecure.
The padding is zero padding, up to the size of the block. This means that if your plaintext ends with a 00 byte that you will loose data. Padding is not mentioned, but I tested this by encrypting something and then decrypting it with the same key. It seems the padding bytes are still there. This is not insecure, but as it may lead to invalid plaintext, it is definitely wrong.
There is no authentication tag (e.g. HMAC) added, meaning that anybody can just change the ciphertext and get away with it. In the best case this will lead just to garbage on the other system. In the worst case (and this one is most likely) you will completely lose confidentiality as well. This is probably what you were trying to achieve in the first place.
I hope I have given you enough pointers to create an implementation - for learning purposes or to migrate away from the given code. If you are using random crap - no other word for this - from the Internet, you will however end up with zero security. I won't provide code, as I don't want this to proliferate.
I have set up a simple symmetric AES-en/decryption in C#, but I'm having problems with the padding. According to MSDN, the padding bytes for PKCS #7 are supposed to be 0x07, but in my case it's just zero-bytes (0x00).
How is this possible? It almost seems as if this was not correctly implemented in .NET...
Here is my code:
Aes aes = new AesManaged();
aes.Key = new byte[] { /* ... */ };
aes.IV = new byte[] { /* ... */ };
// Debugging shows:
// aes.Padding = PaddingMode.PKCS7
// the data to encrypt (1 byte only, to demonstrate padding)
byte[] plainData = new byte[1] { 0xFF };
byte[] encData;
// (encrypt)
using (MemoryStream encStream = new MemoryStream())
{
using (CryptoStream cryptoStream = new CryptoStream(encStream, aes.CreateEncryptor(), CryptoStreamMode.Write))
{
cryptoStream.Write(plainData, 0, plainData.Length);
}
encData = encStream.ToArray();
}
// (new length is 16 bytes (128 bits), incl. padding)
plainData = new byte[16];
// (decrypt)
using (MemoryStream decrStream = new MemoryStream(encData))
{
using (CryptoStream cryptoStream = new CryptoStream(decrStream, aes.CreateDecryptor(), CryptoStreamMode.Read))
{
cryptoStream.Read(plainData, 0, plainData.Length);
}
}
// output:
// 16 bytes,
// 1st byte = 0xFF,
// other 15 bytes = 0x00 (instead of 0x07!)
The decryptor is correctly removing the padding that was applied by the encryptor, thus the zero bytes in your output are simply the un-touched bytes in the original plainData array. The cryptoStream.Read(...) call returns an integer indicating the number of bytes that were read (1 in this case), which you should be using to determine how many bytes in the output array are valid data.
If for whatever reason you are interested in seeing the padding bytes, you can set aes.Padding = PaddingMode.None; after the encryption is performed, but before you create the decryptor. You will then find that cryptoStream.Read(...) returns 16, and plainData has 0xff as its first byte, followed by 15 bytes of 0x0f padding (not sure why your question indicates you were expecting 0x07 though).
For PKCS7 mode it should be blocksize - contentsize, i.e. 16 - 1 = 15 in your case. Your mistake is that you expect it after decryption but padding happens internally before encryption. There are no guarantees that plainData will contain padded bytes according to mode choosen.