Encryption :
public static byte[] EncryptAES(Message msg)
{
byte[] encText; // This will keep the encrypted text
byte[] encLength; // This will keep the length of the encrypted text
byte[] finalEncText = null; // This keeps the encLength + encText (#####[encText] / [encLength][encText])
// Building the plaintext message :
string plainText = msg.MessageCode.ToString();
if (msg.Parameters != null)
foreach (string parameter in msg.Parameters)
plainText += parameter;
// Encrypting the plaintext :
encText = EncryptAES(plainText);
string encLen = encText.Length.ToString();
string fittedEncLen = MessageSender.FitStringIntoSize(encLen, Globals.MESSAGE_LENGTH_LEN); // Fit the length of the encrypted text into a certain size
encLength = Encoding.ASCII.GetBytes(fittedEncLen); // convert the length into byte[]
finalEncText = new byte[encLength.Length + encText.Length];
System.Buffer.BlockCopy(encLength, 0, finalEncText, 0, encLength.Length);
System.Buffer.BlockCopy(encText, 0, finalEncText, encLength.Length, encText.Length); // Copy the byte arrays into the new byte array
return finalEncText;
}
private static byte[] EncryptAES(string text)
{
// This function encrypts a plaintext message using the aes key we have from the server
if (AesKey == null || IV == null) // If we dont have an aes key / iv, dont encrypt
return Encoding.ASCII.GetBytes(text);
byte[] encryptedText;
try
{
Aes aes = Aes.Create();
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.Zeros;
aes.Key = Encoding.ASCII.GetBytes(AesKey);
aes.IV = Encoding.ASCII.GetBytes(IV);
ICryptoTransform cryptor = aes.CreateEncryptor(aes.Key, aes.IV);
using (MemoryStream memStream = new MemoryStream())
{
using (CryptoStream crypotStream = new CryptoStream(memStream, cryptor, CryptoStreamMode.Write))
{
using (StreamWriter writerStream = new StreamWriter(crypotStream))
{
writerStream.Write(text);
}
encryptedText = memStream.ToArray();
}
}
aes.Dispose();
}
catch
{
// In case of an error while encrypting, dont encrypt
encryptedText = Encoding.ASCII.GetBytes(text);
}
return encryptedText;
}
[The added fittedEncLen is basically a prefix of fixed length of 5 chars, that contains the length of the encrypted message following it, before decrypting the server reads those 5 chars and then it decrypts the encrypted part]
Sending the message to the server [TCPClient] [C#] :
public int Send(Message message)
{
/*
* Encrpyts the message and then sends it to the network stream.
*
* Return code:
* 0 on success.
* -1 on failure.
*/
byte[] msg = Cryptography.EncryptAES(message); // Encrypt the message
// Sending message
try
{
this._networkStream.Write(msg, 0, msg.Length);
this._networkStream.Flush();
}
catch
{
return -1;
}
return 0;
}
Receiving [C++] :
wstring Helper::getWideStringPartFromSocket(SOCKET sc, int bytesNum)
{
// This function reads the message from the socket, using wide string
std::wstringstream cls;
cls << getPartFromSocket(sc, bytesNum, 0);
return cls.str();
}
char* Helper::getPartFromSocket(SOCKET sc, int bytesNum, int flags)
{
if (bytesNum == 0)
return "";
char* data = new char[bytesNum + 1];
int res = recv(sc, data, bytesNum, flags);
if (res == INVALID_SOCKET)
{
string s = "Error while recieving from socket: ";
s += to_string(sc);
throw exception(s.c_str());
}
data[bytesNum] = 0;
return data;
}
BufferedString* Helper::makeBufferedString(SOCKET sc)
{
/*
The socket contains <length of encrypted message (unencrypted)> <encrypted message>.
This function will read the length of the unencrypted message, read
the encrypted message, decrypt it, store it in a BufferedString
object and return the object.
Length of length number: MESSAGE_LENGTH_LEN.
*/
int sizeOfMessage = Helper::getIntPartFromSocket(sc, MESSAGE_LENGTH_LEN);
if (sizeOfMessage == 0)
return NULL;
wstring wideString = getWideStringPartFromSocket(sc, sizeOfMessage);
string decrypted = "";
if (wideString.length() < sizeOfMessage)
{
std::wstringstream cls;
cls << wideString;
cls << getWideStringPartFromSocket(sc, sizeOfMessage - wideString.length());
wideString = cls.str();
}
SocketEncryptionKeychain* keyChain = SocketEncryptionKeychain::getKeychain(sc);
if (keyChain != nullptr) // If the socket has a keychain, decrypt the message
decrypted = Cryptography::decryptAES(wideString, keyChain->getKey(), keyChain->getIV()); // Try to decrypt the message
else // If the keychain is null, just convert the widestring to a string
decrypted = wideStringToString(wideString);
return new BufferedString(decrypted);
}
SocketEncryptionKeychain basically contains the AES Key and IV for each socket
BufferedString is a class that contains the string, and you can read from it like you read from a socket [its a buffer that once you read from it, what you read is deleted] [basically a string buffer, nothing special]
Decrypting [C++]:
string Cryptography::decryptAES(wstring cipherText, byte aesKey[], byte iv[])
{
if (aesKey == nullptr || iv == nullptr) // If the key or iv are null, dont decrypt
return Helper::wideStringToString(cipherText);
string plaintext;
try
{
// Decrypt :
byte* cipher = wideStringToByteArray(cipherText); // Convert the wide string to byte*
CryptoPP::AES::Decryption aesDecryption(aesKey, 32);
CryptoPP::CBC_Mode_ExternalCipher::Decryption ecbDecryption(aesDecryption, iv);
CryptoPP::StreamTransformationFilter stfDecryptor(ecbDecryption, new CryptoPP::StringSink(plaintext), StreamTransformationFilter::ZEROS_PADDING);
stfDecryptor.Put(cipher, cipherText.length());
stfDecryptor.MessageEnd();
Helper::safeDelete(cipher);
}
catch (CryptoPP::InvalidCiphertext& ex)
{
// In case of an error don't decrypt
plaintext = Helper::wideStringToString(cipherText);
}
return plaintext;
}
byte* Cryptography::wideStringToByteArray(wstring text)
{
// This function translates the wstring into a byte*
byte* bytes = new byte[text.length()]; // Convert the wstring to byte*
for (int i = 0; i < text.length(); i++)
{
bytes[i] = text[i];
}
return bytes;
}
[Helper::safeDelete is a function that just deletes the pointer and sets it as null]
The decryption only fails once in a while
You may have other problems, but here's one:
using (CryptoStream crypotStream = new CryptoStream(memStream, cryptor, CryptoStreamMode.Write))
{
using (StreamWriter writerStream = new StreamWriter(crypotStream))
{
writerStream.Write(text);
}
encryptedText = memStream.ToArray();
}
You drained the CryptoStream's output before telling the CryptoStream it was done. So you've possibly lost up to 16 bytes.
You need to either:
call FlushFinalBlock() on crypotStream(sic).
don't call memStream.ToArray() until after the using for the CryptoStream has exited.
So, the problem was while parsing the char* to wstring in the function
The problem in this function is the way I parse it:
wstring Helper::getWideStringPartFromSocket(SOCKET sc, int bytesNum)
{
// This function reads the message from the socket, using wide string
std::wstringstream cls;
cls << getPartFromSocket(sc, bytesNum, 0);
return cls.str();
}
I used a wstringstream, and the encrypted text can sometimes contain null-terminating character.
So instead of using a wstringstream I used this:
wstring Helper::getWideStringPartFromSocket(SOCKET sc, int bytesNum)
{
// This function reads the message from the socket, using wide string
char* readBuffer = getPartFromSocket(sc, bytesNum, 0);
return wstring(&readBuffer[0], &readBuffer[bytesNum]);
}
and then it does not cut the message at null-character
Related
I am currently working on transforming my C# AES-GCM cryptography code to PHP. However, after some research, the text encrypted by my PHP system cannot be decrypted by the C# one. I want to know if there is any difference from both codes:
C# with BouncyCastle:
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
using System;
using System.IO;
using System.Text;
//the helper for all AES methods
public class AESHelper {
private const int KEY_BIT_SIZE = 256;
private const int MAC_BIT_SIZE = 128;
private const int NONCE_BIT_SIZE = 128;
private readonly SecureRandom random;
private static AESHelper instance;
public static AESHelper Instance //property of this class. Create an instance if it is not created yet
{
get
{
if (instance == null)
instance = new AESHelper();
return instance;
}
}
public AESHelper()
{
random = new SecureRandom();
}
//decrypt with strings
public string Decrypt(string message, string key, int nonSecretPayloadLength = 0)
{
if (string.IsNullOrEmpty(message))
throw new ArgumentException("Message required!", "message");
var decodedKey = Convert.FromBase64String(key);
var cipherText = Convert.FromBase64String(message);
var plainText = DecryptWithKey(cipherText, decodedKey, nonSecretPayloadLength);
return Encoding.UTF8.GetString(plainText);
}
//encrypt with strings
public string Encrypt(string text, string key, byte[] nonSecretPayload = null)
{
if (string.IsNullOrEmpty(text))
throw new ArgumentException("Text required!", "text");
var decodedKey = Convert.FromBase64String(key);
var plainText = Encoding.UTF8.GetBytes(text);
var cipherText = EncryptWithKey(plainText, decodedKey, nonSecretPayload);
return Convert.ToBase64String(cipherText);
}
//create new key
public string NewKey()
{
var key = new byte[KEY_BIT_SIZE / 8];
random.NextBytes(key);
return Convert.ToBase64String(key);
}
//decrypt with byte array
private byte[] DecryptWithKey(byte[] message, byte[] key, int nonSecretPayloadLength = 0)
{
if (key == null || key.Length != KEY_BIT_SIZE / 8)
throw new ArgumentException(String.Format("Key needs to be {0} bit!", KEY_BIT_SIZE), "key");
if (message == null || message.Length == 0)
throw new ArgumentException("Message required!", "message");
using (var cipherStream = new MemoryStream(message))
using (var cipherReader = new BinaryReader(cipherStream))
{
var nonSecretPayload = cipherReader.ReadBytes(nonSecretPayloadLength);
var nonce = cipherReader.ReadBytes(NONCE_BIT_SIZE / 8);
var cipher = new GcmBlockCipher(new AesEngine());
var parameters = new AeadParameters(new KeyParameter(key), MAC_BIT_SIZE, nonce, nonSecretPayload);
cipher.Init(false, parameters);
var cipherText = cipherReader.ReadBytes(message.Length - nonSecretPayloadLength - nonce.Length);
var plainText = new byte[cipher.GetOutputSize(cipherText.Length)];
try
{
var len = cipher.ProcessBytes(cipherText, 0, cipherText.Length, plainText, 0);
cipher.DoFinal(plainText, len);
}
catch (InvalidCipherTextException)
{
return null;
}
return plainText;
}
}
//encrypt with byte array
private byte[] EncryptWithKey(byte[] text, byte[] key, byte[] nonSecretPayload = null)
{
if (key == null || key.Length != KEY_BIT_SIZE / 8)
throw new ArgumentException(String.Format("Key needs to be {0} bit!", KEY_BIT_SIZE), "key");
nonSecretPayload = nonSecretPayload ?? new byte[] { };
var nonce = new byte[NONCE_BIT_SIZE / 8];
random.NextBytes(nonce, 0, nonce.Length);
var cipher = new GcmBlockCipher(new AesEngine());
var parameters = new AeadParameters(new KeyParameter(key), MAC_BIT_SIZE, nonce, nonSecretPayload);
cipher.Init(true, parameters);
var cipherText = new byte[cipher.GetOutputSize(text.Length)];
var len = cipher.ProcessBytes(text, 0, text.Length, cipherText, 0);
cipher.DoFinal(cipherText, len);
using (var combinedStream = new MemoryStream())
{
using (var binaryWriter = new BinaryWriter(combinedStream))
{
binaryWriter.Write(nonSecretPayload);
binaryWriter.Write(nonce);
binaryWriter.Write(cipherText);
}
return combinedStream.ToArray();
}
}
}
Here is the PHP system:
<?php
echo '<pre>';
$hash_string = 'qIANSOwtdfF4y5Yk33ZLE5s6KwKBAeu6qzJRG84Sjjo=';
echo "password : ";
var_dump($hash_string);
echo '<hr>';
$decode_string = base64_decode($hash_string);
$app_cc_aes_key = substr($decode_string, 0, 32);
$cipher = 'aes-256-gcm';
$iv_len = openssl_cipher_iv_length($cipher);
echo "app_cc_aes_key : ";
var_dump($app_cc_aes_key);
echo '<br>';
echo "cipher :";
var_dump($cipher);
echo '<hr>';
$data = '7bc9d6ae-982f-11e9-bc42-526af7764f64';
echo "data : {$data}";
echo '<hr>';
$tag_length = 16;
$iv = openssl_random_pseudo_bytes($iv_len);
$tag = "";
$encrypt = openssl_encrypt($data, $cipher, $app_cc_aes_key, OPENSSL_RAW_DATA, $iv, $tag, "", $tag_length);
$encrypt_text = base64_encode($iv.$tag.$encrypt);
echo "encrypt :";
var_dump($encrypt);
echo '<br>';
echo "encrypt_text :";
var_dump($encrypt_text);
echo '<hr>';
$decoded_text = base64_decode($encrypt_text);
$iv = substr($decoded_text, 0, $iv_len);
$tag = substr($decoded_text, $iv_len, $tag_length);
$ciphertext = substr($decoded_text, $iv_len + $tag_length);
$decrypt_text = openssl_decrypt($ciphertext, $cipher, $app_cc_aes_key, OPENSSL_RAW_DATA, $iv, $tag);
echo "decrypt_text : {$decrypt_text}";
echo '<hr>';
?>
Can anyone tell me if there is something missing or different in the PHP code that makes them done differently? Or if there is some internal difference between the PHP functions and the BouncyCastle functions that make them different?
In the C#-code, the data are concatenated in the following order during encryption:
nonSecretPyload nonce cipherText
Here cipherText consists of two parts, the encrypted message and the authentication tag. Appending the tag to the encrypted message is done automatically by GcmBlockCipher#DoFinal.
In the PHP-code, the data are concatenated in the following order during encryption:
$iv $tag $encrypt
Here $iv is the counterpart to nonce. In contrast to GcmBlockCipher#DoFinal, the PHP-method openssl_encrypt returns only the encrypted message ($encrypt). The authentication tag is returned in a separate variable (6th openssl_encrypt-parameter $tag). Therefore, $tag and $encrypt correspond in reverse order to cipherText. The additional authenticated data, i.e. the counterpart to nonSecretPyload are not considered in the PHP-code at all.
It is immediately apparent that the orders of the individual components in the two codes are different. This means that a message encrypted in the C#-code cannot be decrypted in the PHP-code (and vice versa). For this to be possible, the order in the PHP-code must be changed as follows:
$aad $iv $encrypt $tag
Here $aad is the counterpart to nonSecretPyload. The order (as well the consideration of the additional authenticated data) must be adapted both in the encryption part and in the decryption part.
In addition, different IV lengths are used: In the C#-code 16 bytes, in the PHP-code 12 bytes (the latter because openssl_cipher_iv_length('aes-256-gcm') returns 12), where 12 bytes is actually the recommended length. For compatibility, a uniform IV length must be used in both codes!
I use this code (found here c sharp helper aes encryption) to encrypt a string and the encrypted string I want to save to a file.
#region "Encrypt Strings and Byte[]"
// Note that extension methods must be defined in a non-generic static class.
// Encrypt or decrypt the data in in_bytes[] and return the result.
public static byte[] CryptBytes(string password, byte[] in_bytes, bool encryptAES)
{
// Make an AES service provider.
AesCryptoServiceProvider aes_provider = new AesCryptoServiceProvider();
// Find a valid key size for this provider.
int key_size_bits = 0;
for (int i = 4096; i > 1; i--)
{
if (aes_provider.ValidKeySize(i))
{
key_size_bits = i;
break;
}
}
Debug.Assert(key_size_bits > 0);
Console.WriteLine("Key size: " + key_size_bits);
// Get the block size for this provider.
int block_size_bits = aes_provider.BlockSize;
// Generate the key and initialization vector.
byte[] key = null;
byte[] iv = null;
byte[] salt = { 0x0, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0xF1, 0xF0, 0xEE, 0x21, 0x22, 0x45 };
MakeKeyAndIV(password, salt, key_size_bits, block_size_bits, out key, out iv);
// Make the encryptor or decryptor.
ICryptoTransform crypto_transform;
if (encryptAES)
{
crypto_transform = aes_provider.CreateEncryptor(key, iv);
}
else
{
crypto_transform = aes_provider.CreateDecryptor(key, iv);
}
// Create the output stream.
using (MemoryStream out_stream = new MemoryStream())
{
// Attach a crypto stream to the output stream.
using (CryptoStream crypto_stream = new CryptoStream(out_stream,
crypto_transform, CryptoStreamMode.Write))
{
// Write the bytes into the CryptoStream.
crypto_stream.Write(in_bytes, 0, in_bytes.Length);
try
{
crypto_stream.FlushFinalBlock();
}
catch (CryptographicException)
{
// Ignore this exception. The password is bad.
}
catch
{
// Re-throw this exception.
throw;
}
// return the result.
return out_stream.ToArray();
}
}
}
// String extensions to encrypt and decrypt strings.
public static byte[] EncryptAES(this string the_string, string password)
{
System.Text.ASCIIEncoding ascii_encoder = new System.Text.ASCIIEncoding();
byte[] plain_bytes = ascii_encoder.GetBytes(the_string);
return CryptBytes(password, plain_bytes, true);
}
public static string DecryptAES(this byte[] the_bytes, string password)
{
byte[] decrypted_bytes = CryptBytes(password, the_bytes, false);
System.Text.ASCIIEncoding ascii_encoder = new System.Text.ASCIIEncoding();
return ascii_encoder.GetString(decrypted_bytes);
}
public static string CryptString(string password, string in_string, bool encrypt)
{
// Make a stream holding the input string.
byte[] in_bytes = Encoding.ASCII.GetBytes(in_string);
using (MemoryStream in_stream = new MemoryStream(in_bytes))
{
// Make an output stream.
using (MemoryStream out_stream = new MemoryStream())
{
// Encrypt.
CryptStream(password, in_stream, out_stream, true);
// Return the result.
out_stream.Seek(0, SeekOrigin.Begin);
using (StreamReader stream_reader = new StreamReader(out_stream))
{
return stream_reader.ReadToEnd();
}
}
}
}
// Convert a byte array into a readable string of hexadecimal values.
public static string ToHex(this byte[] the_bytes)
{
return ToHex(the_bytes, false);
}
public static string ToHex(this byte[] the_bytes, bool add_spaces)
{
string result = "";
string separator = "";
if (add_spaces) separator = " ";
for (int i = 0; i < the_bytes.Length; i++)
{
result += the_bytes[i].ToString("x2") + separator;
}
return result;
}
// Convert a string containing 2-digit hexadecimal values into a byte array.
public static byte[] ToBytes(this string the_string)
{
List<byte> the_bytes = new List<byte>();
the_string = the_string.Replace(" ", "");
for (int i = 0; i < the_string.Length; i += 2)
{
the_bytes.Add(
byte.Parse(the_string.Substring(i, 2),
System.Globalization.NumberStyles.HexNumber));
}
return the_bytes.ToArray();
}
#endregion // Encrypt Strings and Byte[]
With the code above you will get a list byte with this function it wil be converted to a list char
// Return a string that represents the byte array
// as a series of hexadecimal values separated
// by a separator character.
public static string ToHex(this byte[] the_bytes, char separator)
{
return BitConverter.ToString(the_bytes, 0).Replace('-', separator);
}
I get my data from a list of strings encrypt them like this and want to write them to a file
var encryptedLines = (from line in output
select Helper.ToHex(Encryption.EncryptAES(line, symKey),' ').ToList());
but File.WriteAllLines(fileWrite, encryptedLines); always give me the exception form the title or if i write result it of course just writes down System.Collections.Generic.List`1[System.Char] because it doesnt realy convert the datatype to list string
That beeing said I dont understand why I cant just write all lines of chars to a file?
I tried .ToString() or var result = encryptedLines.Select(c => c.ToString()).ToList();
You may either convert your char list to char array or convert the char list to a string using
listOfChars.Aggregate("", (str, x) => str + x);
The second approach is not recommended as it has a quadratic complexity (check the comments on this answer)
UPDATE:
After the comments by Mr. Lee I checked back again and I find this to be way more efficient:
listOfChars.Aggregate(new StringBuilder(""), (str, x) => str.Append(x));
I am working on re-writing our encryption class to be FIPS compliant, and in doing so have to re-work how we're handling non-secret payload data. At the moment, I'm writing out the size of my non-secret payload, then writing the size of my IV. I follow that up by writing my non-secret payload and IV, with all of these writes sharing a BinaryWriter. Lastly, I then share the same MemoryStream and write my the data needing to be encrypted into the the CryptoStream.
This is what the class currently looks like:
public class Encryption
{
private const int SaltBlockSize = 8;
private const int SaltBitSize = 64;
private const int KeyBitSize = 256;
private const int SaltIterations = 10000;
private const int nonSecretPayloadOffsetInPayload = 0;
private const int ivOffsetInPayload = 1;
public byte[] GetNonSecretPayload(byte[] completePayload)
{
byte[] nonSecretPayload;
using (var memoryStream = new MemoryStream(completePayload))
{
using (var binaryReader = new BinaryReader(memoryStream))
{
int nonSecretPayloadLength = binaryReader.ReadInt32();
binaryReader.BaseStream.Position = 3;
nonSecretPayload = binaryReader.ReadBytes(nonSecretPayloadLength);
}
}
return nonSecretPayload;
}
public byte[] EncryptMessageWithPassword(byte[] secretMessage, string password, byte[] nonSecretPayload = null)
{
if (string.IsNullOrEmpty(password))
{
throw new InvalidOperationException("You can not provide an empty password, you must give a string that is at least 12 characters in size. If you just want to obfuscate the message without any protection, an alternative way is to use a Base64 String");
}
else if (password.Length < 12)
{
throw new InvalidOperationException("The minimum size your password can be is 12 characters.");
}
byte[] saltHash;
byte[] saltKey = this.CreateSaltKeysFromPassword(password, 0, out saltHash);
byte[] encryptedValue = null;
using (AesCryptoServiceProvider aesProvider = new AesCryptoServiceProvider())
{
aesProvider.Key = saltKey;
aesProvider.Mode = CipherMode.CBC;
aesProvider.Padding = PaddingMode.PKCS7;
aesProvider.GenerateIV();
using (MemoryStream memoryStream = new MemoryStream())
{
// Write our IV out first so we can pull the IV off later during decryption.
// The IV does not need to be encrypted, it is safe to store as as unencrypted buffer in the encrypted byte array.
using (BinaryWriter ivWriter = new BinaryWriter(memoryStream, Encoding.UTF8, true))
{
// The first two writes to the stream should be the size of the non-secret payload
// and the size of the IV. If no payload exists, then we write 0.
if (nonSecretPayload == null || nonSecretPayload.Length == 0)
{
ivWriter.Write(0);
}
else
{
ivWriter.Write(nonSecretPayload.Length);
}
ivWriter.Write(aesProvider.IV.Length);
// If we have a payload, write it out.
if (nonSecretPayload != null && nonSecretPayload.Length > 0)
{
ivWriter.Write(nonSecretPayload);
}
// Write the Initialization Vector.
ivWriter.Write(aesProvider.IV);
}
// Create our encryptor and write the secret message to the encryptor stream.
var encryptor = aesProvider.CreateEncryptor(saltKey, aesProvider.IV);
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
{
cryptoStream.Write(secretMessage, 0, secretMessage.Length);
cryptoStream.FlushFinalBlock();
}
// Get the non-secret payload, IV, payload and IV lengths and encrypted data back as an array of bytes.
encryptedValue = memoryStream.ToArray();
}
}
return encryptedValue;
}
public string EncryptMessageWithPassword(string secretMessage, string password, byte[] nonSecretPayLoad = null)
{
byte[] secreteMessageBytes = Encoding.UTF8.GetBytes(secretMessage);
byte[] encryptedMessage = this.EncryptMessageWithPassword(secreteMessageBytes, password, nonSecretPayLoad);
return Convert.ToBase64String(encryptedMessage);
}
private byte[] CreateSaltKeysFromPassword(string password, int nonSecretPayloadSize, out byte[] saltHash)
{
byte[] saltKey;
//Use Random Salt to prevent pre-generated weak password attacks.
using (var generator = new Rfc2898DeriveBytes(password, SaltBitSize / SaltBlockSize, SaltIterations))
{
// Get a generated salt derived from the user password, hashed n-times where n = SaltIterations
saltHash = generator.Salt;
//Generate Keys
saltKey = generator.GetBytes(KeyBitSize / SaltBlockSize);
}
return saltKey;
}
}
I would expect in my GetNonSecretPayload(byte[] payload); that by setting the position, or using binaryReader.BaseStream.Seek(2); to skip the IV length item, I would skip the IV size entry in the byte[] array and be able to read the bytes associated with the actual non-secret data. This doesn't work though, presumably because this isn't an array underneath the covers that I can just move to the next element in the array, skipping the IV length wrote out originally.
I have the following unit test.
[TestClass]
public class EncryptionTests
{
private const string _ContentToEncrypt = "This is a test to make sure the encryption Type actually encrypts the data right.";
private const string _Password = "EncryptedPassword1";
[TestMethod]
public void Extract_non_secret_payload_content_from_encrypted_string()
{
// Arrange
var encryption = new Encryption();
string nonSecretData = "My payload is not considered secret and can be pulled out of the payload without decrypting";
// Convert the secret and non-secret data into a byte array
byte[] payload = Encoding.UTF8.GetBytes(nonSecretData);
byte[] encodedBytes = Encoding.UTF8.GetBytes(_ContentToEncrypt);
// Encrypt the secret data while injecting the nonsecret payload into the encrypted stream.
byte[] encryptedValue = encryption.EncryptMessageWithPassword(encodedBytes, _Password, payload);
// Act
// Pull the non-secret payload out of the encrypted message - without having to decrypt it.
byte[] UnencryptedPayloadWithinEncryptedArray = encryption.GetNonSecretPayload(encryptedValue);
string payloadContent = Encoding.UTF8.GetString(UnencryptedPayloadWithinEncryptedArray);
// Assert
Assert.AreEqual(nonSecretData, payloadContent);
}
}
What I get with my current binaryReader.BaseStream.Position = 3 is
"\0\u0010\0\0\0My payload is not considered secret and can be pulled out of the payload without decry"
I've read and wrote data like this in the past using a BinaryWriter, but I've never had to seek through it in order to skip data. What am I doing wrong here?
I have to request data from an external existing webservice written in C#.
This webservice requires some of the data to be encrypted (The connection uses an SSL connection, some of the data is aes encrypted)
On the php site openssl is used for decrypting.
The following settings are used on the c# site
(This are the default values for the AesCryptoServiceProvider):
Algorithm: AES
Padding: PKCS7
Mode: CBC
Keysize: 256
The padding for PKCS7 works as following:
01 If 1 byte is missing
02 02 If 2 bytes are missing
and so on
so this values are not added by the padding.
What am I doing wrong?
I've checked this with c#, php and ruby - the decrypted data starts with 255, 254
To reproduce use the following parameters:
data:1234567890123456
key: First1
salt(iv):Data
using System;
using System.Security.Cryptography;
using System.Text;
using System.IO;
namespace crypto_test
{
class MainClass
{
public static void Main(string[] args)
{
bool running = true;
while (running)
{
Console.WriteLine("Enter data:");
var data = Console.ReadLine();
Console.WriteLine("Enter key:");
var key = Console.ReadLine();
Console.WriteLine("Enter iv:");
var iv = Console.ReadLine();
Console.WriteLine("Enter d for decode");
var decode = (Console.ReadLine() == "d");
string encoded=Crypt(data, key, iv, decode);
Console.WriteLine(encoded);
if (!decode)
{
encoded= Crypt(encoded, key, iv, true);
Console.WriteLine(encoded);
}
Console.WriteLine("quit to exit");
running = !(Console.ReadLine() == "quit");
}
}
public static string Crypt(string value, string password, string salt, bool decrypt)
{
DeriveBytes rgb = new Rfc2898DeriveBytes(password, Encoding.Unicode.GetBytes(salt));
SymmetricAlgorithm algorithm = new AesCryptoServiceProvider();
byte[] rgbKey = rgb.GetBytes(algorithm.KeySize >> 3);
byte[] rgbIV = rgb.GetBytes(algorithm.BlockSize >> 3);
Console.WriteLine("rbKey: size:{0} key:{1}", (algorithm.KeySize >> 3), GetHex(rgbKey));
Console.WriteLine("rgbIV: size:{0} key:{1}", (algorithm.BlockSize >> 3), GetHex(rgbIV));
ICryptoTransform transform = decrypt ? algorithm.CreateDecryptor(rgbKey, rgbIV) : algorithm.CreateEncryptor(rgbKey, rgbIV);
Console.WriteLine("Mode {0}", algorithm.Mode);
Console.WriteLine("PAdding {0}", algorithm.Padding);
using (MemoryStream buffer = new MemoryStream())
{
using (CryptoStream stream = new CryptoStream(buffer, transform, CryptoStreamMode.Write))
{
try
{
if (decrypt)
{
byte[] data = Convert.FromBase64String(value);
stream.Write(data,0,data.Length);
}
else
{
using (StreamWriter writer = new StreamWriter(stream, Encoding.Unicode))
{
writer.Write(value);
}
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
byte[] buff = buffer.ToArray();
if (decrypt)
{
return Encoding.Unicode.GetString(buff) + "\r\n" + GetHex(buff);
}
else
return Convert.ToBase64String(buff);
}
}
public static string GetHex(byte[] data)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < data.Length; ++i)
sb.Append(data[i].ToString("X2"));
return sb.ToString();
}
}
}
I have not found an equivalent to Rfc2898DeriveBytes until now,
so I copied the key and iv
php
<?php
$salt='Data';
$pass='First1';
$data='1234567890123456';
$encrypted_base64='VKNd9Pi+cttaM6ne8pzAuFbH1U0gJiJ2Wlbbr1rU5z8vbIfAS6nb0/5py4p54aK7';
$encrypted=base64_decode($encrypted_base64);
$key = pack('H*', "30EE7F95F0EF4835F048A481424F2F52EE21B7CEB97F8CC437E5949DB53797D9");
$iv = pack('H*', "B29F5ECF7057065758102385509F0637");
$cipher='AES-256-CBC';
$decrypted = openssl_decrypt($encrypted,$cipher, $key,true,$iv);
for($i =0; $i<strlen($decrypted);++$i)
{
echo "char:" . ord($decrypted[$i])."<br/>";
}
echo $decrypted
?>
ruby:
require ('openssl')
require ('base64')
while true
enc_data='VKNd9Pi+cttaM6ne8pzAuFbH1U0gJiJ2Wlbbr1rU5z8vbIfAS6nb0/5py4p54aK7'
data = Base64.decode64(enc_data)
key_hex='30EE7F95F0EF4835F048A481424F2F52EE21B7CEB97F8CC437E5949DB53797D9'
iv_hex='B29F5ECF7057065758102385509F0637'
key = [key_hex].pack('H*')
iv = [iv_hex].pack('H*')
decipher = OpenSSL::Cipher::AES.new(256, :CBC)
decipher.decrypt
decipher.key = key
decipher.iv = iv
plain = decipher.update(data) + decipher.final
puts plain
puts plain.bytes
end
Good news, your decryption seems to work OK.
What you are seeing in the decrypted ciphertext is the byte order mark for UTF-16 LE, which is (incorrectly) indicated by Microsoft as Encoding.Unicode. You need to do either one off two things:
decode the text with a decoder that groks UTF-16 LE including byte order mark;
encode using much more reasonable UTF-8 encoding (in the C# code).
Personally I would put a strong preference on (2).
I have a problem decrypting an encrypted string that was encrypted in Java using the DES algorithm. I think my main problem is, that I don't see any salt or IV specifications in the java code.
I have following information:
This HexSequence is the encrypted data I have to decrypt: 9465E19A6B9060D75C3F7256ED1F4D21EDC18BB185304B92061308A32725BE760F1847E3B19C1D3548F61165EA2E785E48F61165EA2E78
Algorithm: DES, Padding: DES/ECB/NoPadding, Key: TESTKEY123
After decryption I should get: 550000000018h000000273Al2011112214340600000000000000000000000000
The java-code used to encrypt the data looks like this:
public class Encryptor {
private SecretKey secretKey;
private Cipher cipher;
public Encryptor(String algorithmName, String paddingName, String key) {
String keyHexCode = StringUtils.convertUnicodeToHexCode(key.getBytes());
try {
byte[] desKeyData = StringUtils.convertHexStringToByteArray(keyHexCode);
DESKeySpec desKeySpec = null;
try {
desKeySpec = new DESKeySpec(desKeyData);
} catch (InvalidKeyException e) {
e.printStackTrace();
}
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(algorithmName);
try {
secretKey = keyFactory.generateSecret(desKeySpec);
} catch (InvalidKeySpecException e) {
e.printStackTrace();
}
try {
cipher = Cipher.getInstance(paddingName);
} catch (NoSuchPaddingException e) {
// TODO: handle exception
}
} catch (NoSuchAlgorithmException e) {
// TODO: handle exception
}
}
private void initEncryptor(int mode) {
try {
cipher.init(mode, secretKey);
} catch (InvalidKeyException e) {
// TODO: handle exception
}
}
public String encrypt(String clearText) {
initEncryptor(Cipher.ENCRYPT_MODE);
try {
// Encrypt the cleartext
byte[] encryptedBytes = cipher.doFinal(clearText.getBytes());
return StringUtils.convertUnicodeToHexCode(encryptedBytes).toUpperCase();
} catch (IllegalBlockSizeException e) {
// TODO: handle exception
} catch (BadPaddingException e) {
// TODO: handle exception
}
return "";
}
public String decrypt(String encryptedTextHex) {
byte[] encryptedText = StringUtils.convertHexCodeSequenceToUnicode(encryptedTextHex);
initEncryptor(Cipher.DECRYPT_MODE);
try {
// Decrypt the encryptedTextHex
return new String(cipher.doFinal(encryptedText));
} catch (IllegalBlockSizeException e) {
// TODO: handle exception
} catch (BadPaddingException e) {
// TODO: handle exception
}
return "";
}
}
I tried to use following .net-code to decrypt the data:
public class URLDecryptor
{
public static string GetValue(string Data)
{
DESCryptoServiceProvider cryptoProvider = new DESCryptoServiceProvider();
byte[] bytes = System.Text.UnicodeEncoding.Unicode.GetBytes("TESTKEY123");
byte[] salt = new byte[8];
byte[] iv = new byte[8];
Rfc2898DeriveBytes password = new Rfc2898DeriveBytes("TESTKEY123", salt);
cryptoProvider.Key = password.GetBytes(8);
cryptoProvider.IV = iv;
cryptoProvider.Padding = PaddingMode.None;
cryptoProvider.Mode = CipherMode.ECB;
MemoryStream memStream = new MemoryStream(convertHexCodeSequenceToUnicode(Data));
CryptoStream cryptoStream = new CryptoStream(memStream, cryptoProvider.CreateDecryptor(cryptoProvider.Key, cryptoProvider.IV), CryptoStreamMode.Read);
StreamReader reader = new StreamReader(cryptoStream);
string value = reader.ReadToEnd;
reader.Close();
cryptoStream.Close();
return value;
}
private static byte[] convertHexCodeSequenceToUnicode(string hexCodeSequence)
{
byte[] bytes = new byte[(hexCodeSequence.Length / 2) + 1]; //This is strange
int index = 0;
int count = 0;
while (count < hexCodeSequence.Length) {
string hexCode = hexCodeSequence.Substring(count, 2);
bytes[index] = getHexValue(hexCode);
count += 2;
index += 1;
}
return bytes;
}
public static byte getHexValue(string hexCode)
{
return byte.Parse(hexCode, System.Globalization.NumberStyles.HexNumber);
}
}
What's strange is that line:
byte[] bytes = new byte[(hexCodeSequence.Length / 2) + 1];
The data is 55 bytes long but I have to put it in 56 bytes. It appends a 0-byte to the and of the array, but if I don't do this the cryptostream throws an error that the data to decrypt is too short.
If I try it this way I only get garbage as output. I'm using a empty salt and IV because I can't see which salt and IV the java code is using. Are there any default values I don't know?
EDIT:
Java code to get the byte out of the hexCode:
private static byte getNegativeValueForHexConversion(String hexCode) {
int i = Integer.parseInt(hexCode, 16);
return (byte) (i > 127 ? i - 256 : i);
}
Looks like Java uses a signed byte and .Net uses an unsigned byte for all its functions. Is this maybe the problem?
DES is a block cipher with a 64-bit block size. Thus (in ECB mode at least) the ciphertext you have to decrypt must be a multiple of 64 bits (8 bytes) long. Yours is 55 bytes, so you do not have the full ciphertext - this is why you're having to add a zero byte. Have you run the Java code yourself and seen that the output is 55 bytes long? Is this a copy and paste error?
The exception to this would be DES used in a mode which effectively creates a key stream, that is then XORed with the plaintext to produce the ciphertext. This would include CFB, OFB and CTR modes. So one possibility is that decrypting with one of these would work (off the top of my head, I can't remember whether the .NET crypto libraries support CTR). Are you sure that ECB was specified in the Java code?
But also, you have the problem that the Java code looks like it's doing a straightforward text to hex conversion from the key text to get the key bytes, whereas the .NET code is doing an RFC-2898 compatible conversion, which will not give you the same key bytes.