Как кодировать и зашифровать простые данные

В проекте, над которым я работал некоторое время назад, я столкнулся с проблемой, которая не была уникальной. Это включало то, как мы могли безопасно передавать идентифицируемую информацию о пользователе, такую ​​как номера телефонов, закодированные и зашифрованные сторонней службой в Ruby, для расшифровки и декодирования в наше приложение для пользователя.

//Link with encrypted token
https://example.com?token=XXXXX

Ruby обеспечивает простую обработку для знаковых и беззнаковых обратных байтов с использованием методов pack() и unpack() для доступа к базовым битам и байтам. Однако у Javascript есть проблема неправильной обработки больших целых чисел, поскольку он может хранить только целые числа со знаком или без знака длиной до 53 бит. См. Number.MAX_SAFE_INTEGER.

В этом случае мы оба использовали один и тот же ключ шифрования, процесс, известный как симметричное шифрование. Это означает, что для дешифрования данных должен использоваться один и тот же ключ шифрования.

В этом проекте и некоторых приложениях, где мы хотим кодировать и зашифровать конфиденциальные данные, нам также необходимо иметь возможность декодировать и расшифровывать эту информацию, когда она нам нужна, в любом месте нашего приложения, как таковое, чтобы расшифрованное значение не изменялось во время процесс обеспечения целостности данных. По сути:

const value = 0123456789 // Initial value
const token = encrypt(value) // Encrypted token
const decryptedValue = decrypt(token) // Decrypted value
value === decryptedValue // Must be TRUE

Простое использование кодировки Base64, которая запутывает и создает короткие токены, не является надежным решением безопасности, поскольку Base64 строки могут быть легко декодированы. В этой статье я продемонстрирую, как вы можете создать простое, безопасное и быстрое решение симметричного шифрования для шифрования данных и применить его в любом приложении даже в виде коротких, оптимизированных для SEO параметров URL с помощью следующих инструментов:

  • Typescript - добавляет статические типизированные определения в JavaScript.
  • Симметричное шифрование - метод криптографического шифрования, который использует одни и те же ключи шифрования как для шифрования, так и для дешифрования данных.
  • Crypto - встроенный модуль Node.js, обеспечивающий криптографическую функциональность.
  • Buffer - подкласс JavaScript класса Uint8Array, используемый для кодирования и декодирования символов.
  • Тестирование с использованием Мокко и Чай.
  • Автоматизированное тестирование, управление версиями и публикация пакетов с использованием GitHub, CircleCI и Semantic release

Разработка

Вам также необходимо иметь глобальную установку npm и / или yarn. Посмотрите, как установить YARN и NPM. Я также предпочитаю использовать NVM для управления своими версиями узлов.

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

$ mkdir encrypt-decrypt-library
$ cd encrypt-decrypt-library

Инициализируйте package.json для проекта:

$ yarn init -y

Затем нам нужно установить Typescript в качестве зависимости разработчика. Это потому, что мы будем компилировать наш код Typescript в JavaScript для нашей производственной сборки.

$ yarn add -D typescript

Затем нам нужно установить ts-node, чтобы мы могли выполнять файлы Typescript без необходимости их компиляции, а также @types/node.

// TypeScript execution for node
$ yarn add -D ts-node @types/node

Затем нам нужно добавить файл tsconfig.json в корень нашего проекта для нашей конфигурации Typescript. Мы инициализируем файл tsconfig.json следующим образом:

$ yarn tsc --init --rootDir src --outDir dist

Это сгенерирует начальный tsconfig.json файл в корне проекта с возможными параметрами конфигурации, с закомментированными несущественными параметрами. Я обновил этот файл следующим образом:

Далее нам нужно установить дополнительные пакеты для нашей библиотеки. Обратите внимание, что модуль crypto NPM устарел, поскольку теперь он является встроенным модулем узла. Этот модуль предоставляет нам криптографические функции, такие как функции хеширования, шифрования, дешифрования, подписи, проверки и HMAC OpenSSL.

Мы будем использовать класс Buffer для записи и чтения беззнаковых 64-битных целых чисел, потому что JavaScript имеет максимальное безопасное целое число, равное 53 битам, и наша библиотека должна быть способна обрабатывать 64-битный прямой порядок байтов, особенно когда нам нужно зашифровать и расшифровывать большие целые числа, такие как номера телефонов.

Нам также необходимо установить dotenv для загрузки переменных окружения из .env файла в process.env.

$ yarn add -D dotenv

Затем нам нужно создать наш src каталог, а затем файл нашей библиотеки, как показано ниже:

$ mkdir src
$ touch src/encryption.ts

Нам также необходимо создать .env файл для хранения наших переменных среды и .env.example файл для размещения всех ключей .env переменных. Файл .env необходимо игнорировать в файле .gitignore, чтобы переменные среды не попали в систему управления версиями по соображениям безопасности.

$ touch .env .env.example .gitignore

Затем мы создаем наш класс шифрования, обновляя encryption.ts следующим образом:

В браузере Javascript предлагает две функции для декодирования и кодирования строк base64, btoa() и atob(), см. Base64. Поскольку наш код выполняется вне браузера и из-за проблем, связанных с кодированием и декодированием UTF-16-битной DOMString, мы будем кодировать и декодировать 64-битные большие целые числа в байты с прямым порядком байтов и наоборот, используя Буфер класс, включающий методы writeBigUInt64BE() и readBigUInt64BE():

// allocate Buffer instance
const buf = Buffer.allocUnsafe(8);
// Write value to Buffer instance at the specified offset as big-endian.
buf.writeBigUInt64BE(BigInt(value));
// Read an unsigned, big-endian 64-bit integer from the Buffer instance at the specified offset
// and returns as a string
buf.readBigUInt64BE(0).toString();

Затем нам также необходимо реализовать криптографические функции в наших функциях encrypt() и decrypt(). Я обновил класс шифрования следующим образом:

Для этого мы используем встроенную Node Crypto library и реализуем методы crypto.createCipheriv () и crypto.createDecipheriv () для создания и вернуть экземпляр Cipher и Decipher соответственно, используя следующие параметры:

  • algorithm - используемый базовый алгоритм, основанный на OpenSSL
  • key - необработанный ключ, используемый алгоритмом
  • iv - вектор инициализации

Для шифрования данных мы используем метод crypto.createCipheriv () для создания экземпляра Cipher, а затем методы cipher.update () и cipher.final () для создания зашифрованных данных. . Чтобы расшифровать данные, мы используем метод crypto.createDecipheriv () для создания экземпляра Decipher, а также decipher.update () и decipher.final () Методы получения незашифрованных данных. См. CreateCipherIV и CreateDecipherIV для получения дополнительной информации о crypto методах и параметрах.

Оба наших метода encrypt() и decrypt() используют алгоритмы симметричного шифрования, гарантируя, что тот же ключ шифрования, который используется при шифровании данных, должен использоваться при их расшифровке. Узнайте больше об алгоритмах симметричного шифрования. Далее нам нужно добавить несколько тестов в нашу библиотеку.

Тесты

Чтобы добавить тесты в нашу библиотеку, нам необходимо установить некоторые зависимости, а именно Mocha и Chai:

$ yarn add -D mocha @types/mocha chai @types/chai

Нам нужно добавить несколько тестов для наших encrypt и decrypt методов. Во-первых, нам нужно создать папку tests в корне нашего проекта. В этой папке будут находиться наши тестовые файлы. Затем мы создаем тестовый файл encryption.test.ts:

$ mkdir tests
$ touch tests/encryption.test.ts

Обновите файл encryption.test.ts следующим образом:

ALGORITHM, ENCRYPTION_KEY и SALT - это переменные среды, установленные в нашем файле .env.

Затем нам нужно обновить свойство scripts в нашем package.json с помощью нашей test команды:

// package.json
"scripts": {
  "test": "./node_modules/.bin/mocha \"tests/**/*.ts\" --require ts-node/register --require dotenv/config"
},

Мы можем определять сценарии узлов для запуска в свойстве scripts нашего package.json файла.

Мы запускаем тест, используя аргументы Node CLI - Mocha требует ts-node, чтобы мы могли выполнить тестовый файл Typescript без необходимости их компилировать. Регистрируем загрузчик ts-node с помощью ts-node/register. Также указываем путь к тестовым файлам. Он запустит каждый тестовый файл в папке tests, соответствующий шаблону. Также требуется dotenv/config, который позволяет нам использовать переменные, установленные в нашем .env файле.

Чтобы запустить тест, мы просто запускаем в командной строке yarn run test.

$ yarn run test
yarn run v1.22.4
$ ./node_modules/.bin/mocha "tests/**/*.ts" --require ts-node/register --require dotenv/config
Encryption
    Ensuring encrypting and decrypting of string and number
      ✓ should encrypt and decrypt the string correctly
      ✓ should encrypt and decrypt the number as a big integer correctly
      ✓ should encrypt and decrypt the number as a string correctly
    Ensure decrypting of Big Integer token into a valid number
      ✓ should decrypt the encoded slug to a Big Integer correctly
    Ensure decrypting of string token into a valid string or number
      ✓ should decrypt the encoded slug to a valid string correctly
    Ensure decrypting of an 8-byte Big integer token into a valid number
      ✓ should decrypt the encoded slug to a Big Integer correctly
    Ensure validation of encrypt or decrypt methods 
      ✓ should return an error message if encrypt has no value
      ✓ should return an error message if decrypt has no value
    Ensure validation of library with no configuration
      ✓ should fail to encrypt the string correctly
      ✓ should fail to decrypt the string correctly
10 passing (25ms)

Линтинг

Чтобы гарантировать, что у нас есть четкое и последовательное соглашение о кодировании в нашей библиотеке, важно добавить линтинг. Linting помогает разработке, обеспечивая соблюдение стиля кода, выявляя, сообщая и исправляя любые несоответствия и ошибки в нашем коде. Для нашей библиотеки шифрования я выбрал ESLint, потому что он поддерживает как JavaScript, так и TypeScript.

Сначала установите все необходимые зависимости разработчика:

// Install ESLint and plugins
$ yarn add -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin

Нам также необходимо установить Prettier, который очень хорошо работает с ESLint и отлично справляется с форматированием кода.

  • prettier - основная библиотека prettier
  • eslint-config-prettier - отключает правила ESLint, которые могут конфликтовать с prettier.
  • eslint-plugin-prettier - запускает Prettier как правило ESLint.
// Install Prettier and plugins
$ yarn add -D prettier eslint-config-prettier eslint-plugin-prettier
// Installs husky and lint
$ yarn add -D husky lint-staged

Мы также установили lint-staged, который обеспечивает выполнение линтинга для файлов до их фиксации. Это гарантирует, что все файлы, сохраненные в GIT, не будут содержать ошибок линтинга или форматирования. Husky, используемый в сочетании с lint-staged, позволяет нам запускать указанные команды линтинга при предварительной фиксации и помогает упростить git-хуки. Вы можете узнать больше о Git Hooks здесь.

Затем нам нужно настроить файл конфигурации для ESLint:

$ npx eslint --init

Это создает файл конфигурации .eslintrc.js. Я добавил некоторые изменения в наши плагины и правила:

Затем создайте файл .eslintignore, который сообщает ESLint, что каталоги или файлы игнорируются, и добавьте следующее:

node_modules
dist
tests
coverage
styleguide.config.js
src/**/*.test.tsx
src/**/*.md
src/serviceWorker.ts

Обновите наш package.json, добавив нашу команду lint к свойству scripts:

// package.json
"scripts": {
  ...
  "lint": "eslint 'src/**/*.{js,ts}' --quiet --fix"
},

Вышеупомянутый сценарий можно запустить из командной строки, используя npm run lint или yarn lint. Эта команда запустит ESLint для всех файлов .js и .ts. Он автоматически исправит любые ошибки ESLint, но любые другие ошибки будут распечатаны в командной строке.

Чтобы настроить lint-staged и husky, мы также добавляем в package.json следующую конфигурацию:

// package.json
"husky": {
  "hooks": {
    "pre-commit": "lint-staged"
  }
},
"lint-staged": {
  "src/**/*.{js,ts}": [
    "prettier --single-quote --write",
    "eslint --fix"
  ]
},

В приведенной выше конфигурации каждый раз, когда выполняется фиксация в git, lint-staged будет запускать Prettier и ESLint для любых поэтапных файлов с соответствующими расширениями. Он автоматически исправит любые ошибки, которые можно исправить, и добавит их в текущую фиксацию. Однако он не сработает, если есть ошибки линтинга, которые не могут быть исправлены автоматически. Эти ошибки необходимо будет исправить вручную перед повторной попыткой зафиксировать код.

Производство

Чтобы использовать нашу библиотеку шифрования в качестве модуля NPM, нам нужно экспортировать наш класс шифрования, а затем скомпилировать наши файлы Typescript в JavaScript.

Сначала мы создаем наш файл экспорта index.ts в нашей src папке следующим образом:

$ touch src/index.ts

и экспортируем наш класс шифрования следующим образом:

import Encryption from "./encryption";

export default Encryption;

Для производства мы добавляем сценарий build в свойство scripts нашего package.json:

// package.json
"scripts": {
  ...
  "build": "NODE_ENV=production tsc"
},

Свойства rootDir и outDir параметров компилятора в файле tsconfig.json определяют корневой каталог входных файлов и выходной каталог для скомпилированных файлов. Запуск yarn build скомпилирует файлы TypeScript в файлы JavaScript в папке dist.

Нам также необходимо установить зависимость разработчика rimraf, которая действует как команда rm -rf.

$ yarn add -D rimraf

Затем мы обновляем package.json, обновляя наш build скрипт и добавляя свойства main и module, чтобы установить точку входа для нашей библиотеки, которой является наш скомпилированный Javascript. file и свойство types, чтобы указать на наш файл записи объявления Typescript:

// package.json
"scripts": {
  ...
  "build": "rimraf ./dist && NODE_ENV=production tsc"
},
......
"main": "dist/index.js",
"module": "dist/index.js",
"types": "dist/index.d.ts"

Теперь, когда мы запустим yarn build, rimraf удалит нашу старую dist папку до того, как компилятор TypeScript выдаст новый код для dist.

Документация

Всегда важно включать надежную документацию, чтобы помочь нам отслеживать различные аспекты приложения. Это помогает улучшить качество кода и упрощает дальнейшую разработку, сопровождение и передачу знаний.

Затем нам нужно создать README.md файл в корне нашего проекта:

$ touch README.md

И обновите следующим образом:

Издательский

Прежде чем мы сможем опубликовать нашу библиотеку, мы хотим исключить определенные файлы из нашего окончательного пакета. Мы сделаем это, добавив свойство files в наш package.json, которое сообщает NPM, какие files и / или directories включить в наш пакет:

// package.json
"files": [ "dist" ],

Мы также создаем .npmignore файл в корне нашего проекта, который также указывает, какие каталоги или файлы NPM следует игнорировать:

// .npmignore
src
node_modules
tests
.idea
coverage

Затем мы публикуем наш пакет в NPM, запустив npm publish в командной строке. Вам необходимо иметь учетную запись в NPM и, возможно, потребуется запустить npm login, чтобы войти в свою учетную запись NPM перед публикацией. Аккаунты в NPM бесплатны. NPM также требует, чтобы имя вашего пакета было уникальным.

Автоматизированный рабочий процесс

Важно автоматизировать процесс выпуска, так как это экономит время и улучшает качество кода. Это позволяет нам автоматизировать различные шаги, описанные выше, такие как установка зависимостей, линтинг, тестирование, управление версиями и публикация нашей библиотеки в NPM.

Пожалуйста, прочтите мою статью Как автоматизировать непрерывную интеграцию и разработку, управление версиями и публикацию, чтобы получить пошаговое руководство по настройке CI / CD с помощью Github, CircleCI и Semantic Release для автоматизации рабочих процессов, включая автоматизация тестирования, линтинг, управление версиями, развертывание и публикация в NPM.

Значки статуса

Возможно, нам потребуется включить значки сборки в наш проект, чтобы предоставить общий доступ к состоянию сборки нашего пакета. Значки статуса могут быть встроены в верхнюю часть файла README проекта.

Мы добавим значки статуса CircleCI и Semantic Release в верхнюю часть файла README нашего проекта. См. CircleCI - Добавление значков статуса.

# CircleCI Template: 
[![CircleCI](https://circleci.com/<VCS>/<ORG_NAME>/<PROJECT_NAME>.svg?style=svg)](<LINK>)
# Semantic Release Template: 
[![semantic-release](https://badge.fury.io/js/<PROJECT_NAME>.svg?style=svg)](<NPM-PACKAGE-LINK>)
// CircleCI Build Status
[![CircleCI](https://circleci.com/gh/s-barrah/encrypt-decrypt-library.svg?style=svg)](https://app.circleci.com/pipelines/github/s-barrah/encrypt-decrypt-library)
// Semantic Release 
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
// NPM Status
[![semantic-release](https://badge.fury.io/js/encrypt-decrypt-library.svg)](https://www.npmjs.com/package/encrypt-decrypt-library)

использование

Чтобы использовать нашу библиотеку в любом проекте, установите с помощью YARN или NPM:

// Via Yarn
$ yarn add encrypt-decrypt-library
// Via NPM
$ npm install --save encrypt-decrypt-library

И мы можем импортировать и использовать в любом месте проекта:

import Encryption from "encrypt-decrypt-library";
const config = {
   algorithm: process.env.ALGORITHM,
   encryptionKey: process.env.ENCRYPTION_KEY,
   salt: process.env.SALT,
} 
const encryption = new Encryption(config);
// Encrypt a string
encryption.encrypt('Hello world')
// Encrypted as an unsigned 64-bit Integer
encryption.encrypt(123, true)
// Encrypted as string
encryption.decrypt('gmmBh17Q4QA=')

// Encrypted as an integer
encryption.decrypt('NF1r855MimY=', true)

Заключение

Есть разные причины, по которым нам может потребоваться шифрование и дешифрование данных. Мы можем захотеть передать конфиденциальную информацию как параметры URL-адреса и скрыть ее от пользователей. Кодировка Base64 может создавать короткие и удобные для SEO токены URL, а в Node библиотеки, такие как atob и btoa, могут имитировать функциональность браузеров, однако кодировка Base64 небезопасна. Вы можете узнать больше о JavaScript Base64.

Целью этой статьи было продемонстрировать, как создать простую и удобную в использовании библиотеку шифрования, которую можно импортировать и использовать в любом из наших проектов для кодирования и шифрования простых строк и чисел в короткие, безопасные и удобные для SEO параметры URL. .

Есть несколько библиотек NPM, которые обеспечивают шифрование и кодирование, например CryptoJS. Вы также можете изучить этот список криптографических библиотек JavaScript и решить, что подходит для вашего варианта использования.

Не стесняйтесь клонировать, разветвлять или включать мой репозиторий GitHub библиотеки Simple Encryption library ниже, если это вам поможет:



Дальнейшее чтение