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;
}
}
Related
I have a .net framework 4.5.2 project in which AES 256/GCM/NO padding implementation has to be performed. I found out that framework doesn't support GCM directly hence I tried using BouncyCastle. While passing the params it looks like something is wrong being passed to initialization. Also I wanted to make the Keysize as 256 bit . I took this page as reference
Below is the code which I'm trying :
public class BouncyCastleCustom:ICipherParameters
{
private const string ALGORITHM = "AES";
private const byte AesIvSize = 16;
private const byte GcmTagSize = 16; // in bytes
private readonly CipherMode _cipherMode = CipherMode.GCM;
//private const string _cipherMode = "GCM";
private readonly string _algorithm = ALGORITHM;
public string Encrypt(string plainText, byte[] key)
{
var random = new SecureRandom();
var iv = random.GenerateSeed(AesIvSize);
var keyParameters = CreateKeyParameters(key, iv, GcmTagSize * 8);
var cipher = CipherUtilities.GetCipher(_algorithm);
cipher.Init(true, keyParameters);/*System.ArgumentException: 'invalid parameter passed to AES init - Org.BouncyCastle.Crypto.Parameters.AeadParameters'*/
var plainTextData = Encoding.UTF8.GetBytes(plainText);
var cipherText = cipher.DoFinal(plainTextData);
return PackCipherData(cipherText, iv);
}
public string Decrypt(string cipherText, byte[] key)
{
var (encryptedBytes, iv, tagSize) = UnpackCipherData(cipherText);
var keyParameters = CreateKeyParameters(key, iv, tagSize * 8);
var cipher = CipherUtilities.GetCipher(_algorithm);
cipher.Init(false, keyParameters);
var decryptedData = cipher.DoFinal(encryptedBytes);
return Encoding.UTF8.GetString(decryptedData);
}
private ICipherParameters CreateKeyParameters(byte[] key, byte[] iv, int macSize)
{
var keyParameter = new KeyParameter(key);
if (_cipherMode == CipherMode.CBC)
{
return new ParametersWithIV(keyParameter, iv);
}
else if (_cipherMode == CipherMode.GCM)
{
return new AeadParameters(keyParameter, macSize, iv);
}
throw new Exception("Unsupported cipher mode");
}
private string PackCipherData(byte[] encryptedBytes, byte[] iv)
{
var dataSize = encryptedBytes.Length + iv.Length + 1;
if (_cipherMode == CipherMode.GCM)
dataSize += 1;
var index = 0;
var data = new byte[dataSize];
data[index] = AesIvSize;
index += 1;
if (_cipherMode == CipherMode.GCM)
{
data[index] = GcmTagSize;
index += 1;
}
Array.Copy(iv, 0, data, index, iv.Length);
index += iv.Length;
Array.Copy(encryptedBytes, 0, data, index, encryptedBytes.Length);
return Convert.ToBase64String(data);
}
private (byte[], byte[], byte) UnpackCipherData(string cipherText)
{
var index = 0;
var cipherData = Convert.FromBase64String(cipherText);
byte ivSize = cipherData[index];
index += 1;
byte tagSize = 0;
if (_cipherMode == CipherMode.GCM)
{
tagSize = cipherData[index];
index += 1;
}
byte[] iv = new byte[ivSize];
Array.Copy(cipherData, index, iv, 0, ivSize);
index += ivSize;
byte[] encryptedBytes = new byte[cipherData.Length - index];
Array.Copy(cipherData, index, encryptedBytes, 0, encryptedBytes.Length);
return (encryptedBytes, iv, tagSize);
}
public enum CipherMode //cannot access BouncyCastle inbuilt CipherMode
{
CBC,
GCM
}
}
static void Main(string[] args)
{
BouncyCastleCustom aes = new BouncyCastleCustom();
var encrypted = aes.Encrypt("testDemo", Encoding.UTF8.GetBytes("mysmallkey1234551298765134567890"));
Console.WriteLine("Encrypted testDemo: " + encrypted);
string decrypted = aes.Decrypt(encrypted, Encoding.UTF8.GetBytes("mysmallkey1234551298765134567890"));
Console.WriteLine("Decrypted: " + decrypted);
}
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'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.
I am having problems to extract a string from a memory stream. The memory stream is decorated with a crypto stream. It appears, unless I flush the crypto stream, I cannot read anything from the memory stream.
I am trying to generate multiple strings (which will then be parsed to numerical values) within a for-loop. So far I can't read from a memory stream while the crypto stream is still active.
As one can see, I am trying to measure the runtime but the length of my memory stream always appears to be zero. I also need to find an efficient way to get a string from the encrypted byte array which i extract from the memory stream.
Stopwatch watch = new Stopwatch();
MemoryStream ms = new MemoryStream();
ICryptoTransform encryptor = aesInstance.CreateEncryptor(aesInstance.Key, aesInstance.IV);
CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write);
UTF8Encoding encoder = new UTF8Encoding();
int counter = (int)numericUpDown2.Value;
byte[] text;
byte[] num;
watch.Start();
for (int k = 0; k < rounds; k++) {
text = encoder.GetBytes(counter.ToString());
cs.Write(text, 0, text.Length);
cs.FlushFinalBlock();
num = new byte[ms.Length];
ms.Read(num, 0, (int)num.Length);
ms.Flush();
counter++;
}
watch.Stop();
A simple example based on what I wrote in the comment:
var lst = new List<string> {
"Foo",
"Bar",
"FooBarFooBarFooBarFooBar",
"FooBar",
};
MemoryStream ms = new MemoryStream();
var aesInstance = Aes.Create();
foreach (var str in lst)
{
ICryptoTransform encryptor = aesInstance.CreateEncryptor(aesInstance.Key, aesInstance.IV);
byte[] bytes = Encoding.UTF8.GetBytes(str);
byte[] encrypted = encryptor.TransformFinalBlock(bytes, 0, bytes.Length);
byte[] length = BitConverter.GetBytes(encrypted.Length);
ms.Write(length, 0, length.Length);
ms.Write(encrypted, 0, encrypted.Length);
}
ms.Position = 0;
while (ms.Position < ms.Length)
{
ICryptoTransform decryptor = aesInstance.CreateDecryptor(aesInstance.Key, aesInstance.IV);
byte[] length = new byte[4];
int read = ms.Read(length, 0, length.Length);
if (read < length.Length)
{
throw new Exception();
}
int length2 = BitConverter.ToInt32(length, 0);
byte[] encrypted = new byte[length2];
read = ms.Read(encrypted, 0, encrypted.Length);
if (read < encrypted.Length)
{
throw new Exception();
}
byte[] decrypted = decryptor.TransformFinalBlock(encrypted, 0, encrypted.Length);
string str = Encoding.UTF8.GetString(decrypted);
Console.WriteLine("Encrypted: {0} bytes, value: {1}", encrypted.Length, str);
}
The length of each encrypted "packet" is prepended as an Int32 to the packet. If you watch the output, you'll see that for the strings given the length of the packet is always 16 or 32. For longer strings it will go up by 16 at a time (48, 64, 80, 96...). Note that there is a bug with the CBC mode, so you can't correctly TransformFinalBlock twice or you'll get an error on decryption. To solve this I'm recreating the encryptor/decryptor for each string. This will cause equal strings to be encrypted in the same way. So if you encrypt "Foo" twice, they will be the same XXXXXXXXXYYYYYYYY in the encrypted stream.
CTR Mode
As I wrote in the comments, the best thing would be to have a CTR mode. The CTR mode has the advantage that the encrypted stream has the same length as the non-encrypted stream, and the input stream can be encrypted/decrypted one byte at a time. Using these two "characteristics" we can modify the encryption/decryption sample to encrypt/decrypt even the string length. Note that in the AesCtr class I've added some tests based on the vectors from http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf, so the implementation should be correct.
public class AesManagedCtr : Aes
{
private AesManaged Aes;
public AesManagedCtr()
{
Aes = new AesManaged();
Aes.Mode = CipherMode.ECB;
Aes.Padding = PaddingMode.None;
}
public override byte[] IV
{
get
{
return Aes.IV;
}
set
{
Aes.IV = value;
}
}
public override byte[] Key
{
get
{
return Aes.Key;
}
set
{
Aes.Key = value;
}
}
public override int KeySize
{
get
{
return Aes.KeySize;
}
set
{
Aes.KeySize = value;
}
}
public override CipherMode Mode
{
get
{
return Aes.Mode;
}
set
{
if (value != CipherMode.ECB)
{
throw new CryptographicException();
}
}
}
public override PaddingMode Padding
{
get
{
return Aes.Padding;
}
set
{
if (value != PaddingMode.None)
{
throw new CryptographicException();
}
}
}
public override int BlockSize
{
get
{
return 8;
}
set
{
if (value != 8)
{
throw new CryptographicException();
}
}
}
public override KeySizes[] LegalBlockSizes
{
get
{
return new[] { new KeySizes(BlockSize, BlockSize, 0) };
}
}
public override int FeedbackSize
{
get
{
return Aes.FeedbackSize;
}
set
{
if (FeedbackSize != Aes.FeedbackSize)
{
throw new CryptographicException();
}
}
}
public override ICryptoTransform CreateDecryptor()
{
// Note that we always use the Aes.CreateEncryptor, even for
// decrypting, because we only have to "rebuild" the encrypted
// CTR nonce.
return CreateEncryptor();
}
public override ICryptoTransform CreateDecryptor(byte[] key, byte[] iv)
{
// Note that we always use the Aes.CreateEncryptor, even for
// decrypting, because we only have to "rebuild" the encrypted
// CTR nonce.
return CreateEncryptor(key, iv);
}
public override ICryptoTransform CreateEncryptor()
{
return new StreamCipher(Aes.CreateEncryptor(), IV);
}
public override ICryptoTransform CreateEncryptor(byte[] key, byte[] iv)
{
if (key == null)
{
throw new ArgumentNullException("key");
}
if (!ValidKeySize(key.Length * 8))
{
throw new ArgumentException("key");
}
if (iv == null)
{
throw new ArgumentNullException("iv");
}
if (iv.Length * 8 != BlockSizeValue)
{
throw new ArgumentException("iv");
}
return new StreamCipher(Aes.CreateEncryptor(key, iv), iv);
}
public override void GenerateIV()
{
Aes.GenerateIV();
}
public override void GenerateKey()
{
Aes.GenerateKey();
}
protected override void Dispose(bool disposing)
{
try
{
if (disposing)
{
Aes.Dispose();
}
}
finally
{
base.Dispose(disposing);
}
}
protected sealed class StreamCipher : ICryptoTransform
{
private ICryptoTransform Transform;
private byte[] IV;
private byte[] EncryptedIV = new byte[16];
private int EncryptedIVOffset = 0;
public StreamCipher(ICryptoTransform transform, byte[] iv)
{
Transform = transform;
// Note that in this implementation the IV/Nonce and the
// Counter described by http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_.28CTR.29
// are additioned together in a single IV, that then is
// incremented by 1 in a "big-endian" mode.
IV = (byte[])iv.Clone();
Transform.TransformBlock(IV, 0, IV.Length, EncryptedIV, 0);
}
public bool CanReuseTransform
{
get { return true; }
}
public bool CanTransformMultipleBlocks
{
get { return true; }
}
public int InputBlockSize
{
get { return 1; }
}
public int OutputBlockSize
{
get { return 1; }
}
public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
{
int count = Math.Min(inputCount, outputBuffer.Length - outputOffset);
for (int i = 0; i < count; i++)
{
if (EncryptedIVOffset == EncryptedIV.Length)
{
IncrementNonceAndResetOffset();
}
outputBuffer[outputOffset + i] = (byte)(inputBuffer[inputOffset + i] ^ EncryptedIV[EncryptedIVOffset]);
EncryptedIVOffset++;
}
return count;
}
public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
{
// This method can be reused. There is no "final block" in
// CTR mode, because characters are encrypted one by one
byte[] outputBuffer = new byte[inputCount];
TransformBlock(inputBuffer, inputOffset, inputCount, outputBuffer, 0);
return outputBuffer;
}
public void Dispose()
{
if (Transform != null)
{
Transform.Dispose();
Transform = null;
IV = null;
EncryptedIV = null;
}
GC.SuppressFinalize(this);
}
private void IncrementNonceAndResetOffset()
{
int i = IV.Length - 1;
do
{
unchecked
{
IV[i]++;
}
if (IV[i] != 0 || i == 0)
{
break;
}
i--;
}
while (true);
Transform.TransformBlock(IV, 0, IV.Length, EncryptedIV, 0);
EncryptedIVOffset = 0;
}
}
// A simple string-to-byte[] converter
private static byte[] GetBytes(string str)
{
if (str.Length % 2 != 0)
{
throw new ArgumentException();
}
byte[] bytes = new byte[str.Length / 2];
for (int i = 0; i < bytes.Length; i++)
{
bytes[i] = byte.Parse(str.Substring(i * 2, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
}
return bytes;
}
public static void Test()
{
// Taken from http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
// F.5.1 CTR-AES128.Encrypt
// F.5.2 CTR-AES128.Decrypt
// F.5.3 CTR-AES192.Encrypt
// F.5.4 CTR-AES192.Decrypt
// F.5.5 CTR-AES256.Encrypt
// F.5.6 CTR-AES256.Decrypt
string[] keys = new[]
{
// 128 bits
"2b7e151628aed2a6abf7158809cf4f3c",
// 192 bits
"8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b",
// 256 bits
"603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4",
};
string iv = "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff";
string[] plains = new[]
{
"6bc1bee22e409f96e93d7e117393172a",
"ae2d8a571e03ac9c9eb76fac45af8e51",
"30c81c46a35ce411e5fbc1191a0a52ef",
"f69f2445df4f9b17ad2b417be66c3710",
};
string[][] encrypteds = new[]
{
// 128 bits
new[]
{
"874d6191b620e3261bef6864990db6ce",
"9806f66b7970fdff8617187bb9fffdff",
"5ae4df3edbd5d35e5b4f09020db03eab",
"1e031dda2fbe03d1792170a0f3009cee",
},
// 192 bits
new[]
{
"1abc932417521ca24f2b0459fe7e6e0b",
"090339ec0aa6faefd5ccc2c6f4ce8e94",
"1e36b26bd1ebc670d1bd1d665620abf7",
"4f78a7f6d29809585a97daec58c6b050",
},
// 256 bits
new[]
{
"601ec313775789a5b7a7f504bbf3d228",
"f443e3ca4d62b59aca84e990cacaf5c5",
"2b0930daa23de94ce87017ba2d84988d",
"dfc9c58db67aada613c2dd08457941a6",
},
};
for (int i = 0; i < keys.Length; i++)
{
var aes = new AesManagedCtr();
aes.Key = GetBytes(keys[i]);
aes.IV = GetBytes(iv);
Console.WriteLine("{0} bits", aes.KeySize);
{
Console.WriteLine("Encrypt");
ICryptoTransform encryptor = aes.CreateEncryptor();
var cipher = new byte[16];
for (int j = 0; j < plains.Length; j++)
{
byte[] plain = GetBytes(plains[j]);
encryptor.TransformBlock(plain, 0, plain.Length, cipher, 0);
string cipherHex = BitConverter.ToString(cipher).Replace("-", string.Empty).ToLowerInvariant();
if (cipherHex != encrypteds[i][j])
{
throw new Exception("Error encrypting " + j);
}
Console.WriteLine(cipherHex);
}
}
Console.WriteLine();
{
Console.WriteLine("Decrypt");
ICryptoTransform decryptor = aes.CreateDecryptor();
var plain = new byte[16];
for (int j = 0; j < encrypteds[i].Length; j++)
{
byte[] encrypted = GetBytes(encrypteds[i][j]);
decryptor.TransformBlock(encrypted, 0, encrypted.Length, plain, 0);
string plainHex = BitConverter.ToString(plain).Replace("-", string.Empty).ToLowerInvariant();
if (plainHex != plains[j])
{
throw new Exception("Error decrypting " + j);
}
Console.WriteLine(plainHex);
}
}
Console.WriteLine();
}
}
}
And then
var lst = new List<string> {
"Foo",
"Bar",
"FooBarFooBarFooBarFooBar",
"FooBar",
};
MemoryStream ms = new MemoryStream();
var aesInstance = new AesManagedCtr();
ICryptoTransform encryptor = aesInstance.CreateEncryptor(aesInstance.Key, aesInstance.IV);
foreach (var str in lst)
{
byte[] bytes = Encoding.UTF8.GetBytes(str);
byte[] length = BitConverter.GetBytes(bytes.Length);
byte[] encryptedLength = encryptor.TransformFinalBlock(length, 0, length.Length);
byte[] encryptedBytes = encryptor.TransformFinalBlock(bytes, 0, bytes.Length);
ms.Write(encryptedLength, 0, encryptedLength.Length);
ms.Write(encryptedBytes, 0, encryptedBytes.Length);
}
ms.Position = 0;
ICryptoTransform decryptor = aesInstance.CreateDecryptor(aesInstance.Key, aesInstance.IV);
while (ms.Position < ms.Length)
{
byte[] encryptedLength = new byte[4];
int read = ms.Read(encryptedLength, 0, encryptedLength.Length);
if (read < encryptedLength.Length)
{
throw new Exception();
}
byte[] length = decryptor.TransformFinalBlock(encryptedLength, 0, encryptedLength.Length);
int length2 = BitConverter.ToInt32(length, 0);
byte[] encryptedBytes = new byte[length2];
read = ms.Read(encryptedBytes, 0, encryptedBytes.Length);
if (read < encryptedBytes.Length)
{
throw new Exception();
}
byte[] bytes = decryptor.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length);
string str = Encoding.UTF8.GetString(bytes);
Console.WriteLine("Encrypted: {0} bytes, value: {1}", encryptedBytes.Length, str);
}
Note the differences with the other example: here we reuse the encryptor/decryptor, because in this way every "block" is encrypted in a chain, and even if the same string is repeated twice, the encrypted version will be different.
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.