Related
I have encrypted a file in c# code using RijndaelManaged which is available in System.Security.Cryptography. This file needs to be transferred to a mobile app developed using dart/flutter and I need it to be decrypted using dart code and present it to the user. How can this be done?
Below shown is the code to do the encryption in c#:
string password = keyPhrase; // Your Key Here
UnicodeEncoding UE = new UnicodeEncoding();
byte[] key = UE.GetBytes(password);
string cryptFile = outputFile;
FileStream fsCrypt = new FileStream(cryptFile, FileMode.Create);
RijndaelManaged RMCrypto = new RijndaelManaged();
CryptoStream cs = new CryptoStream(fsCrypt,
RMCrypto.CreateEncryptor(key, key),
CryptoStreamMode.Write);
FileStream fsIn = new FileStream(inputFile, FileMode.Open);
int data;
while ((data = fsIn.ReadByte()) != -1)
cs.WriteByte((byte)data);
fsIn.Close();
cs.Close();
fsCrypt.Close();
Thank you
I ran into the same problem. After many hours, a solution was found. My code is based on this question1 and question2 Code on C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
var m_strPassPhrase = "YYYYYYYYYYYYYYYYYYY";
var p_strSaltValue = "XXXXXXXXXXXXXXXXX";
var m_strPasswordIterations = 2;
var m_strInitVector = "ZZZZZZZZZZZZZZZZ";
var plainText = "myPassword";
var blockSize = 32;
var saltValueBytes = Encoding.ASCII.GetBytes(p_strSaltValue);
var password = new Rfc2898DeriveBytes(m_strPassPhrase, saltValueBytes, m_strPasswordIterations);
var keyBytes = password.GetBytes(blockSize);
var symmetricKey = new RijndaelManaged();
var initVectorBytes = Encoding.ASCII.GetBytes(m_strInitVector);
var encryptor = symmetricKey.CreateEncryptor(keyBytes, initVectorBytes);
var memoryStream = new System.IO.MemoryStream();
var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write);
var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
cryptoStream.FlushFinalBlock();
var cipherTextBytes = memoryStream.ToArray();
memoryStream.Close();
cryptoStream.Close();
var cipherText = Convert.ToBase64String(cipherTextBytes);
Console.WriteLine(cipherText);
Console.WriteLine("\n end");
}
}
}
For flutter you can use pointycastle
Code on Dart(use decryptString and cryptString methods):
import 'dart:convert';
import 'package:pointycastle/block/aes_fast.dart';
import 'dart:typed_data';
import 'package:pointycastle/export.dart';
import 'package:pointycastle/key_derivators/pbkdf2.dart';
import 'package:pointycastle/paddings/pkcs7.dart';
import 'package:pointycastle/pointycastle.dart';
const KEY_SIZE = 32; // 32 byte key for AES-256
const ITERATION_COUNT = 2;
const SALT = "XXXXXXXXXXXXXXXXX";
const INITIAL_VECTOR = "ZZZZZZZZZZZZZZZZ";
const PASS_PHRASE = "YYYYYYYYYYYYYYYYYYY";
Future<String> cryptString(String text) async {
String encryptedString = "";
final mStrPassPhrase = toUtf8(PASS_PHRASE);
encryptedString =
AesHelper.encrypt(mStrPassPhrase, toUtf8(text), mode: AesHelper.CBC_MODE);
return encryptedString;
}
Future<String> decryptString(String text) async {
String decryptedString = "";
final mStrPassPhrase = toUtf8(PASS_PHRASE);
decryptedString =
AesHelper.decrypt(mStrPassPhrase, toUtf8(text), mode: AesHelper.CBC_MODE);
return decryptedString;
}
///MARK: AesHelper class
class AesHelper {
static const CBC_MODE = 'CBC';
static const CFB_MODE = 'CFB';
static Uint8List deriveKey(dynamic password,
{String salt = '',
int iterationCount = ITERATION_COUNT,
int derivedKeyLength = KEY_SIZE}) {
if (password == null || password.isEmpty) {
throw new ArgumentError('password must not be empty');
}
if (password is String) {
password = createUint8ListFromString(password);
}
Uint8List saltBytes = createUint8ListFromString(salt);
Pbkdf2Parameters params =
new Pbkdf2Parameters(saltBytes, iterationCount, derivedKeyLength);
KeyDerivator keyDerivator =
new PBKDF2KeyDerivator(new HMac(new SHA1Digest(), 64));
keyDerivator.init(params);
return keyDerivator.process(password);
}
static Uint8List pad(Uint8List src, int blockSize) {
var pad = new PKCS7Padding();
pad.init(null);
int padLength = blockSize - (src.length % blockSize);
var out = new Uint8List(src.length + padLength)..setAll(0, src);
pad.addPadding(out, src.length);
return out;
}
static Uint8List unpad(Uint8List src) {
var pad = new PKCS7Padding();
pad.init(null);
int padLength = pad.padCount(src);
int len = src.length - padLength;
return new Uint8List(len)..setRange(0, len, src);
}
static String encrypt(String password, String plaintext,
{String mode = CBC_MODE}) {
String salt = toASCII(SALT);
Uint8List derivedKey = deriveKey(password, salt: salt);
KeyParameter keyParam = new KeyParameter(derivedKey);
BlockCipher aes = new AESFastEngine();
var ivStr = toASCII(INITIAL_VECTOR);
Uint8List iv =
createUint8ListFromString(ivStr);
BlockCipher cipher;
ParametersWithIV params = new ParametersWithIV(keyParam, iv);
switch (mode) {
case CBC_MODE:
cipher = new CBCBlockCipher(aes);
break;
case CFB_MODE:
cipher = new CFBBlockCipher(aes, aes.blockSize);
break;
default:
throw new ArgumentError('incorrect value of the "mode" parameter');
break;
}
cipher.init(true, params);
Uint8List textBytes = createUint8ListFromString(plaintext);
Uint8List paddedText = pad(textBytes, aes.blockSize);
Uint8List cipherBytes = _processBlocks(cipher, paddedText);
return base64.encode(cipherBytes);
}
static String decrypt(String password, String ciphertext,
{String mode = CBC_MODE}) {
String salt = toASCII(SALT);
Uint8List derivedKey = deriveKey(password, salt: salt);
KeyParameter keyParam = new KeyParameter(derivedKey);
BlockCipher aes = new AESFastEngine();
var ivStr = toASCII(INITIAL_VECTOR);
Uint8List iv = createUint8ListFromString(ivStr);
Uint8List cipherBytesFromEncode = base64.decode(ciphertext);
Uint8List cipherIvBytes =
new Uint8List(cipherBytesFromEncode.length + iv.length)
..setAll(0, iv)
..setAll(iv.length, cipherBytesFromEncode);
BlockCipher cipher;
ParametersWithIV params = new ParametersWithIV(keyParam, iv);
switch (mode) {
case CBC_MODE:
cipher = new CBCBlockCipher(aes);
break;
case CFB_MODE:
cipher = new CFBBlockCipher(aes, aes.blockSize);
break;
default:
throw new ArgumentError('incorrect value of the "mode" parameter');
break;
}
cipher.init(false, params);
int cipherLen = cipherIvBytes.length - aes.blockSize;
Uint8List cipherBytes = new Uint8List(cipherLen)
..setRange(0, cipherLen, cipherIvBytes, aes.blockSize);
Uint8List paddedText = _processBlocks(cipher, cipherBytes);
Uint8List textBytes = unpad(paddedText);
return new String.fromCharCodes(textBytes);
}
static Uint8List _processBlocks(BlockCipher cipher, Uint8List inp) {
var out = new Uint8List(inp.lengthInBytes);
for (var offset = 0; offset < inp.lengthInBytes;) {
var len = cipher.processBlock(inp, offset, out, offset);
offset += len;
}
return out;
}
}
///MARK: HELPERS
Uint8List createUint8ListFromString(String s) {
Uint8List ret = Uint8List.fromList(s.codeUnits);
return ret;
}
String toUtf8(value) {
var encoded = utf8.encode(value);
var decoded = utf8.decode(encoded);
return decoded;
}
String toASCII(value) {
var encoded = ascii.encode(value);
var decoded = ascii.decode(encoded);
return decoded;
}
The default mode of Rijndael in .Net is 128 bit block size - compatible with AES. Unless you are using a non-standard block size, prefer .Net's AesManaged.
You haven't specified which padding or mode you are using. The .Net default seems to be CBC, so we'll assume that. It's not clear whether it defaults to a certain padding mode.
(Note that you are using the key both as the IV and the key. The IV should be unique for each invocation of the encryption routine. TLDR - the way you are using AesManaged is insecure - don't use this code in real life.)
Also, you are decoding the key from a string. The key length of AES must be exactly 128 or 256 bits (or one of the more unusual ones). Unless you have chosen your string well, it is unlikely to UTF-8 encode to an exact key length. Also, by using a string you are only using bytes in the key that happen to be characters. Typically, to use a string as a password you would convert it to a key using a key derivation algorithm (e.g. PBKDF2) rather than just UTF-8 encoding it.
With all that said, if your password is exactly 16 (or 32 long) and your file is an exact multiple of 16 bytes (if it is not, you need to decide how to pad it) you should be able to decrypt it like this:
import 'dart:convert';
import 'dart:io';
import 'package:pointycastle/export.dart';
main() async {
var key = utf8.encode('abcdefghijklmnop');
var cipher = CBCBlockCipher(AESFastEngine())
..init(false, ParametersWithIV<KeyParameter>(KeyParameter(key), key));
var cipherText = await File('encryptedFile').readAsBytes();
var plainText = cipher.process(cipherText);
await File('decryptedFile').writeAsBytes(plainText, flush: true);
}
I need to correct an encryption method written in C#.
First, a little background: I am taking charge of an existing web application with an ecma6 / html frontend and c# web api .net standard 4.6 backend.
It has many integrations with different customers for user identification. Some of the integrations simply navigate to a customers URL to do a login process on the infrastructure of the customer, and then return to the app with an encripted user token in the URL's query string.
This token is encripted using AES256 encryption.
The backend is correctly decrypting the tokens, but when I tried to use the encryption routine to build a unit test, I discovered something is wrong. When I encrypt and then decrypt a message, the decryption routine throws the following error:
Unhandled Exception:
System.Security.Cryptography.CryptographicException: Length of the data to decrypt is invalid.
Input message is "key1=value1;key2=value2" (without the quotes)
Encrypted message I get is NzcrOTc3Kzk3Nys5NzcrOTc3Kzk3Nys5NzcrOVpsVHZ2NzF3NzcrOUZ6UVlRZ3Z2djcxSVlPKy92U0V6NzcrOVNqZFY3Nys5VHpBZA==
I need to correct the implementation error in the encryption method. The implementation of the decryption method shows expected behavior, and you'll notice a double Base64 decoding done on the encrypted string: this is given, as we are integrated with an already developed encryption routine done by a customer in PERL which we detected did double encoding.
I inspected the order of operations to see a mismatch among the encryption and decryption and I was unable to detect an inconsistency, so the need to ask for help.
The code I synthesized for this test is:
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
class MainClass {
public static void Main (string[] args) {
var secretKey = "This is my secret key";
var secretIV = "This is my secret iv";
var originalMessage = "key1=value1;key2=value2";
var userToken = Cryptography.EncryptAES256CBCBase64x2(originalMessage, secretKey, secretIV);
Console.WriteLine(userToken);
var unencryptedToken = Cryptography.DecryptAES256CBCBase64x2(userToken, secretKey, secretIV);
if (originalMessage == unencryptedToken)
Console.WriteLine("All fine!");
else
Console.WriteLine("Error!");
}
}
public static class Cryptography
{
public static string DecryptAES256CBCBase64x2(string base64EncryptedString, string secretKey, string secretIV)
{
base64EncryptedString = SaveBase64String(base64EncryptedString);
var keyBytes = Encoding.UTF8.GetBytes(secretKey);
var ivBytes = Encoding.UTF8.GetBytes(secretIV);
var hash = SHA256.Create();
var keyHash = hash.ComputeHash(keyBytes);
Array.Resize(ref keyHash, 32);
var keyHashString = string.Empty;
foreach (byte x in keyHash)
keyHashString += string.Format("{0:x2}", x);
keyHash = Encoding.UTF8.GetBytes(keyHashString.Substring(0, 32));
var ivHash = hash.ComputeHash(ivBytes);
Array.Resize(ref ivHash, 16);
var ivHashString = string.Empty;
foreach (byte x in ivHash)
ivHashString += string.Format("{0:x2}", x);
ivHash = Encoding.UTF8.GetBytes(ivHashString.Substring(0, 16));
// Create an RijndaelManaged object
// with the specified key and IV.
using (var rijAlg = new RijndaelManaged())
{
rijAlg.Padding = PaddingMode.PKCS7;
rijAlg.Mode = CipherMode.CBC;
rijAlg.Key = keyHash;
rijAlg.IV = ivHash;
var encryptedBytes =
Convert.FromBase64String(
Encoding.UTF8.GetString(
Convert.FromBase64String(base64EncryptedString)));
// Create a decryptor to perform the stream transform.
var decryptor = rijAlg.CreateDecryptor(rijAlg.Key, rijAlg.IV);
// Create the streams used for decryption.
using (var msDecrypt = new MemoryStream(encryptedBytes))
{
using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (var srDecrypt = new StreamReader(csDecrypt))
{
// Read the decrypted bytes from the decrypting stream
// and place them in a string.
return srDecrypt.ReadToEnd();
}
}
}
}
}
public static string EncryptAES256CBCBase64x2(string baseString, string secretKey, string secretIV)
{
var keyBytes = Encoding.UTF8.GetBytes(secretKey);
var ivBytes = Encoding.UTF8.GetBytes(secretIV);
var hash = SHA256.Create();
var keyHash = hash.ComputeHash(keyBytes);
Array.Resize(ref keyHash, 32);
var keyHashString = string.Empty;
foreach (byte x in keyHash)
keyHashString += string.Format("{0:x2}", x);
keyHash = Encoding.UTF8.GetBytes(keyHashString.Substring(0, 32));
var ivHash = hash.ComputeHash(ivBytes);
Array.Resize(ref ivHash, 16);
var ivHashString = string.Empty;
foreach (byte x in ivHash)
ivHashString += string.Format("{0:x2}", x);
ivHash = Encoding.UTF8.GetBytes(ivHashString.Substring(0, 16));
// Create an RijndaelManaged object
// with the specified key and IV.
using (var rijAlg = new RijndaelManaged())
{
rijAlg.Padding = PaddingMode.PKCS7;
rijAlg.Mode = CipherMode.CBC;
rijAlg.Key = keyHash;
rijAlg.IV = ivHash;
var encryptedBytes = Encoding.UTF8.GetBytes(baseString);
// Create a encryptor to perform the stream transform.
var encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);
// Create the streams used for encryption.
using (var msEncrypt = new MemoryStream(encryptedBytes))
{
using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Read))
{
using (var srEncrypt = new StreamReader(csEncrypt))
{
// Read the encrypted bytes from the encrypting stream
// and place them in a string.
var result = srEncrypt.ReadToEnd();
return Convert.ToBase64String(
Encoding.UTF8.GetBytes(
Convert.ToBase64String(
Encoding.UTF8.GetBytes(result))));
}
}
}
}
}
public static string SaveBase64String(string data)
{
data = data.Replace("-", "+").Replace("_", "/");
var mod = data.Length % 4;
if (mod > 2)
mod = 1;
return data + string.Empty.PadRight(mod, '=');
}
}
At the following link an online example is available for you to try: https://repl.it/#ormasoftchile/Test-encrypt-decrypt
Thank you everyone.
In the current code, the ciphertext is stored in a string (StreamReader.ReadToEnd), which generally doesn't work, since the data are corrupted thereby. Instead, the ciphertext should be stored in a byte-array, which can be Base64-encoded if required.
To fix the problem
remove the line:
var encryptedBytes = Encoding.UTF8.GetBytes(baseString);
and replace the entire MemoryStream-block by:
using (var msEncrypt = new MemoryStream())
{
using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
swEncrypt.Write(baseString);
}
var encryptedBytes = msEncrypt.ToArray();
return Convert.ToBase64String(Encoding.UTF8.GetBytes(Convert.ToBase64String(encryptedBytes)));
}
}
Another point is the double Base64-encoding/decoding. This makes no sense and is simply redundant and degrades performance. If possible, this should be changed.
I need to implement AES encryption in 2 different projects, but one must use the .NET standard crypto libraries and the other must use BouncyCastle. Both are C# code. Relevant methods are as follows:
.NET:
internal class NETAesCryptor : IAesCryptor
{
public Tuple<byte[], byte[]> Encrypt(string plaintext, byte[] key)
{
byte[] ciphertext, iv;
using (var aes_provider = new AesCryptoServiceProvider())
{
aes_provider.Padding = PaddingMode.PKCS7;
aes_provider.GenerateIV();
iv = aes_provider.IV;
var encryptor = aes_provider.CreateEncryptor(key, iv);
using (var ms = new MemoryStream())
{
using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
{
using (var sw = new StreamWriter(cs))
{
sw.Write(plaintext);
}
ciphertext = ms.ToArray();
}
}
}
var result = new Tuple<byte[], byte[](ciphertext, iv);
return result;
}
public string Decrypt(byte[] ciphertext, byte[] iv, byte[] key)
{
string plaintext;
using (var aes_provider = new AesCryptoServiceProvider())
{
aes_provider.Padding = PaddingMode.PKCS7;
aes_provider.IV = iv;
var decryptor = aes_provider.CreateDecryptor(key, iv);
using (var ms = new MemoryStream(ciphertext))
{
using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
{
using (var sr = new StreamReader(cs))
{
plaintext = sr.ReadToEnd();
}
}
}
}
return plaintext;
}
}
Bouncycastle:
internal class BCAesCryptor : IAesCryptor
{
private SecureRandom _r;
public BCAesCryptor()
{
_r = new SecureRandom();
}
public Tuple<byte[], byte[]> Encrypt(string plaintext, byte[] key)
{
var plaintext_bytes = Encoding.UTF8.GetBytes(plaintext);
var iv = GenerateRandomBytes(16);
var engine = new AesEngine();
var cbc_cipher = new CbcBlockCipher(engine);
var cipher = new PaddedBufferedBlockCipher(cbc_cipher, new Pkcs7Padding());
var key_param = new KeyParameter(key);
var key_param_with_iv = new ParametersWithIV(key_param, iv);
cipher.Init(true, key_param_with_iv);
var ciphertext = new byte[cipher.GetOutputSize(plaintext_bytes.Length)];
var length = cipher.ProcessBytes(plaintext_bytes, ciphertext, 0);
cipher.DoFinal(ciphertext, length);
var result = new Tuple<byte[], byte[]>(ciphertext, iv);
return result;
}
public string Decrypt(byte[] ciphertext, byte[] iv, byte[] key)
{
var engine = new AesEngine();
var cbc_cipher = new CbcBlockCipher(engine);
var cipher = new PaddedBufferedBlockCipher(cbc_cipher, new Pkcs7Padding());
var key_param = new KeyParameter(key);
var key_param_with_iv = new ParametersWithIV(key_param, iv);
cipher.Init(false, key_param_with_iv);
var plaintext = new byte[cipher.GetOutputSize(ciphertext.Length)];
var length = cipher.ProcessBytes(ciphertext, plaintext, 0);
cipher.DoFinal(plaintext, length);
var result = Encoding.UTF8.GetString(plaintext);
return result;
}
private byte[] GenerateRandomBytes(int length = 16)
{
var result = new byte[length];
_r.NextBytes(result);
return result;
}
}
Encryption/decryption between .NET methods works OK, and Bouncycastle encryption/.NET decryption also works OK. But for some reason, Bouncycastle decryption adds a variable number of \0 characters at the end of the plaintext, and I don't know why is this happening.
Test code I'm using:
[TestClass]
public class AesCryptorTests
{
private byte[] _key;
private string _plaintext;
public AesCryptorTests()
{
_key = GenerateRandomBytes();
_plaintext = "Lorem ipsum dolor sit amet";
}
[TestMethod]
public void TestMethod2()
{
var bc = new BCAesCryptor();
var net = new NETAesCryptor();
var result = net.Encrypt(_plaintext, _key);
var new_plaintext = bc.Decrypt(result.Ciphertext, result.IV, _key);
Assert.AreEqual(_plaintext, new_plaintext);
}
private byte[] GenerateRandomBytes(int cantidad = 16)
{
var result = new byte[cantidad];
using (var r = new RNGCryptoServiceProvider())
{
r.GetBytes(result);
}
return result;
}
}
In the previous test, the decryption returns Lorem ipsum dolor sit amet\0\0\0\0\0\0 instead of the plaintext.
Any advice/comment would be greatly appreciated.
The Bouncy Castle can only guess the output size of the plaintext message in advance during the call to GetOutputSize. It cannot know how many padding bytes are used, because those are only available after decryption. So they would have to partially decrypt the ciphertext to know the amount of padding, and that's taking it a step too far. Therefore you get just an estimate on the high side so that the maximum number of bytes can still fit in your newly created buffer.
You'll need the return value of the ProcessBytes and DoFinal to see the actual number of bytes that are decrypted from the ciphertext (in the input buffer and internal buffer) when the methods are called. DoFinal decrypts the last block(s) and then removes the padding from the final block, so only at that time is the size of the (remaining) plaintext known.
What you're currently seeing as zero valued bytes are just the unused bytes of the buffer, as the plaintext size is smaller than the value returned by GetOutputSize.
Of course, this is all hidden in the streaming code of the .NET sample, where ReadToEnd is required to doing some advanced buffering (probably using a MemoryStream internally itself).
Following instructions from Maarten Bodewes, the final working code is as follows:
public string Decrypt(byte[] ciphertext, byte[] iv, byte[] key)
{
var engine = new AesEngine();
var cbc_cipher = new CbcBlockCipher(engine);
var cipher = new PaddedBufferedBlockCipher(cbc_cipher, new Pkcs7Padding());
var key_param = new KeyParameter(key);
var key_param_with_iv = new ParametersWithIV(key_param, iv);
cipher.Init(false, key_param_with_iv);
var decryption_buffer = new byte[cipher.GetOutputSize(ciphertext.Length)];
var initial_length = cipher.ProcessBytes(ciphertext, decryption_buffer, 0);
var last_bytes = cipher.DoFinal(decryption_buffer, initial_length);
var total_bytes = initial_length + last_bytes;
var plaintext = new byte[total_bytes];
Array.Copy(decryption_buffer, plaintext, total_bytes);
var result = Encoding.UTF8.GetString(plaintext);
return result;
}
Note that the length of the plaintext is now calculated with the integer outputs of the decryption methods, and a simple array copy is able to create a plaintext without extra characters.
I had andriod code and I tried to convert it to c#. It's a simple Encryption class. But when I try to decrypt data with it I catch: Wrong algorithm: AES or Rijndael required.
Here is my converted code:
public static string decrypt(string data)
{
byte[] dataBytes = Convert.FromBase64String(data);
SecretKey secretKey = getSecretKey(hashTheKey("ABCD"));
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(2, secretKey, new IvParameterSpec(new byte[16]),
SecureRandom.getInstance("SHA1PRNG"));
var x = cipher.doFinal(dataBytes);
return System.Text.Encoding.UTF8.GetString(x);
}
public static SecretKey getSecretKey(char[] key)
{
var secretKeyType = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
var secretkey = secretKeyType.generateSecret(new PBEKeySpec(key,
System.Text.Encoding.UTF8
.GetBytes("ABCD"),
100, 128)).getEncoded();
return new SecretKeySpec(secretkey, "AES/CBC/PKCS5Padding");
}
public static char[] hashTheKey(string key)
{
MessageDigest messageDigest = MessageDigest.getInstance("SHA1");
messageDigest.update(System.Text.Encoding.UTF8.GetBytes(key));
return Convert.ToBase64String(messageDigest.digest()).ToCharArray();
}
Here is my original android code:
private char[] hashTheKey(String key) throws UnsupportedEncodingException, NoSuchAlgorithmException {
MessageDigest messageDigest = MessageDigest.getInstance("SHA1");
messageDigest.update(key.getBytes());
return Base64.encodeToString(messageDigest.digest(),
Base64.NO_PADDING).toCharArray();
}
private SecretKey getSecretKey(char[] key) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeySpecException {
return new SecretKeySpec(
SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
.generateSecret(new PBEKeySpec(key,
"ABCD".getBytes("UTF8"),
100, 128)).getEncoded(), "AES");
}
public String decrypt(String data) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, UnsupportedEncodingException, InvalidKeySpecException {
byte[] dataBytes = Base64.decode(data, Base64.DEFAULT);
SecretKey secretKey = getSecretKey(hashTheKey("ABCD"));
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(2, secretKey, new IvParameterSpec(new byte[16]),
SecureRandom.getInstance("SHA1PRNG"));
return new String(cipher.doFinal(dataBytes));
}
c# and java are using the same well-estabilished cryptography algorithms, but differs in approach how to invoke them. It is still possible to convert the code though.
One key point is difference in base64 encoding - C# always use padding.
Converted code goes like:
const int KeySize = 128;
static string HashTheKey(string key) {
String hashKey;
using (var sha = new SHA1Managed()) {
hashKey = Convert.ToBase64String(sha.ComputeHash(Encoding.UTF8.GetBytes(key)));
}
// beware - you're on C# now so remove the padding and add the newline to match java
return hashKey.Replace("=", "") + "\n";
}
static byte[] GetSecretKey(string password) {
var salt = Encoding.UTF8.GetBytes("JVAaVhAiddKAaghraikhmaini");
using (var pass = new Rfc2898DeriveBytes(password, salt, 65536)) {
return pass.GetBytes(KeySize / 8);
}
}
static void Main(string[] args) {
string encrypted = "vtlkQHTz7/oz2weuAAkLz2Q5c2yj2LGukF7SHJjT+TA8oRLixTQSXQ7dG1O736hyT1HJxcz0P4DzzVaO5chWKKSJQ2uPEpDQJu/fZGguqDw=";
byte[] encryptedBytes = Convert.FromBase64String(encrypted);
using (var aes = new AesManaged()) {
aes.KeySize = KeySize;
aes.Padding = PaddingMode.PKCS7;
aes.Key = GetSecretKey(HashTheKey("Android"));
// you're using the same init vector in your android code
aes.IV = new byte[16];
using (var decryptor = aes.CreateDecryptor()) {
// dumps {"barcode":"12345678","token":"cad603fc-1e53-4a95-9150-f1694baa07f9"}
Console.Out.WriteLine(Encoding.UTF8.GetString(decryptor.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length)));
}
}
}
C# does not handle the encryption algorithms as Android or java do you have to use either AES or Rijndael algorithm as you can see the error to covert to the simple text into Encrypted Base64 and vice versa you can use the following class in C#
public static class Stringcipher
{
// This constant is used to determine the keysize of the encryption algorithm in bits.
// We divide this by 8 within the code below to get the equivalent number of bytes.
private const int Keysize = 256;
// This constant determines the number of iterations for the password bytes generation function.
private const int DerivationIterations = 1000;
public static string Encrypt(string plainText, string passPhrase)
{
// Salt and IV is randomly generated each time, but is preprended to encrypted cipher text
// so that the same Salt and IV values can be used when decrypting.
var saltStringBytes = Generate256BitsOfRandomEntropy();
var ivStringBytes = Generate256BitsOfRandomEntropy();
var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
{
var keyBytes = password.GetBytes(Keysize / 8);
using (var symmetricKey = new RijndaelManaged())
{
symmetricKey.BlockSize = 256;
symmetricKey.Mode = CipherMode.CBC;
symmetricKey.Padding = PaddingMode.PKCS7;
using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes))
{
using (var memoryStream = new MemoryStream())
{
using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
{
cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
cryptoStream.FlushFinalBlock();
// Create the final bytes as a concatenation of the random salt bytes, the random iv bytes and the cipher bytes.
var cipherTextBytes = saltStringBytes;
cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray();
cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray();
memoryStream.Close();
cryptoStream.Close();
return Convert.ToBase64String(cipherTextBytes);
}
}
}
}
}
}
public static string Decrypt(string cipherText, string passPhrase)
{
// Get the complete stream of bytes that represent:
// [32 bytes of Salt] + [32 bytes of IV] + [n bytes of CipherText]
var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText);
// Get the saltbytes by extracting the first 32 bytes from the supplied cipherText bytes.
var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray();
// Get the IV bytes by extracting the next 32 bytes from the supplied cipherText bytes.
var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray();
// Get the actual cipher text bytes by removing the first 64 bytes from the cipherText string.
var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8) * 2).Take(cipherTextBytesWithSaltAndIv.Length - ((Keysize / 8) * 2)).ToArray();
using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
{
var keyBytes = password.GetBytes(Keysize / 8);
using (var symmetricKey = new RijndaelManaged())
{
symmetricKey.BlockSize = 256;
symmetricKey.Mode = CipherMode.CBC;
symmetricKey.Padding = PaddingMode.PKCS7;
using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes))
{
using (var memoryStream = new MemoryStream(cipherTextBytes))
{
using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
{
var plainTextBytes = new byte[cipherTextBytes.Length];
var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
memoryStream.Close();
cryptoStream.Close();
return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
}
}
}
}
}
}
private static byte[] Generate256BitsOfRandomEntropy()
{
var randomBytes = new byte[32]; // 32 Bytes will give us 256 bits.
using (var rngCsp = new RNGCryptoServiceProvider())
{
// Fill the array with cryptographically secure random bytes.
rngCsp.GetBytes(randomBytes);
}
return randomBytes;
}
}
I was told not to use RSA to encrypt simple text but to use AES. I found a simple piece of code to implement AES:
public static class Crypto
{
#region Settings
private static int _iterations = 2;
private static int _keySize = 256;
private static string _hash = "SHA1";
private static string _salt = "aselrias38490a32"; // Random
private static string _vector = "8947az34awl34kjq"; // Random
#endregion
public static string Encrypt(string value, string password)
{
return Encrypt<AesManaged>(value, password);
}
public static string Encrypt<T>(string value, string password)
where T : SymmetricAlgorithm, new()
{
byte[] vectorBytes = Encoding.ASCII.GetBytes(_vector);
byte[] saltBytes = Encoding.ASCII.GetBytes(_salt);
byte[] valueBytes = Encoding.UTF8.GetBytes(value);
byte[] encrypted;
using (T cipher = new T())
{
PasswordDeriveBytes _passwordBytes =
new PasswordDeriveBytes(password, saltBytes, _hash, _iterations);
byte[] keyBytes = _passwordBytes.GetBytes(_keySize/8);
cipher.Mode = CipherMode.CBC;
using (ICryptoTransform encryptor = cipher.CreateEncryptor(keyBytes, vectorBytes))
{
using (MemoryStream to = new MemoryStream())
{
using (CryptoStream writer = new CryptoStream(to, encryptor, CryptoStreamMode.Write))
{
writer.Write(valueBytes, 0, valueBytes.Length);
writer.FlushFinalBlock();
encrypted = to.ToArray();
}
}
}
cipher.Clear();
}
return Convert.ToBase64String(encrypted);
}
public static string Decrypt(string value, string password)
{
return Decrypt<AesManaged>(value, password);
}
public static string Decrypt<T>(string value, string password) where T : SymmetricAlgorithm, new()
{
byte[] vectorBytes = Encoding.ASCII.GetBytes(_vector);
byte[] saltBytes = Encoding.ASCII.GetBytes(_salt);
byte[] valueBytes = Convert.FromBase64String(value);
byte[] decrypted;
int decryptedByteCount = 0;
using (T cipher = new T())
{
PasswordDeriveBytes _passwordBytes = new PasswordDeriveBytes(password, saltBytes, _hash, _iterations);
byte[] keyBytes = _passwordBytes.GetBytes(_keySize/8);
cipher.Mode = CipherMode.CBC;
try
{
using (ICryptoTransform decryptor = cipher.CreateDecryptor(keyBytes, vectorBytes))
{
using (MemoryStream from = new MemoryStream(valueBytes))
{
using (CryptoStream reader = new CryptoStream(from, decryptor, CryptoStreamMode.Read))
{
decrypted = new byte[valueBytes.Length];
decryptedByteCount = reader.Read(decrypted, 0, decrypted.Length);
}
}
}
}
catch (Exception ex)
{
return String.Empty;
}
cipher.Clear();
}
return Encoding.UTF8.GetString(decrypted, 0, decryptedByteCount);
}
}
However, this is based on a string coming back and then used to decrypt in the same program. I need to encrypt the following data in a WinForms program and the decrypt in a whole separate Windows Service program:
string fileName = System.IO.Path.Combine(Application.StartupPath, "alphaService.xml");
XDocument doc = new XDocument();
XElement xml = new XElement("Info",
new XElement("DatabaseServerName", txtServerName.Text),
new XElement("DatabaseUserName", txtDatabaseUserName.Text),
new XElement("DatabasePassword", txtDatabasePassword.Text),
new XElement("ServiceAccount", txtAccount.Text),
new XElement("ServicePassword", txtServicePassword.Text),
new XElement("RegistrationCode", txtRegistrationCode.Text));
doc.Add(xml);
doc.Save(fileName);
// Convert XML doc to byte stream
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(fileName);
// byte[] fileBytes = Encoding.Default.GetBytes(xmlDoc.OuterXml);
string encrypted = Crypto.Encrypt(xmlDoc.OuterXml, "testpass");
How can I do it? Please show sample code.
EDIT: Kevin, I have implemented your algorithm but the problem is I want to generate the key once and save it for use in the other program to decrypt but I need to pass the byte[] to the encrypt function. So I tried converting using System.Text.Encoding.ASCII.GetBytes(key); and it doesn't do it correctly. I have the wrong number of bytes for byte[] for the key.
string fileName = System.IO.Path.Combine(Application.StartupPath, "alphaService.xml");
XDocument doc = new XDocument();
XElement xml = new XElement("Info",
new XElement("DatabaseServerName", txtServerName.Text),
new XElement("DatabaseUserName", txtDatabaseUserName.Text),
new XElement("DatabasePassword", txtDatabasePassword.Text),
new XElement("ServiceAccount", txtAccount.Text),
new XElement("ServicePassword", txtServicePassword.Text),
new XElement("RegistrationCode", txtRegistrationCode.Text));
doc.Add(xml);
doc.Save(fileName);
// Read file to a string
string contents = File.ReadAllText(fileName);
string key = String.Empty;
byte[] aesKey;
using (var aes = Aes.Create())
{
// aesKey = aes.Key;
key = Convert.ToBase64String(aes.Key);
}
string sKey = "LvtZELDrB394hbSOi3SurLWAvC8adNpZiJmQDJHdfJU=";
aesKey = System.Text.Encoding.UTF8.GetBytes(sKey);
string encyptedText = EncryptDecrpt.EncryptStringToBase64String(contents, aesKey);
File.WriteAllText(fileName, encyptedText);
EDIT2: Here's both parts as they stand now. The encrypting side:
private void SaveForm()
{
try
{
string fileName = System.IO.Path.Combine(Application.StartupPath, "alphaService.xml");
XDocument doc = new XDocument();
XElement xml = new XElement("Info",
new XElement("DatabaseServerName", txtServerName.Text),
new XElement("DatabaseUserName", txtDatabaseUserName.Text),
new XElement("DatabasePassword", txtDatabasePassword.Text),
new XElement("ServiceAccount", txtAccount.Text),
new XElement("ServicePassword", txtServicePassword.Text),
new XElement("RegistrationCode", txtRegistrationCode.Text));
doc.Add(xml);
// doc.Save(fileName);
// Read file to a string
// string contents = File.ReadAllText(fileName);
string key = String.Empty;
byte[] aesKey;
//using (var aes = Aes.Create())
//{
// aesKey = aes.Key;
// key = Convert.ToBase64String(aes.Key);
//}
string sKey = "LvtZELDrB394hbSOi3SurLWAvC8adNpZiJmQDJHdfJU=";
aesKey = Convert.FromBase64String(sKey);
string encyptedText = EncryptDecrpt.EncryptStringToBase64String(doc.ToString(), aesKey);
File.WriteAllText(fileName, encyptedText);
//doc.Save(fileName);
The Windows Service side that tries to decrypt:
try
{
string path = AppDomain.CurrentDomain.BaseDirectory;
eventLog1.WriteEntry(path);
string fileName = System.IO.Path.Combine(path, "alphaService.xml");
string sKey = "LvtZELDrB394hbSOi3SurLWAvC8adNpZiJmQDJHdfJU=";
Byte[] keyBytes = Convert.FromBase64String(sKey);
var encryptedText = File.ReadAllText(fileName, new ASCIIEncoding());
string xmlStr = DecryptStringFromBase64String(encryptedText, keyBytes);
eventLog1.WriteEntry(xmlStr);
using (XmlReader reader = XmlReader.Create(new StringReader(xmlStr)))
{
reader.ReadToFollowing("DatabaseServerName");
DatabaseServerName = reader.ReadElementContentAsString();
reader.ReadToFollowing("DatabaseUserName");
DatabaseUserName = reader.ReadElementContentAsString();
reader.ReadToFollowing("DatabasePassword");
DatabasePassword = reader.ReadElementContentAsString();
reader.ReadToFollowing("RegistrationCode");
RegistrationCode = reader.ReadElementContentAsString();
}
eventLog1.WriteEntry("Configuration data loaded successfully");
}
catch (Exception ex)
{
eventLog1.WriteEntry("Unable to load configuration data. " + ex.Message);
}
The algorithm I wrote below uses a random Initialization Vector that it puts at the beginning of the encrypted value so you can encrypt the same value twice and not get the same encrypted output. This is fairly normal and lets you only pass a single "secret" back and forth.
You will need to share your secret key by some out of bounds process because both encryption and decryption need to know the key. That is a seperate topic of key exchange that is documented in other places. Here is an SO link to get you started if you need some help on it.
Also if you are "making up" random values I recommend that you don't. Use something to help you like the following which generates random bytes and then converts them into a base64 string which is easier for human usage or some types of key exchange. Note that this is just an example of how you could generate random key's... in practice this may be based on some user input that is recreatable or you use the users hash value to lookup your random key that you generate. In any event here is the code for the key...
byte[] key;
string base64Key;
using (var aes = Aes.Create())
{
// key as byte[]
key = aes.Key;
// key as base64string - which one you use depends on how you store your keys
base64Key= Convert.ToBase64String(aes.Key);
}
Usage is as follows...
// you get the base64 encoded key from somewhere
var base64Key = "+CffHxKmykUvCrrCILd4rZDBcrIoe3w89jnPNXYi0rU=";
// convert it to byte[] or alternatively you could store your key as a byte[]
// but that depends on how you set things up.
var key = Convert.FromBase64String(base64Key);
var plainText = "EncryptThis";
var encryptedText = EncryptStringToBase64String(plainText, key);
var decryptedText = DecryptStringFromBase64String(encryptedText, key);
Here are the encryption methods... EncryptStringToBase64String and DecryptStringFromBase64String.
EDIT: Great point owlstead about using Aes.BlockSize for the IV size. I've also cleaned up the arguement checks.
private const int KeySize = 256; // in bits
static string EncryptStringToBase64String(string plainText, byte[] Key)
{
// Check arguments.
if (Key == null || Key.Length <= 0)
throw new ArgumentNullException("Key");
byte[] returnValue;
using (var aes = Aes.Create())
{
aes.KeySize = KeySize;
aes.GenerateIV();
aes.Mode = CipherMode.CBC;
var iv = aes.IV;
if (string.IsNullOrEmpty(plainText))
return Convert.ToBase64String(iv);
var encryptor = aes.CreateEncryptor(Key, iv);
// Create the streams used for encryption.
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
//Write all data to the stream.
swEncrypt.Write(plainText);
}
// this is just our encrypted data
var encrypted = msEncrypt.ToArray();
returnValue = new byte[encrypted.Length + iv.Length];
// append our IV so our decrypt can get it
Array.Copy(iv, returnValue, iv.Length);
// append our encrypted data
Array.Copy(encrypted, 0, returnValue, iv.Length, encrypted.Length);
}
}
}
// return encrypted bytes converted to Base64String
return Convert.ToBase64String(returnValue);
}
static string DecryptStringFromBase64String(string cipherText, byte[] Key)
{
// Check arguments.
if (string.IsNullOrEmpty(cipherText))
return string.Empty;
if (Key == null || Key.Length <= 0)
throw new ArgumentNullException("Key");
string plaintext = null;
// this is all of the bytes
var allBytes = Convert.FromBase64String(cipherText);
using (var aes = Aes.Create())
{
aes.KeySize = KeySize;
aes.Mode = CipherMode.CBC;
// get our IV that we pre-pended to the data
byte[] iv = new byte[aes.BlockSize/8];
if (allBytes.Length < iv.Length)
throw new ArgumentException("Message was less than IV size.");
Array.Copy(allBytes, iv, iv.Length);
// get the data we need to decrypt
byte[] cipherBytes = new byte[allBytes.Length - iv.Length];
Array.Copy(allBytes, iv.Length, cipherBytes, 0, cipherBytes.Length);
// Create a decrytor to perform the stream transform.
var decryptor = aes.CreateDecryptor(Key, iv);
// Create the streams used for decryption.
using (MemoryStream msDecrypt = new MemoryStream(cipherBytes))
{
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;
}
EDIT 2: Never convert actual binary data (like a random key) into a string using a TextEncoding. If data starts life as a string and you convert into binary using an encoding then and ONLY then can you convert it from binary into a string using the proper encoding. Otherwise you will have code that works sometimes which is a recipe for torturing yourself.
// This is base64 not UTF8, unicode, ASCII or anything else!!!
string sKey = "LvtZELDrB394hbSOi3SurLWAvC8adNpZiJmQDJHdfJU=";
aesKey = Convert.FromBase64String(sKey);
Edit 3:
Why use File.WriteAllText to write the file but use File.ReadAllBytes when you read it? You can write it and read it as text and use ASCII encoding since base64 is guaranteed to be ASCII. Also Decrypt returns a decrypted string which you are not storing or using. The decrypted string is what you need to parse because it's your xml.
You can use this for saving the file...
var encryptedText = File.ReadAllText(fileName, new ASCIIEncoding());
In your decrypt you should do this...
var encryptedText = File.ReadAllText(fileName, new ASCIIEncoding());
string xmlStr = DecryptStringFromBase64String(encryptedStr , keyBytes);
EDIT 4: I've attempted to duplicate your exception and I can't make it happen... here is my test code that I'm running in a console app and it works.
public static void EncryptMethod()
{
var fileName = #"c:/text.xml";
XDocument doc = new XDocument();
XElement xml = new XElement("Info",
new XElement("DatabaseServerName", "txtServerName.Text"),
new XElement("DatabaseUserName", "txtDatabaseUserName.Text"),
new XElement("DatabasePassword", "txtDatabasePassword.Text"),
new XElement("ServiceAccount", "txtAccount.Text"),
new XElement("ServicePassword", "txtServicePassword.Text"),
new XElement("RegistrationCode", "txtRegistrationCode.Text"));
doc.Add(xml);
var sKey = "LvtZELDrB394hbSOi3SurLWAvC8adNpZiJmQDJHdfJU=";
var aesKey = Convert.FromBase64String(sKey);
string encyptedText = EncryptStringToBase64String(doc.ToString(), aesKey);
File.WriteAllText(fileName, encyptedText);
}
public static void DecryptMethod()
{
var fileName = #"c:/text.xml";
string sKey = "LvtZELDrB394hbSOi3SurLWAvC8adNpZiJmQDJHdfJU=";
Byte[] keyBytes = Convert.FromBase64String(sKey);
var encryptedText = File.ReadAllText(fileName, new ASCIIEncoding());
string xmlStr = DecryptStringFromBase64String(encryptedText, keyBytes);
using (XmlReader reader = XmlReader.Create(new StringReader(xmlStr)))
{
reader.ReadToFollowing("DatabaseServerName");
Console.WriteLine(reader.ReadElementContentAsString());
reader.ReadToFollowing("DatabaseUserName");
Console.WriteLine(reader.ReadElementContentAsString());
reader.ReadToFollowing("DatabasePassword");
Console.WriteLine(reader.ReadElementContentAsString());
reader.ReadToFollowing("RegistrationCode");
Console.WriteLine(reader.ReadElementContentAsString());
}
}
Usage from the console app...
EncryptMethod();
DecryptMethod();