I'm going crazy about this one. Spend whole day and still can't understand what is going on. I'm using AES256CBC encryption both in .Net and JavaScript. For some reason I got different results, despite that I'm using same key an iv. My codes are:
JavaScript:
function convertStringToArrayBuffer(str) {
var length = str.length;
var bytes = new Uint8Array(length);
for(var i = 0; i < length; i++) {
bytes[i] = str.charCodeAt(i);
}
return bytes;
}
var keyB64 ="sy/d1Ddy/9K3p8x6pWMq2P8Qw2ftUjkkrAA7xFC7aK8=";
var viB64 = "t8eI2F+QmlUBWZJVIlTX6Q==";
var dataToEnc = "Test123!"
let dataInBytes = convertStringToArrayBuffer(dataToEnc);
let key = window.atob(keyB64);
let iv = window.atob(viB64);
console.log(key);
console.log(iv);
window.crypto.subtle.importKey("raw", convertStringToArrayBuffer(key).buffer, {name: "AES-CBC", length: 256}, false, ["encrypt"]).then(function(key){
console.log(key);
window.crypto.subtle.encrypt({name: "AES-CBC", iv: convertStringToArrayBuffer(iv).buffer}, key, dataInBytes.buffer).then(function(encrypted){
console.log(encrypted);
});
});
This one produces
.Net:
public static void Test()
{
var dataToEnc = "Test123!";
var keyB64 = "sy/d1Ddy/9K3p8x6pWMq2P8Qw2ftUjkkrAA7xFC7aK8=";
var viB64 = "t8eI2F+QmlUBWZJVIlTX6Q==";
var key = Convert.FromBase64String(keyB64);
var iv = Convert.FromBase64String(viB64);
var data = Encoding.UTF8.GetBytes(dataToEnc);
byte[] encrypted = null;
using (Aes aesAlg = Aes.Create())
{
aesAlg.Key = key;
aesAlg.IV = iv;
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
swEncrypt.Write(data);
}
encrypted = msEncrypt.ToArray();
}
}
}
}
This one produces
I belive it is something trivial, yet I can't find this. I appreciate any hint here.
If anyone else is having this issue #Topaco's comment is right way to go, I'm pasting it below
The bug is in the C# code. You have to use swEncrypt.Write(dataToEnc) instead of swEncrypt.Write(data). The overload you are currently using implicitly executes data.ToString()
Related
I'm triying to Encrypt string with C# and decrypt it using Angular crypto-js library but it's giving me different output.
I tried different c# aes encryption implementations but crypto-js library can't decrypt the encrypted data in c#. Thank you for any help.
Here is my code
Program.cs
static void Main()
{
var r = EncryptString("exampleString", "examplePassword");
Console.Write(r);
}
public static string EncryptString(string plainText, string passPhrase)
{
if (string.IsNullOrEmpty(plainText))
{
return "";
}
// generate salt
byte[] key, iv;
var salt = new byte[8];
var rng = new RNGCryptoServiceProvider();
rng.GetNonZeroBytes(salt);
DeriveKeyAndIv(passPhrase, salt, out key, out iv);
// encrypt bytes
var encryptedBytes = EncryptStringToBytesAes(plainText, key, iv);
// add salt as first 8 bytes
var encryptedBytesWithSalt = new byte[salt.Length + encryptedBytes.Length + 8];
Buffer.BlockCopy(Encoding.ASCII.GetBytes("Salted__"), 0, encryptedBytesWithSalt, 0, 8);
Buffer.BlockCopy(salt, 0, encryptedBytesWithSalt, 8, salt.Length);
Buffer.BlockCopy(encryptedBytes, 0, encryptedBytesWithSalt, salt.Length + 8, encryptedBytes.Length);
// base64 encode
return Convert.ToBase64String(encryptedBytesWithSalt);
}
private static void DeriveKeyAndIv(string passPhrase, byte[] salt, out byte[] key, out byte[] iv)
{
// generate key and iv
var concatenatedHashes = new List<byte>(48);
var password = Encoding.UTF8.GetBytes(passPhrase);
var currentHash = new byte[0];
var md5 = MD5.Create();
bool enoughBytesForKey = false;
// See http://www.openssl.org/docs/crypto/EVP_BytesToKey.html#KEY_DERIVATION_ALGORITHM
while (!enoughBytesForKey)
{
var preHashLength = currentHash.Length + password.Length + salt.Length;
var preHash = new byte[preHashLength];
Buffer.BlockCopy(currentHash, 0, preHash, 0, currentHash.Length);
Buffer.BlockCopy(password, 0, preHash, currentHash.Length, password.Length);
Buffer.BlockCopy(salt, 0, preHash, currentHash.Length + password.Length, salt.Length);
currentHash = md5.ComputeHash(preHash);
concatenatedHashes.AddRange(currentHash);
if (concatenatedHashes.Count >= 48)
enoughBytesForKey = true;
}
key = new byte[32];
iv = new byte[16];
concatenatedHashes.CopyTo(0, key, 0, 32);
concatenatedHashes.CopyTo(32, iv, 0, 16);
md5.Clear();
}
static byte[] EncryptStringToBytesAes(string plainText, byte[] key, byte[] iv)
{
// Check arguments.
if (plainText == null || plainText.Length <= 0)
throw new ArgumentNullException("plainText");
if (key == null || key.Length <= 0)
throw new ArgumentNullException("key");
if (iv == null || iv.Length <= 0)
throw new ArgumentNullException("iv");
// Declare the stream used to encrypt to an in memory
// array of bytes.
MemoryStream msEncrypt;
// Declare the RijndaelManaged object
// used to encrypt the data.
RijndaelManaged aesAlg = null;
try
{
// Create a RijndaelManaged object
// with the specified key and IV.
aesAlg = new RijndaelManaged { Mode = CipherMode.CBC, KeySize = 256, BlockSize = 128, Key = key, IV = iv };
// Create an encryptor to perform the stream transform.
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
// Create the streams used for encryption.
msEncrypt = new MemoryStream();
using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (var swEncrypt = new StreamWriter(csEncrypt))
{
//Write all data to the stream.
swEncrypt.Write(plainText);
swEncrypt.Flush();
swEncrypt.Close();
}
}
}
finally
{
// Clear the RijndaelManaged object.
aesAlg?.Clear();
}
// Return the encrypted bytes from the memory stream.
return msEncrypt.ToArray();
}
Simply decrypting it using crypto-js
let CryptoJS = require('crypto-js');
let r = CryptoJS.AES.decrypt('exampleString', 'examplePassword').toString();
The example code is attempting to decrypt the original unencrypted string, which looks to be a mistake perhaps created when trying to simplify the example code for posting the question? Either way the steps required are not too difficult, but the toString() call needs to be replaced.
var data = "U2FsdGVkX1/Zvh/5BnLfUgfbg5ROSD7Aohumr9asPM8="; // Output from C#
let r2 = CryptoJS.enc.Utf8.stringify(CryptoJS.AES.decrypt(data, 'examplePassword'));
console.log(r2);
i have such a code to generate password written in Java
MessageDigest messageDigestPassword = MessageDigest.getInstance("SHA1");
messageDigestPassword .reset();
byte[] password = "password".getBytes();
messageDigestPassword .update(password);
byte[] encryptedPassword = messageDigestPassword .digest();
String date = "2019-10-22T11:33:13.393Z";
byte[] dateBytes = date.getBytes(StandardCharsets.UTF_8);
int offset = 0;
byte[] outputBytes = new byte[dateBytes.length + encryptedPassword .length];
System.arraycopy(dateBytes, 0, outputBytes, offset, dateBytes.length);
offset += dateBytes.length;
System.arraycopy(encryptedPassword , 0, outputBytes, offset, encryptedPassword .length);
MessageDigest finalMessageDigeset = MessageDigest.getInstance("SHA-1");
finalMessageDigeset.reset();
finalMessageDigeset.update(outputBytes);
byte[] finalPasswordBytes= finalMessageDigeset .digest();
String finalBase64Password = new String(Base64.encode(finalPasswordBytes));
and im trying to rewrite it to JavaScript to use it in postman with - CryptoJS
So far i have :
function wordArrayToByteArray(wordArray, length) {
if (wordArray.hasOwnProperty("sigBytes") &&
wordArray.hasOwnProperty("words")) {
length = wordArray.sigBytes;
wordArray = wordArray.words;
}
var result = [],
bytes,
i = 0;
while (length > 0) {
bytes = wordToByteArray(wordArray[i], Math.min(4, length));
length -= bytes.length;
result.push(bytes);
i++;
}
return [].concat.apply([], result);
}
function stringToBytes ( str ) {
var ch, st, re = [];
for (var i = 0; i < str.length; i++ ) {
ch = str.charCodeAt(i); // get char
st = []; // set up "stack"
do {
st.push( ch & 0xFF ); // push byte to stack
ch = ch >> 8; // shift value down by 1 byte
}
while ( ch );
// add stack contents to result
// done because chars have "wrong" endianness
re = re.concat( st.reverse() );
}
// return an array of bytes
return re;
}
var dateFixed = "2019-10-22T11:33:13.393Z";
var fixedDateBytes = stringToBytes(dateFixed);
var sha1Password= CryptoJS.SHA1("password");
console.log("sha1Password",sha1Password.toString(CryptoJS.enc.Hex));
var sha1PasswordBytes= wordArrayToByteArray(sha1Password, 20);
var concatedBytes= fixedDateBytes.concat(sha1PasswordBytes);
var finalShaPassWords= CryptoJS.SHA1(concatedBytes);
console.log("finalShaPassWords",finalShaPassWords.toString(CryptoJS.enc.Hex));
console.log("finalShaPassWords",finalShaPassWords.toString(CryptoJS.enc.Base64));
However unfortunatelly Base64 representations written in those 2 languages doesnt match.
I have checked and bytes from date are equal. Bytes from hashed password are not. So hashing after concat fails in JavaScript.
I have checked first password hashing and generated bytes and both of them are the same. So my guess line var sha1PasswordBytes= wordArrayToByteArray(sha1Password, 20); causes that line var finalShaPassWords= CryptoJS.SHA1(concatedBytes); returns bad value.
Can someone give me some idea what is wrong? Mayby it should be written diffrent ?
Since you are using CryptoJS anyway, you can also use the CryptoJS encoders and the WordArray#concat-method, which considerably simplifies the code:
var CryptoJS = require("crypto-js");
// Input
var inPwd = "password";
var inDate = "2019-10-22T11:33:13.393Z";
// Processing
var pwdHash = CryptoJS.SHA1(inPwd); // hash and convert to WordArray
var date = CryptoJS.enc.Utf8.parse(inDate); // convert to WordArray
var joinedData = date.clone().concat(pwdHash); // join date and hashed password
var joinedDataHash = CryptoJS.SHA1(joinedData); // hash joined data
var joinedDataHashB64 = CryptoJS.enc.Base64.stringify(joinedDataHash); // convert to Base64 string
// Output
console.log("Result: " + joinedDataHashB64 ); // Output: D235TBTZMfpSyB/CDl5MHAjH5fI=
The output of this code is the same as the output of the Java-code: D235TBTZMfpSyB/CDl5MHAjH5fI=
I'm trying to replicate the Java code in JavaScript.
below is my Java code:
public static String encrypt(String input)
final byte[] SALT= { (byte) 0x21, (byte) 0x21, (byte) 0xF0, (byte) 0x55, (byte) 0xC3, (byte) 0x9F, (byte) 0x5A, (byte) 0x75 };
final int ITERATION_COUNT = 31;
{
if (input == null)
{
throw new IllegalArgumentException();
}
try
{
KeySpec keySpec = new PBEKeySpec(null, SALT, ITERATION_COUNT);
AlgorithmParameterSpec paramSpec = new PBEParameterSpec(SALT, ITERATION_COUNT);
SecretKey key = SecretKeyFactory.getInstance("PBEWithMD5AndDES").generateSecret(keySpec);
Cipher ecipher = Cipher.getInstance(key.getAlgorithm());
ecipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
byte[] enc = ecipher.doFinal(input.getBytes());
String res = new String(Base64.encodeBase64(enc));
// escapes for url
res = res.replace('+', '-').replace('/', '_').replace("%", "%25").replace("\n", "%0A");
LOGGER.info("String Encrypted Successfully");
return res;
}
catch (Exception e)
{
LOGGER.error("encrypt Exception: "+e.getMessage());
}
return "";
}
and the JavaScript code, so far hammed up is below:
var encrypt = function(){
var iterations = 31;
var key = CryptoJS.MD5("PBEWithMD5AndDES");
var salt = CryptoJS.enc.Hex.parse('0021002100f0005500C3009F005A0075');
var options = {
mode: CryptoJS.mode.CBC,
iv: salt
};
var hashedPassword = CryptoJS.MD5($scope.data.webPassword);
var encryptedPassword = CryptoJS.DES.encrypt(hashedPassword, key,options).toString();
var result = encryptedPassword.toString(CryptoJS.enc.Base64);
}
but with both the encryption the encoded string I'm getting is different.
PBEwithMD5andDES is obsolete technology and should not be used nowadays. This answer is only provided for demonstration purposes.
PBEwithMD5andDES is defined in PKCS#5 v1.5 which is nothing more than deriving key+IV using PBKDF1 (with MD5) and encrypting with DES.
var password = CryptoJS.enc.Utf8.parse("test");
var salt = CryptoJS.enc.Hex.parse("2121F055C39F5A75");
var iterations = 31;
// PBE according to PKCS#5 v1.5 (in other words: PBKDF1)
var md5 = CryptoJS.algo.MD5.create();
md5.update(password);
md5.update(salt);
var result = md5.finalize();
md5.reset();
for(var i = 1; i < iterations; i++) {
md5.update(result);
result = md5.finalize();
md5.reset();
}
// splitting key and IV
var key = CryptoJS.lib.WordArray.create(result.words.slice(0, 2));
var iv = CryptoJS.lib.WordArray.create(result.words.slice(2, 4));
var encrypted = CryptoJS.DES.encrypt("test", key, {
iv: iv
});
enchex.innerHTML = encrypted.ciphertext.toString();
encbase64.innerHTML = encrypted.ciphertext.toString(CryptoJS.enc.Base64);
<script src="https://cdn.rawgit.com/CryptoStore/crypto-js/3.1.2/build/rollups/tripledes.js"></script>
<script src="https://cdn.rawgit.com/CryptoStore/crypto-js/3.1.2/build/rollups/md5.js"></script>
<div>Hex: <span id="enchex"></span></div>
<div>Base64: <span id="encbase64"></span></div>
Here is a jsFiddle to experiment with and here is the example Java code. Both produce the same result in Hex: aa8101a7d63093c6.
Security considerations:
PBEwithMD5andDES should not be used and there are better alternatives like PBEWithHmacSHA256AndAES_128 which require a slightly different approach.
The number of iterations must be large (a thousand to a million) in order to make it hard to brute-force the password. DES only provides 56 bits of security, so it is even possible to brute-force the key directly with today's means.
The salt must be randomly generated in order to achieve semantic security. The salt itself doesn't need to be secret. Since it has a known length it can be simply prepended to the ciphertext and sliced off before decryption.
I have problem encrypting files with SJCL and javascript.
I have managed to encrypt text-files with using FileReader API and readAsBinaryString.
When it comes to encrypting pdf/png/.. then problem arrises probably due to encoding.
I found that I can use readAsArrayBuffer which suits this task perfectly, so I basically read file and create new typed array with new Uint8Array() but i dont know how exactly I am supposed to encrypt such a data.
Here's my code:
/** Convert from an array of bytes to a bitArray. */
function toBitArrayCodec(bytes) {
var out = [], i, tmp=0;
for (i=0; i<bytes.length; i++) {
tmp = tmp << 8 | bytes[i];
if ((i&3) === 3) {
out.push(tmp);
tmp = 0;
}
}
if (i&3) {
out.push(sjcl.bitArray.partial(8*(i&3), tmp));
}
return out;
}
/** Convert from a bitArray to an array of bytes. */
function fromBitArrayCodec(arr) {
var out = [], bl = sjcl.bitArray.bitLength(arr), i, tmp;
for (i=0; i<bl/8; i++) {
if ((i&3) === 0) {
tmp = arr[i/4];
}
out.push(tmp >>> 24);
tmp <<= 8;
}
return out;
}
var reader = new FileReader();
reader.readAsArrayBuffer(fileData); //filedata comes from function
reader.onload = function() {
var bytes = new Uint8Array(reader.result);
var bits = toBitArrayCodec(bytes);
var crypt = sjcl.encrypt("aaaaa", bits);
var decrypt = sjcl.decrypt("aaaaa", crypt);
var byteNumbers = fromBitArrayCodec(decrypt);
var byteArray = new Uint8Array(byteNumbers);
saveData(byteArray, 'png.png');
I am getting error on
Uncaught URIError: URI malformed sjcl.js:12sjcl.codec.utf8String.fromBits sjcl.js:12sjcl.json.decrypt sjcl.js:44reader.onload
I need to know how to encrypt uint8array or another alternative how to encrypt(pdf/png/..) files.
The plaintext in sjcl is expected to be utf8 encoded. Encrypting a manually built bitArray works, because the encryption is done on the bitArray and it doesn't have to be decoded. But at the end of the decryption is an encoding step which converts the recovered plaintext bitArray into a utf8string. This doesn't work, because it contains unprintable characters, because the source was probably binary.
The solution would be to encode it as Base64 before encrypting and convert it back after decrypting.
var bytes = new Uint8Array(reader.result);
var bits = toBitArrayCodec(bytes);
var base64bits = sjcl.codec.base64.fromBits(bits); // added
var crypt = sjcl.encrypt("aaaaa", base64bits);
var base64decrypt = sjcl.decrypt("aaaaa", crypt);
var decrypt = sjcl.codec.base64.toBits(base64decrypt); // added
var byteNumbers = fromBitArrayCodec(decrypt);
var byteArray = new Uint8Array(byteNumbers);
i have this pseudo code and i would like to convert it into working code:
string constructSignature(string timestamp, string UID, string secretKey) {
baseString = timestamp + "_" + UID; // Construct a "base string" for signing
binaryBaseString = ConvertUTF8ToBytes(baseString); // Convert the base string into a binary array
binaryKey = ConvertFromBase64ToBytes(secretKey); // Convert secretKey from BASE64 to a binary array
binarySignature = hmacsha1(binaryKey, baseString); // Use the HMAC-SHA1 algorithm to calculate the signature
signature = ConvertToBase64(binarySignature); // Convert the signature to a BASE64
return signature;
}
any idea?
thanks
Some ideas:
Don't calculate BinaryBaseString, given that you never use it
Don't refer to UTF8 for no reason at all -- strings in JavaScript are Unicode, which is only distantly connected to UTF.
Don't put implementation details in your pseudocode -- especially ones you don't actually need (like assuming the secret key should be in "bytes", whatever that means, when in fact, the standard library takes and returns strings).
Don't write comments that just restate what the code is doing.
Which leaves us with:
var constructSignature = function(timestamp, UID, secretKey) {
return Crypto.HMAC(Crypto.SHA1, timestamp + "_" + UID, secretKey,
{ asString: true });
};
Here is a full implementation of your code:
let atob = function(a){
return new Buffer(a, 'base64').toString('binary');
}
function ConvertUTF8ToBytes(str) {
var utf8 = unescape(encodeURIComponent(str));
var arr = [];
for (var i = 0; i < utf8.length; i++) {
arr.push(utf8.charCodeAt(i));
}
return new Uint8Array(arr);
}
let ConvertFromBase64ToBytes = function (b64Data){
var byteCharacters = atob(b64Data);
var byteNumbers = new Array(byteCharacters.length);
for (var i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
return new Uint8Array(byteNumbers);
}
let constructSignature = function(timestamp, UID, secretKey) {
let baseString = timestamp + "_" + UID;
let binaryBaseString = ConvertUTF8ToBytes(baseString)
let binaryKey = ConvertFromBase64ToBytes(secretKey)
let binarySignature = Crypto.createHmac('sha1', binaryKey)
.update( binaryBaseString )
.digest('base64');
return binarySignature;
};