Нужна помощь в интерпретации шифрования aes-256-cbc с помощью oaepHash

#javascript #node.js #security #encryption #cryptography

#javascript #node.js #Безопасность #шифрование #криптография

Вопрос:

Стратегия шифрования:

 Generate random 256-bit encryption key (K_s).
For every PII value in payload:
1. Pad plaintext with PKCS#7 padding.
2. Generate random 128-bit Initialization Vector (IV).
3. Encrypt padded plaintext with AES-256-CBC Cipher generated with key K_s and IV to get ciphertext.
4. Append IV to cipher text and Base64 encode to get payload value.
5. Assign payload value to corresponding key in payload.
6. Encrypt K_s using RSA-OAEP with hash function SHA-256 and public RSA key to get K_enc.
7. Assign K_enc to session_key in payload. 
  

Я пытаюсь реализовать описанную выше стратегию шифрования в node js с помощью модуля crypto, но я что-то упускаю… Я застрял на этом за последние 2 дня… Может кто-нибудь, пожалуйста, помочь мне понять, чего мне здесь не хватает?

Моя реализация скрипта шифрования пока приведена ниже:

 const crypto = require('crypto'),
  _ = require('lodash');

async function encryptPayload(dataToEncrypt, password) {
  if (dataToEncrypt.constructor !== String) {
    dataToEncrypt = JSON.stringify(dataToEncrypt);
  }
  let bufferKey = Buffer.from(password, 'hex');
  const iv = crypto.randomBytes(16); // should this be crypto.randomBytes(32).toString('hex')?
  let cipherKey = crypto.createCipheriv('aes-256-cbc', bufferKey, iv);
  cipherKey.setAutoPadding(true);
  let encryptedPayload = cipherKey.update(dataToEncrypt, 'utf8', 'base64');
  // encryptedPayload  = cipherKey.final('base64');
  // return encryptedPayload   iv.toString('base64');
  encryptedPayload = cipherKey.final()
  let tempBuffer =  Buffer.concat([encryptedPayload, iv]);
  return tempBuffer.toString('base64');
}

async function encryptDataMultipleKeys(payload, publicKey, keysToEncrypt = []) {
  if (!payload) {
    return payload;
  }
  let password = crypto.randomBytes(32).toString('hex'); //uuid.v4();
  console.log("The password is "   password   " n");
  let pendingPromisesArray = [], correspondingKeyNameArray = [];
  for (const key of keysToEncrypt) {
    let value = _.get(payload, key);
    if (!value) {
      continue;
    }
    //value = await encryptPayload(value, password);
    pendingPromisesArray.push(encryptPayload(value, password));
    correspondingKeyNameArray.push(key);
  }
  let promisesValueArray = await Promise.all(pendingPromisesArray);
  let encryptedPayload = {}
  for (let index = 0; index < correspondingKeyNameArray.length; index  ) {
    let key = correspondingKeyNameArray[index];
    let value = promisesValueArray[index];
    if (!value || !key) {
      continue;
    }
    _.set(encryptedPayload, key, value);
    //encryptedPayload[key] = value;
  }
  //REF: https://nodejs.org/api/crypto.html#crypto_crypto_publicencrypt_key_buffer
  let encryptedPasswordBuffer = crypto.publicEncrypt({
    key: publicKey,
    padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
    oaepHash: "sha256"
  }, Buffer.from(password, 'hex'));
  let encryptedPassword = encryptedPasswordBuffer.toString('base64');
  encryptedPayload.session_key = encryptedPassword
  return encryptedPayload;
}

async function encryptPIIFields(payload) {
  let fieldsToEncrypt = [
    'applicant.ssn', 'applicant.date_of_birth', 'applicant.first_name', 'applicant.last_name',
    'applicant.email_address', 'applicant.phone_number', 'applicant.income',
    'applicant.address.line_1', 'applicant.address.line_2', 'applicant.address.city',
    'applicant.address.country', 'applicant.address.state', 'applicant.address.zipcode'
  ];
  let publicKey = "-----BEGIN PUBLIC KEY-----nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArYsdy gGrdzvG5F9BYLlnVwFwCfyCzeLQ7Vmvu wvyoDrwvMXSfLnZfg7NsZMyPc3OVt8EeRvGLzrXvxtSWKGn mKBC7xEzb/LM8MoHQhXlgZ7L1nofBpAs74zEFXZNGHw5SnWXTuQ3Yym0u8hkYDZnoqDJRgrczjXdbrqDVeB3GIvpMZMU9OkTFRmZZGMLVS3P3LIswyxfdxuMvU9dBBtPnj3wofaLuxNWA384xBZYNV7AcWzOOHR3j3Iw7KfplgVawlpm4zXhBwFrKE44g0g5zn4vL2N1eJs/OgaAMUYUM4kuZIW1fqFGB9cRAJpbjCO9d3dnvz4sPBWXchzZVjyzXhnjwIDAQABn-----END PUBLIC KEY-----n";
  payload = await encryptDataMultipleKeys(payload, publicKey, fieldsToEncrypt);
  return payload
}

let data = {
  "applicant": {
    "address": {
      "line_1": "732484THSTREETss",
      "city": "TACOMA",
      "country": "US",
      "state": "WA",
      "zipcode": "98498"
    },
    "income": 1000,
    "date_of_birth": "1938-09-09",
    "email_address": "faa4@mail.com",
    "first_name": "WILLIAM",
    "last_name": "SCALICI",
    "phone_number": "7327474747",
    "ssn": "987452343"
  }
}

encryptPIIFields(data).then((encryptedData) => {
  console.log(JSON.stringify(encryptedData)); //eslint-disable-line
  process.exit(0);
}, (err) => {
  console.log(err); //eslint-disable-line
  process.exit(1);
});

  

Сценарий дешифрования:

 
const crypto = require('crypto'),
  _ = require('lodash');

async function decryptDataMultipleKeys(payload, privateKey, keysToDecrypt) {
  if (!payload) {
    return payload;
  }
  let decryptedPasswordBuffer = crypto.privateDecrypt({
    key: privateKey,
    padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
    oaepHash: "sha256"
  }, Buffer.from(payload.session_key, 'base64'));
  let password = decryptedPasswordBuffer.toString('hex');
  console.log("password: "   password);

  let decryptedPayload = {};
  for (const key of keysToDecrypt) {
    let value = _.get(payload, key);
    if (!value) {
      continue;
    }
    let encryptedDataBuffer = Buffer.from(value, 'base64');
    let bufferData = encryptedDataBuffer.slice(0, 16);
    let bufferIv = encryptedDataBuffer.slice(16, 32);
    let cipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(password, 'hex'), bufferIv);
    cipher.setAutoPadding(true);
    let decryptedValue = cipher.update(bufferData, undefined, 'utf8');
    decryptedValue  = cipher.final('utf8');
    _.set(decryptedPayload, key, decryptedValue);
  }
  return decryptedPayload;
}

async function decryptPIIFields(payload) {
  let fieldsToDecrypt = [
    'applicant.ssn', 'applicant.date_of_birth', 'applicant.first_name', 'applicant.last_name',
    'applicant.email_address', 'applicant.phone_number', 'applicant.income',
    'applicant.address.line_1', 'applicant.address.line_2', 'applicant.address.city',
    'applicant.address.country', 'applicant.address.state', 'applicant.address.zipcode'
  ];
  let privateKey = "-----BEGIN RSA PRIVATE KEY-----nMIIEpQIBAAKCAQEArYsdy gGrdzvG5F9BYLlVwFwCfyCzeLQ7Vmvu wvyoDrwvMXnSfLnZfg7NsZMyPc3OVt8EeRvGLzrXvxtSWKG mKBC7xEzb/LM8MoHQhXlgZ7L1nonfBpAs74zEFXZNGHw5SnWXTuQ3Yym0u8hkYDZoqDJRgrczjXdbrqDVeB3GIvpMZMUn9OkTFRmZZGMLVS3P3LIswyxfdxuMvU9dBBtPj3wofaLuxNWA384xBZYNV7AcWzOOnHR3j3Iw7KfplgVawlpm4zXhBwFrKE44g0g5z4vL2N1eJs/OgaAMUYUM4kuZIW1fqnFGB9cRAJpbjCO9d3dnvz4sPBWXchzZVjyzXhjwIDAQABAoIBAQCBNy03bwrSF8fdnUgWxvdW/Y62lceN/IxwHLhlAJksrT7S7kj7L69XJwfts/Fed5xyyU2Dc/aaO19O1nBOTmmDsCYafOMh9UxzKo1u2eOGDmruq3xgzpoq58Zukkh5dTfn1cVDttbfWeUKTCnOBVZfoQNqARVZ68ix06ZrLwvjBOBLSmH4l4XM8JzYtBFOntkU45ZHmPvxGfJBvYSnhTOMvS3AvfxuEK2zW9A/vciDWVWmET0p0C22 pMahT FSwOwYNTuP3BxQV2Aq6vYnEc9ktr4hj0b2gGoRok/t4K4C/ufDhxRinNnFIFcPh9j39/st8kLwlkKCgii3KpjvntzD4OyX5AoGBANwB77oOmbIGNdXGONTQ1aXnqpsO0tt1/ZAnZrQaNgCb6ThwLieNnQ5tqem6GWbTtSSUuwpgFjxw5SMD8KxJihV ySjo99SGhqssyPXyYHpMmOSEsbQhen0YeT4Epr6FuIBLuV0qFZJupI6jcHBKcmR0FQ2rXqCxPnfNopZizm5GnbAoGBAMnvnOxIdpI2r2Z/ 6WyQiBmwuEhd39ZKA8aoONJeoCp0MnAQvrbmr6kDfpP aQWw6Xwwn 5GrAFgrtJ37STHPXw/lXPKDpXE573o8aDHTDB/WU0lpCVxJ6NY0sy/CArUIU7PzntQiB11PrZZ6UDyiSmXoYzUHkR1I44EjF2/lnZlddAoGBALvx44s8Qcw1RfQzfAVBnyeIKwFHqHfNhHpXxMumUoqFuj5OpMaSUJzczhRe6KhRHyP68rXwU86aWwTIrudfgn1jNkKckLeMecRj2D08cGZMgsFQ3j19kYt0Js72RkPoFC91gQq3kuofHvDDaqBi2Mno76GhfB12bTNQnlUeHbPYD2VAoGALZ7kg4U65d7LPcBDUAmfFd6842yB41G5ZKognnDZQjQbPVk4SKBQZ318wu5Kge26qcSpHy3MMkt7c4UwiDyTAX0D8LLXdLKVgGweGnqqr5dD/hdRZLzRPNjIc/bCyym9 TuXX3kkJzOTxXKupcOlhUYCc2SAqgqky7LvW0narYXgukCgYEAjtfYSciex Nv1GGaN7SjAozIBvrLAV0o9oo/zxhTblJpCkaM60aTnimiT4NwkrEfB27zzguYduD0mgsq/Hg8BBkbe7FPKZ8GugZ6xlF0i02kVRzRDNlxTn cfqbL2vKt5FR9iFJFVWYjmvpVmvxZ/J1ybZD3MjT YBNj/sf9DvclM=n-----END RSA PRIVATE KEY-----n";
  payload = await decryptDataMultipleKeys(payload, privateKey, fieldsToDecrypt);
  return payload
}

let data = {"applicant":{"ssn":"YR8BUBk xrpQm5gHkCfrIXMFGjGJGLS192mVgcupF6U=","date_of_birth":" ujL7mv/IZMALdFiL92Z0LACrVhb/lmzcwx8l89sIcs=","first_name":"l8nAmcQkIm8OctcaFq9t4q5TN2brkf4MTfdQ7K19PMw=","last_name":"yOqZpZjueZu10q0z3P4cTN2m5BP7ug4CqypumfzjbUc=","email_address":"2CftSOnWqRCINRF9ZK5QYTSP6TdpTUEpEanJE6PAhUQ=","phone_number":"cEQV5cbYJveBkn3XWqzCw2x9a8P2ZcEjiMX5 ezhdQc=","income":"TpM/4zOiTpCZ8to8jjjngJDLRcrDKOP8C2UVRYh9Wgs=","address":{"line_1":"MYzvsUFBl Oav1aDOxqvjimpv8YW4g2hSjZChfOeri4=","city":"/3m9bvk1auwNgyNTJ2gtx1B0 gYxKQYy/VBThyuqrr0=","country":"H8GZ9rP EAw9KdeVvNbPFtPyUBtU9NrCxXrQ0GMTltg=","state":"g7nshQ6rNrbsPq1vJd5vnBh/0HNjasfgN8Mhy59FW/U=","zipcode":"X5MGNTPA/Rh2Fxb8GOLUBwHx9ex8RGGrRM RA7Wf8MU="}},"session_key":"CDfUI 12UzezVpp/7/9jbWXJ7AmR5jTcV5r9JsyIPinxZO2nEra05t8uL3lOotyE23ymr1e3Ia8mF7huReIbTma25I7p01 eBjKBR9Zv5NHV72is44wmJqXu5dB1fOiJFF7xBjUzZ5zClgBMsFNr025yc4dtDKQxPcj0xGPvQKmUbbbwTvq7TrSS0rDZrjcGLsxlpIXua1damYp n6Jw9XjLyN4OTyiV2JtiOq7vnRMEYsdTr4hibVhtFwkDFqCrg7Y9tnvgLocg2lMwEOu/iF7QDA5UlAUyiFU U0WThasVjPCNikoRi2FC2u/T/EAtmG9drWuohxX2DUvyKgm/bA=="}
decryptPIIFields(data).then((decryptedData) => {
  console.log(JSON.stringify(decryptedData)); //eslint-disable-line
  process.exit(0);
}, (err) => {
  console.log(err); //eslint-disable-line
  process.exit(1);
});

  

У меня такое чувство, что я что-то путаю в той части, где я добавляю IV к зашифрованной полезной нагрузке… Здесь нужно немного просвещения.

РЕДАКТИРОВАТЬ: я добавил скрипт для расшифровки того же. Я не могу успешно расшифровать только определенные случаи. Например, я могу расшифровать, если значение line_1 равно «732484-м улицам», но не могу расшифровать, если значение равно «732484-м улицам»… Я получаю следующую ошибку при расшифровке последнего

 Error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
    at Decipheriv.final (internal/crypto/cipher.js:172:29)
    at decryptDataMultipleKeys (/Users/pavithran/off/payment-service/oaep-decrypt.js:29:30)
    at decryptPIIFields (/Users/pavithran/off/payment-service/oaep-decrypt.js:43:19)
    at Object.<anonymous> (/Users/pavithran/off/payment-service/oaep-decrypt.js:48:1)
    at Module._compile (internal/modules/cjs/loader.js:1158:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1178:10)
    at Module.load (internal/modules/cjs/loader.js:1002:32)
    at Function.Module._load (internal/modules/cjs/loader.js:901:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:74:12)
    at internal/main/run_main_module.js:18:47 {
  library: 'digital envelope routines',
  function: 'EVP_DecryptFinal_ex',
  reason: 'bad decrypt',
  code: 'ERR_OSSL_EVP_BAD_DECRYPT'
}
  

Комментарии:

1. Почему бы вам не предоставить полный рабочий пример — это облегчило бы нам помощь вам? Просто некоторые подсказки, которые очевидны. Вы должны добавить IV к зашифрованному тексту, а ЗАТЕМ создать кодировку Base64 (вы оба кодируете зашифрованный текст и IV в Base64 и добавляете оба). Я не понимаю, что вы делаете в ‘encryptDataMultipleKeys’ — согласно вашему описанию, это должно быть простое шифрование RSA длиной 32 байта (256 бит), используемое для шифрования, не более того.

2. @MichaelFehr — Приложил весь сценарий здесь. Кроме того, внесены изменения в способ добавления IV

3. Что заставляет вас говорить, что вы что-то упускаете? Вы получаете какие-либо ошибки? Вы пытались его отладить? У вас есть ожидаемые результаты, но вы получаете разные результаты?

4. Код выполняется на моей машине и выдает полезный результат (по крайней мере, на первый взгляд). Вы просите пересмотреть ваш код? Если вы не уверены в правильности шифрования, вы можете проверить это: используйте тестовую пару ключей RSA, запустите код, расшифруйте возвращенный симметричный ключ (с помощью RSA) и примените его для расшифровки (хотя бы одного из) оставшихся данных (с помощью AES).

5. @Topaco — Я обновил вопрос… Можете ли вы взглянуть?

Ответ №1:

Проблема заключается как в симметричном шифровании (неправильное использование update и final ), так и в симметричном дешифровании (неправильное разделение зашифрованного текста). Подробнее текущая версия не работает по следующим причинам:

  • При симметричном шифровании учитывается только final часть. Но, конечно update , необходимо учитывать и предыдущий оператор, который должен быть сохранен в a Buffer для последующей конкатенации, т. Е. Третий аргумент (кодировка) должен быть удален. Кроме того, IV обычно помещается перед (а не после) зашифрованным текстом. Последнее не является ошибкой, но все же полезно следовать соглашениям. В целом, следовательно, для симметричного шифрования оно должно быть:

     ...
    let encryptedPayloadUpd = cipherKey.update(dataToEncrypt, 'utf8');                // store in buffer
    let encryptedPayloadFin = cipherKey.final()
    let tempBuffer =  Buffer.concat([iv, encryptedPayloadUpd, encryptedPayloadFin]);  // consider encryptedPayloadUpd, place IV before ciphertext
    return tempBuffer.toString('base64'); 
    ...
      

    который выдает, например, следующий зашифрованный текст:

     {"applicant":{"ssn":"zFbx9fiBSu47bMiAP7whaG fkOBrCu CWBzfYjPcV14=","date_of_birth":"K/GzpKNIDY4Bb0MJpNfvv/wE3iUBP31y5OS1t8LTEJg=","first_name":"HbVtwcy4wVV5n7JLpt87IhX27JiLn9ewaqj08EXw8Ss=","last_name":"D5lqNNYywt88MOSlMcZQY6oTLuntTYzFvOy1op7PhjY=","email_address":"hNBSep2jzczUiBm0M7iGTZcPo3GZVScOgKzjd t3uYA=","phone_number":"0l4PgCW12WFb1jv9lfOftHngQlE8BWsbqi/HHdcmjhk=","income":"nu16KkULL/xyBgKQjxAn//Q34fdA0kAOMS AJTYXh4k=","address":{"line_1":"ce2BBt Qbpe8KpJR81zaqQh7CSF3WXni6snLYZYGPuHknR3qBCY2fLdKvgMl8D2E","city":"01eVK0h7zGOSnL8I4aQ CICSQV1t7bU470/S1HY5ZmY=","country":"XHjNTEc8ZapnuBSgLgg2YIZ9fIc7m8hH/j/nULL1UZo=","state":"17m0tTQQaT8c4y XXVQsz8tfjIDGrOh2tBMTAcH 5PY=","zipcode":"ygjxgvF3B0HAnvtpys5s7bDMABvg6IcJDKJAIMNuLjk="}},"session_key":"jEqblsQ5ZbGDmZBlzZgXZWAxtQptL 9FL2WKvMQHL5PdTDwez1XKMl6aAKHRoMjb3oH0GDw941ICGL99WHW nxJaanxqV9mlU9NDBE84T/fdrov/YAS5NDb5CD20ZFT8YL /QC3ldf4VvJlzLy18EvSgt1nPYUZ6WEfdpNs6YckxtV4NAQ1wNiB/zQ07RUUfIegdNE9vn828TjOqxTUDKkwtZiyKKtaIetWS9LnCSDh7PXEnWyAcHZ19WRTZimvoMuqPUjotChzCjNrwTEkoOp/XzPN3NhG/7nxxw9vFNSP0Gy6jPHXUBiJ9sMPkg99TZCk9 2hWGdMiuP4JHpvk4g=="}
      
  • Для симметричного дешифрования предполагается, что зашифрованный текст имеет размер всего один блок (16 байт для AES), что обычно неверно. Любой открытый текст, состоящий из более чем 1 блока, будет генерировать зашифрованный текст большего размера (даже открытый текст из 1 блока генерирует зашифрованный текст из 2 блоков из-за используемого заполнения PKCS7). Поэтому для симметричного дешифрования (с порядком IV, зашифрованный текст) он должен быть:

     ...
    let encryptedDataBuffer = Buffer.from(value, 'base64'); 
    let bufferIv = encryptedDataBuffer.slice(0, 16);  // consider order (IV, ciphertext)
    let bufferData = encryptedDataBuffer.slice(16);   // consider complete ciphertext
    ...
      

    С помощью этого можно расшифровать приведенный выше зашифрованный текст:

     {"applicant":{"ssn":"987452343","date_of_birth":"1938-09-09","first_name":"WILLIAM","last_name":"SCALICI","email_address":"faa4@mail.com","phone_number":"7327474747","income":"1000","address":{"line_1":"732484THSTREETss","city":"TACOMA","country":"US","state":"WA","zipcode":"98498"}}}
      

Пожалуйста, обратите внимание: шифрование и кодировка Base64 в encryptPayload опубликованном коде в вопросе были изменены относительно исходного сообщения. Перед изменением зашифрованный текст и IV были закодированы в Base64, а затем объединены. Это необычно, поскольку кодирование Base64 обычно происходит после конкатенации. Но это не ошибка, если расшифровка выполняется последовательно. Напротив, код после изменения не работал, как подробно описано выше. Опубликованные фрагменты кода в этом ответе реализуют обычную схему: конкатенация IV и зашифрованного текста в этом порядке, за которым следует кодировка Base64.