For Encryption the basic idea is to
- Use PBKDF2 for key generation.
- Use AES cipher for encryption.
You can read more about this encryption at : CrytoJs officialdocs
Since Decryption is at server(C#) code we need to provide it with values such as- Salt,iv,encrypted text out of our cipher.
To pass it we encode this all info in base64 string and pass it to server.
For Encryption in Angular- (my project is in TS)
If anyone has errors such as
- "wordArray doesn't contain concat()"
- Type 'WordArray' is not assignable to type 'string'
I was able to go through few documentations and was able to find the solution to be able to combine salt, iv and cipher text.
The issue was with Types we need to have a WordArray format of all these 3 to encode.
My approach is far-fetched but it works
- Convert all LIb.WordArray to ByteArray so its easy to combine all 3 strings to one.
- Before encoding to Base64 convert the combined to WordArray.
- Now Send this encoded string to Server for extracting the salt,iv,encryted text at server end to decrypt it.
Make sure you have @types/crypto-js and crypto-js in your project. My version("crypto-js": "^4.0.0", "@types/crypto-js": "^3.1.47").
Encrytion At Angular side TypeScript:
encrypt(msg: string) {
//will have this at C# as well.
var pass = "secret";
var keySize = 256;
//random salt
var salt = CryptoJS.lib.WordArray.random(16);
// to generate key
var key = CryptoJS.PBKDF2(pass, salt, {
keySize: keySize / 32,
iterations: 1000,
});
// random IV
var iv = CryptoJS.lib.WordArray.random(128 / 8);
//will attach link where you can find these
var encrypted = CryptoJS.AES.encrypt(msg, key, {
iv: iv,
padding: CryptoJS.pad.Pkcs7,
mode: CryptoJS.mode.CBC,
});
//Convert Lib.WordArray to ByteArray so we can combine them like Concat
var saltwords = this.wordArrayToByteArray(salt);
var ivwords = this.wordArrayToByteArray(iv);
var cryptedText = this.wordArrayToByteArray(encrypted.ciphertext);
// combine everything together in ByteArray.
var header = saltwords.concat(ivwords).concat(cryptedText);
//Now convert to WordArray.
var headerWords = this.byteArrayToWordArray(header);
//Encode this to sent to server
var encodedString = CryptoJS.enc.Base64.stringify(headerWords);
return encodedString;
}
Type Conversions:
wordArrayToByteArray(wordArray) {
if (
wordArray.hasOwnProperty("sigBytes") &&
wordArray.hasOwnProperty("words")
) {
length = wordArray.sigBytes;
wordArray = wordArray.words;
}
var result = [],
bytes,
i = 0;
while (length > 0) {
bytes = this.wordToByteArray(wordArray[i], Math.min(4, length));
length -= bytes.length;
result.push(bytes);
i++;
}
return [].concat.apply([], result);
}
byteArrayToWordArray(ba) {
var wa = [],
i;
for (i = 0; i < ba.length; i++) {
wa[(i / 4) | 0] |= ba[i] << (24 - 8 * i);
}
return CryptoJS.lib.WordArray.create(wa);
}
wordToByteArray(word, length) {
var ba = [],
xFF = 0xff;
if (length > 0) ba.push(word >>> 24);
if (length > 1) ba.push((word >>> 16) & xFF);
if (length > 2) ba.push((word >>> 8) & xFF);
if (length > 3) ba.push(word & xFF);
return ba;
}
Decryption at C# is same as mentioned in answer given by EVK.
public static string Decrypt(string cipherText)
{
var password = "secret";
byte[] cipherBytes = Convert.FromBase64String(cipherText);
using (Aes encryptor = Aes.Create())
{
// extract salt (first 16 bytes)
var salt = cipherBytes.Take(16).ToArray();
// extract iv (next 16 bytes)
var iv = cipherBytes.Skip(16).Take(16).ToArray();
// the rest is encrypted data
var encrypted = cipherBytes.Skip(32).ToArray();
Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(password, salt, 1000);
encryptor.Key = pdb.GetBytes(32);
encryptor.Padding = PaddingMode.PKCS7;
encryptor.Mode = CipherMode.CBC;
encryptor.IV = iv;
using (MemoryStream ms = new MemoryStream(encrypted))
{
using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateDecryptor(), CryptoStreamMode.Read))
{
using (var reader = new StreamReader(cs, Encoding.UTF8))
{
return reader.ReadToEnd();
}
}
}
}
}
CryptoJS.AES.encrypt(text, password)? Because that derives key from password in a way which is not quite easy to replicate in C#. If that's not a requirement, it's better to derive key manually (for example with CryptoJS.PBKDF2).