Как кодировать и зашифровать простые данные
В проекте, над которым я работал некоторое время назад, я столкнулся с проблемой, которая не была уникальной. Это включало то, как мы могли безопасно передавать идентифицируемую информацию о пользователе, такую как номера телефонов, закодированные и зашифрованные сторонней службой в 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
- используемый базовый алгоритм, основанный на OpenSSLkey
- необработанный ключ, используемый алгоритмом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.
Сначала установите все необходимые зависимости разработчика:
- eslint - библиотека линтинга ESLint
- @ typescript-eslint / parser - парсер TypeScript для ESLint
- @ typescript-eslint / eslint-plugin - плагин со специфическими для TypeScript правилами ESLint.
// 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 ниже, если это вам поможет:
Дальнейшее чтение
- Симметричное шифрование ключей - зачем, где и как оно используется в банковской сфере
- Различия между хеш-функциями, симметричными и асимметричными алгоритмами
- Крипто - веб-интерфейс API
- TypeScript ESLint
- Использование ESLint и Prettier в проекте TypeScript
- Бинарные строки
- Рубин пак и распаковать
- Высокоскоростная криптография с открытым ключом в JavaScript