AES-шифрование в JS-эквиваленте C # - PullRequest
0 голосов
/ 13 декабря 2018

Мне нужно зашифровать строку, используя шифрование AES.Это шифрование происходило в C # ранее, но его необходимо преобразовать в JavaScript (будет выполняться в браузере).

Текущий код в C # для шифрования выглядит следующим образом -

public static string EncryptString(string plainText, string encryptionKey)
{
    byte[] clearBytes = Encoding.Unicode.GetBytes(plainText);

    using (Aes encryptor = Aes.Create())

    {
        Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(encryptionKey, new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 });
        encryptor.Key = pdb.GetBytes(32);
        encryptor.IV = pdb.GetBytes(16);
        using (MemoryStream ms = new MemoryStream())
        {
            using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateEncryptor(), CryptoStreamMode.Write))

            {
                cs.Write(clearBytes, 0, clearBytes.Length);
                cs.Close();
            }
            plainText = Convert.ToBase64String(ms.ToArray());
        }
    }
    return plainText;
}

Я пытался использовать CryptoJS для репликации той же функциональности, но он не дает мне эквивалентную зашифрованную строку base64.Вот мой код CryptoJS -

function encryptString(encryptString, secretKey) {
    var iv = CryptoJS.enc.Hex.parse('Ivan Medvedev');
    var key = CryptoJS.PBKDF2(secretKey, iv, { keySize: 256 / 32, iterations: 500 });

    var encrypted = CryptoJS.AES.encrypt(encryptString, key,{iv:iv);
    return encrypted;
}

Зашифрованная строка должна быть отправлена ​​на сервер, который сможет ее расшифровать.Сервер может дешифровать зашифрованную строку, сгенерированную из кода C #, но не зашифрованную строку, сгенерированную из кода JS.Я попытался сравнить зашифрованные строки, сгенерированные как кодом, так и обнаружил, что код C # генерирует более длинные зашифрованные строки.Например, сохраняя «Пример строки» в качестве обычного текста и «Пример ключа» в качестве ключа, я получаю следующий результат -

C# - eAQO+odxOdGlNRB81SHR2XzJhyWtz6XmQDko9HyDe0w=
JS - 9ex5i2g+8iUCwdwN92SF+A==

Длина зашифрованной строки JS всегда короче, чем в C #.Есть ли что-то, что я делаю не так?Мне просто нужно скопировать код C # в код JS.

Обновление:
Мой текущий код после Ответ Зергата это -

function encryptString(encryptString, secretKey) {
    var keyBytes = CryptoJS.PBKDF2(secretKey, 'Ivan Medvedev', { keySize: 48 / 4, iterations: 1000 });
    console.log(keyBytes.toString());

    // take first 32 bytes as key (like in C# code)
    var key = new CryptoJS.lib.WordArray.init(keyBytes.words, 32);
    // skip first 32 bytes and take next 16 bytes as IV
    var iv = new CryptoJS.lib.WordArray.init(keyBytes.words.splice(32 / 4), 16);

    console.log(key.toString());
    console.log(iv.toString());

    var encrypted = CryptoJS.AES.encrypt(encryptString, key, { iv: iv });
    return encrypted;
}

Как показано в его / ее ответе, что если код C # преобразует обычный текст в байты с использованием ASCII вместо Unicode, то и код C #, и JS будут давать точные результаты.Но так как я не могу изменить код расшифровки, я должен преобразовать код в эквивалент исходного кода C #, который использовал Unicode.

Итак, я попытался понять, в чем разница междубайтовый массив между ASCII и Unicode байтовым преобразованием в C #.Вот что я нашел -

ASCII Byte Array: [69,120,97,109,112,108,101,32,83,116, 114, 105, 110, 103]
Unicode Byte Array: [69,0,120,0,97,0,109,0,112,0,108,0,101,0,32,0,83,0,116,0, 114,0, 105,0, 110,0, 103,0]

Таким образом, некоторые дополнительные байты доступны для каждого символа в C # (поэтому Unicode выделяет в два раза больше байтов каждому символу, чем ASCII).

Вот разница междуи преобразование Unicode и ASCII соответственно -

ASCII
clearBytes: [69,120,97,109,112,108,101,32,83,116,114,105,110,103,]
encryptor.Key: [123,213,18,82,141,249,182,218,247,31,246,83,80,77,195,134,230,92,0,125,232,210,135,115,145,193,140,239,228,225,183,13,]
encryptor.IV: [101,74,46,177,46,233,68,252,83,169,211,13,249,61,118,167,]
Result: eQus9GLPKULh9vhRWOJjog==

Unicode:
clearBytes: [69,0,120,0,97,0,109,0,112,0,108,0,101,0,32,0,83,0,116,0,114,0,105,0,110,0,103,0,]
encryptor.Key: [123,213,18,82,141,249,182,218,247,31,246,83,80,77,195,134,230,92,0,125,232,210,135,115,145,193,140,239,228,225,183,13,]
encryptor.IV: [101,74,46,177,46,233,68,252,83,169,211,13,249,61,118,167,]
Result: eAQO+odxOdGlNRB81SHR2XzJhyWtz6XmQDko9HyDe0w=

Таким образом, поскольку генерируемый ключ и iv имеют одинаковый байтовый массив в подходах Unicode и ASCII, он не должен был генерировать разные выходные данные, но каким-то образом это происходит,Я думаю, что это из-за длины clearBytes, поскольку он использует свою длину для записи в CryptoStream.

Я попытался посмотреть, каков вывод сгенерированных байтов в коде JS, и обнаружил, что он использует слова, которые необходимы длябыть преобразованы в строки, используя метод toString().

keyBytes: 7bd512528df9b6daf71ff653504dc386e65c007de8d2877391c18cefe4e1b70d654a2eb12ee944fc53a9d30df93d76a7
key: 7bd512528df9b6daf71ff653504dc386e65c007de8d2877391c18cefe4e1b70d
iv: 654a2eb12ee944fc53a9d30df93d76a7

Поскольку я не могу влиять на длину сгенерированной зашифрованной строки в коде JS (нет прямого доступа к потоку записи), поэтому все еще застрял здесь.

Ответы [ 2 ]

0 голосов
/ 15 декабря 2018

TL; DR окончательный код выглядит следующим образом -

function encryptString(encryptString, secretKey) {
    encryptString = addExtraByteToChars(encryptString);
    var keyBytes = CryptoJS.PBKDF2(secretKey, 'Ivan Medvedev', { keySize: 48 / 4, iterations: 1000 });
    console.log(keyBytes.toString());
    var key = new CryptoJS.lib.WordArray.init(keyBytes.words, 32);
    var iv = new CryptoJS.lib.WordArray.init(keyBytes.words.splice(32 / 4), 16);
    var encrypted = CryptoJS.AES.encrypt(encryptString, key, { iv: iv, });
    return encrypted;
}

function addExtraByteToChars(str) {
    let strResult = '';
    for (var i = 0; i < str.length; ++i) {
        strResult += str.charAt(i) + String.fromCharCode(0);
    }
    return strResult;
}

Объяснение:

Код C # в Ответ Зергата (Спасибо ему / ей) использовал ASCII для преобразования обычного текста в байты, в то время как мой код на C # использовал Unicode.Юникод назначал дополнительный байт каждому символу в результирующем байтовом массиве, что не влияло на генерацию ключа и байтов iv, но влияло на результат, поскольку длина encryptedString зависела от длины байтов, сгенерированных из plainText.
Как видно из следующих байтов, сгенерированных для каждого из них с использованием "Example String" и "Example Key" в качестве plainText и secretKey соответственно -

ASCII
clearBytes: [69,120,97,109,112,108,101,32,83,116,114,105,110,103,]
encryptor.Key: [123,213,18,82,141,249,182,218,247,31,246,83,80,77,195,134,230,92,0,125,232,210,135,115,145,193,140,239,228,225,183,13,]
encryptor.IV: [101,74,46,177,46,233,68,252,83,169,211,13,249,61,118,167,]
Result: eQus9GLPKULh9vhRWOJjog==

Unicode:
clearBytes: [69,0,120,0,97,0,109,0,112,0,108,0,101,0,32,0,83,0,116,0,114,0,105,0,110,0,103,0,]
encryptor.Key: [123,213,18,82,141,249,182,218,247,31,246,83,80,77,195,134,230,92,0,125,232,210,135,115,145,193,140,239,228,225,183,13,]
encryptor.IV: [101,74,46,177,46,233,68,252,83,169,211,13,249,61,118,167,]
Result: eAQO+odxOdGlNRB81SHR2XzJhyWtz6XmQDko9HyDe0w=

Результат JS также был аналогичным, что подтвердило, что оноиспользование преобразования байтов ASCII -

keyBytes: 7bd512528df9b6daf71ff653504dc386e65c007de8d2877391c18cefe4e1b70d654a2eb12ee944fc53a9d30df93d76a7
key: 7bd512528df9b6daf71ff653504dc386e65c007de8d2877391c18cefe4e1b70d
iv: 654a2eb12ee944fc53a9d30df93d76a7  

Таким образом, мне просто нужно увеличить длину обычного текста, чтобы он использовал генерирование байтов, эквивалентное Юникоду (извините, не знаком с термином).Так как Unicode назначал 2 пробела для каждого символа в byteArray, оставляя второй пробел равным 0, я в основном создал пробел в символах plainText и заполнил этот пробел символом, значение ASCII которого было 0, используя функцию addExtraByteToChars().И это имело все значение.

Это обходной путь, но он начал работать для моего сценария.Я предполагаю, что это может или не может оказаться полезным для других, таким образом, разделяя выводы.Если кто-то может предложить лучшую реализацию функции addExtraByteToChars() (возможно, какой-то термин для этого преобразования вместо ASCII в Unicode или лучший, эффективный и не хакерский способ сделать это), предложите это.

0 голосов
/ 14 декабря 2018

Вот пример того, как воспроизвести один и тот же шифротекст между C# и CryptoJS:

static void Main(string[] args)
{
    byte[] plainText = Encoding.Unicode.GetBytes("Example String"); // this is UTF-16 LE
    string cipherText;
    using (Aes encryptor = Aes.Create())
    {
        var pdb = new Rfc2898DeriveBytes("Example Key", Encoding.ASCII.GetBytes("Ivan Medvedev"));
        encryptor.Key = pdb.GetBytes(32);
        encryptor.IV = pdb.GetBytes(16);
        using (MemoryStream ms = new MemoryStream())
        {
            using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateEncryptor(), CryptoStreamMode.Write))
            {
                cs.Write(plainText, 0, plainText.Length);
                cs.Close();
            }
            cipherText = Convert.ToBase64String(ms.ToArray());
        }
    }

    Console.WriteLine(cipherText);
}

И JS:

var keyBytes = CryptoJS.PBKDF2('Example Key', 'Ivan Medvedev', { keySize: 48 / 4, iterations: 1000 });
// take first 32 bytes as key (like in C# code)
var key = new CryptoJS.lib.WordArray.init(keyBytes.words, 32);
// skip first 32 bytes and take next 16 bytes as IV
var iv = new CryptoJS.lib.WordArray.init(keyBytes.words.splice(32 / 4), 16);
// use the same encoding as in C# code, to convert string into bytes
var data = CryptoJS.enc.Utf16LE.parse("Example String");
var encrypted = CryptoJS.AES.encrypt(data, key, { iv: iv });
console.log(encrypted.toString());

Оба кода возвращают: eAQO+odxOdGlNRB81SHR2XzJhyWtz6XmQDko9HyDe0w=

...