Как сгенерировать идентификатор ресурса с контрольной суммой
Автор выбрал программу Write for DOnations.
Введение
Уникальные идентификаторы (UID) или идентификаторы могут быть строковым значением или целым числом, и разработчики API часто используют их для обращения к уникальным ресурсам в API. Затем потребители API используют эти идентификаторы для получения одного ресурса из набора ресурсов. Без уникального идентификатора разделить ресурсы и вызывать их по мере необходимости практически невозможно. Идентификаторы могут ссылаться на структурные элементы базы данных, такие как имена таблиц, поля (столбцы) в таблице или ограничения, и могут быть дополнительно указаны для уникального элемента в базе данных. Например, в базе данных, связанной с порталом бронирования отелей, Hotel(id)
может указывать на идентификатор, относящийся к уникальному отелю. С помощью Hotel(id=1234, name=Hyatt)
вы сможете идентифицировать конкретный отель по идентификатору 1234
или по названию Hyatt
.
В Шаблонах проектирования API Джон Дж. Гивакс выделяет семь основных характеристик хорошего идентификатора. Эти характеристики важно учитывать при создании уникального идентификатора:
- Простота в использовании: в идентификаторе следует избегать зарезервированных символов, таких как косая черта (
/
), поскольку эти символы имеют определенное значение в URL-адресах. - Уникальный: идентификатор должен ссылаться на один ресурс в API.
- Быстрое создание: процесс создания идентификатора должен выполняться предсказуемым образом для согласованности при масштабировании.
- Непредсказуемость: непредсказуемость идентификатора обеспечивает преимущества безопасности при управлении уязвимостями.
- Читаемый: идентификатор должен быть удобочитаемым для человека, что достигается за счет отсутствия цифры
1
, нижнего регистраL
, верхнего регистраI
или символ вертикальной черты (|
), так как эти символы могут создать путаницу, если кому-то потребуется проверить идентификатор вручную. - Поддающийся проверке: символ контрольной суммы может использоваться для проверки идентификатора во время проверки целостности.
- Постоянные: после назначения идентификаторы не должны изменяться.
Примечание. Изменение идентификатора может привести к неожиданной путанице. Если у вас есть идентификатор, который указывает Hotel(id=1234, name=Hyatt)
и позже меняется на Hotel(id=5678, name=Hyatt)
, предыдущий идентификатор может быть доступен для повторного использования. Если предыдущий идентификатор доступен и новый отель создается как Hotel(id=1234, name=Grand Villa)
, этот новый отель повторно использует исходный идентификатор (1234
). Затем, когда вы запрашиваете отель 1234
, вы можете получить результаты, отличные от ожидаемых.
В этом руководстве вы создадите уникальный настраиваемый идентификатор ресурса, отвечающий этим характеристикам, и соответствующую контрольную сумму, используя Node.JS. Хэш-функция цифрового объекта. Контрольная сумма этого руководства будет представлять собой один буквенно-цифровой символ, полученный в результате алгоритмического процесса кодирования (или хеширования) размера байтов, соответствующего вашему ресурсу.
Предпосылки
Прежде чем приступить к этому руководству, вам потребуется следующее:
- На вашем компьютере установлен Node.js, который можно настроить, следуя инструкциям по установке Node.js. Это руководство было протестировано с Node.JS версии 16.16.0.
- Знакомство с Node.js. Узнайте больше в серии статей Как программировать в Node.js.
- Знакомство с API. Подробное руководство по работе с API можно найти в разделе Как использовать веб-API в Python3. Хотя статья написана для Python, она поможет вам понять основные принципы работы с API.
- Текстовый редактор, поддерживающий подсветку синтаксиса JavaScript, например Sublime Text. В этом руководстве используется редактор командной строки
nano
.
Шаг 1 — Создание закодированного идентификатора
На этом шаге вы напишете функцию для генерации идентификатора из случайных байтов в уникальную буквенно-цифровую строку. Ваш идентификатор будет закодирован с использованием кодировки base32, но к нему не будет привязана контрольная сумма до тех пор, пока далее в руководстве. Процесс кодирования создаст уникальный идентификатор указанной длины на основе выбранного вами количества байтов, создав идентификатор, который включает в себя некоторые характеристики хорошего идентификатора.
Начните с создания новой папки для этого проекта, затем перейдите в эту папку:
- mkdir checksum
- cd checksum
В этом руководстве папка проекта будет называться checksum
.
Создайте и откройте файл package.json
в папке вашего проекта (используя ваш любимый редактор):
- nano package.json
Затем добавьте следующие строки кода:
{
"name": "checksum",
"version": "1.0.0",
"main": "index.js",
"type": "module"
}
В этом файле вы определяете имя проекта как контрольная сумма
и считаете версию кода 1.0.0
. Вы определяете основной файл JavaScript как index.js
. Если у вас есть type: module
в файле package.json
, ваш исходный код должен использовать Как работать с JSON в JavaScript.
Сохраните и закройте файл.
Вы будете использовать несколько модулей Node.js для создания идентификатора: base32-decode
. Модуль crypto
входит в состав Node.JS, но вам потребуется установить base32-encode
и base32-decode
, чтобы использовать их позже в этом руководстве. . Кодирование — это помещение последовательности символов (буквы, цифры, знаки препинания и определенные символы) в специальный формат для эффективной передачи или хранения. Декодирование — это обратный процесс: преобразование закодированного формата обратно в исходную последовательность символов. Кодировка Base32 использует набор из 32 символов, что делает ее текстовой 32-символьной нотацией для выражения чисел.
В терминальном сеансе установите эти пакеты модулей в папку проекта с помощью следующей команды:
- npm i base32-encode base32-decode
Вы получите вывод, указывающий, что эти модули были добавлены:
Outputadded 3 packages, and audited 5 packages in 2s
found 0 vulnerabilities
Если у вас возникнут проблемы во время установки, вы можете обратиться за помощью к разделу «Как использовать модули Node.js с npm и package.json».
Находясь в папке проекта, создайте новый файл с именем index.js
:
- nano index.js
Добавьте следующие строки кода JavaScript в файл index.js
:
import crypto from 'crypto';
import base32Encode from 'base32-encode';
import base32Decode from 'base32-decode';
function generate_Id(byte_size) {
const bytes = crypto.randomBytes(byte_size);
return base32Encode(bytes, 'Crockford');
}
console.log('ID for byte size = 1:',generate_Id(1), '\n');
console.log('ID for byte size = 12:',generate_Id(12), '\n');
console.log('ID for byte size = 123:',generate_Id(123), '\n');
Команда import
загружает необходимые модули. Чтобы сгенерировать байты из числа, вы определяете функцию generate_Id
, чтобы получить размер байтов в байтах, а затем создать случайные байты этого размера с помощью функции randomBytes
из крипто
модуль. Затем функция generate_Id
кодирует эти байты, используя реализацию Crockford для кодирования base32.
В учебных целях создается несколько идентификаторов, которые затем регистрируются в консоли. Модуль base32-decode
будет использоваться для декодирования идентификатора ресурса на следующих шагах.
Сохраните файл index.js
, затем запустите код в сеансе терминала с помощью этой команды:
node index.js
Вы получите выходной ответ, подобный этому:
OutputID for byte size = 1: Y8
ID for byte size = 12: JTGSEMQH2YZFD3H35HJ0
ID for byte size = 123: QW2E2KJKM8QZ7174DDB1Q3JMEKV7328EE8T79V1KG0TEAE67DEGG1XS4AR57FPCYTS24J0ZRR3E6TKM28AM8FYZ2AZTZ55C9VVQTABE0R7QRH7QBY7V3GBYBNN5D9JK0QMD9NXSWZN95S0772DHN43Q003G0QNTPA2J3AFA3P7Q167C1VNR92Z85PCDXCMEY0M7WA
Значения вашего идентификатора могут отличаться из-за случайности сгенерированных байтов. Сгенерированный идентификатор может быть короче или длиннее, в зависимости от выбранного вами размера байта.
Вернувшись в index.js
, закомментируйте выходные данные консоли, используя функцию комментирования JavaScript (добавив двойную косую черту //
перед строкой):
...
//console.log('ID for byte size = 1:',generate_Id(1), '\n');
//console.log('ID for byte size = 12:',generate_Id(12), '\n');
//console.log('ID for byte size = 123:',generate_Id(123), '\n');
Эти строки демонстрируют, как кодирование будет выводить разные идентификаторы на основе связанных байтов. Поскольку эти строки не будут использоваться в следующих разделах, их можно закомментировать, как показано в этом блоке кода, или полностью удалить.
На этом шаге вы создали закодированный идентификатор, кодируя случайные байты. На следующем шаге вы объедините закодированные байты и контрольную сумму, создав уникальный идентификатор.
Шаг 2 — Генерация идентификатора ресурса
Теперь вы создадите идентификатор с символом контрольной суммы. Генерация символа контрольной суммы представляет собой двухэтапный процесс. В учебных целях каждая функция, создающая составную функцию, будет построена отдельно в следующих подразделах. Сначала вы напишете функцию, которая выполняет операцию по модулю. Затем вы напишете еще одну функцию, которая сопоставляет результаты с символом контрольной суммы, и таким образом вы сгенерируете контрольную сумму для своего идентификатора ресурса. Наконец, вы проверите идентификатор и контрольную сумму, чтобы убедиться, что идентификатор ресурса точен.
Запуск операции по модулю
В этом разделе вы преобразуете байты, соответствующие идентификатору числа, в число от 0 до 36 (включительно, что означает любое число от 0
до 36
, включая 0
и 36
). Байты, соответствующие идентификатору числа, преобразуются в целое число в результате значений BigInteger (BigInt
).
Чтобы реализовать эту процедуру, добавьте следующие строки кода в конец файла index.js
:
...
function calculate_checksum(bytes) {
const intValue = BigInt(`0x${bytes.toString('hex')}`);
return Number(intValue % BigInt(37));
}
Функция calculate_checksum
работает с байтами, определенными ранее в файле. Эта функция преобразует байты в шестнадцатеричные значения, которые далее преобразуются в значения BigInteger BigInt
. Тип данных BigInt
представляет числа, большие, чем числа, представленные примитивным типом данных number
в Javascript
. Например, хотя целое число 37
относительно мало, оно преобразуется в BigInt
для операции по модулю.
Чтобы добиться этого преобразования, вы сначала устанавливаете переменную intValue
с методом преобразования BigInt
, используя метод toString
для установки bytes
в hex
. Затем вы возвращаете числовое значение с помощью конструктора Number
, в котором вы запускаете операцию по модулю с символом %
, чтобы найти остаток между intValue
и BigInt
, используя примерное значение 37
. Это целочисленное значение (в данном примере 37
) действует как индекс для выбора буквенно-цифрового символа из пользовательской строки буквенно-цифровых символов.
Если значение intValue
равно 123
(в зависимости от байтов
), операция модуля будет 123 % 37
. Результатом этой операции с 37
в качестве целочисленного значения будет остаток от 12
и частное от 3
. При значении 154
для идентификатора ресурса операция 154 % 37
приведет к остатку 6
.
Эта функция сопоставляет входящие байты с результатом по модулю. Далее вы напишете функцию для сопоставления результата по модулю с символом контрольной суммы.
Получение символа контрольной суммы
После получения результата по модулю в предыдущем разделе вы можете сопоставить его с символом контрольной суммы.
Добавьте следующие строки кода в файл index.js
чуть ниже предыдущего кода:
...
function get_checksum_character(checksumValue) {
const alphabet = '0123456789ABCDEFG' +
'HJKMNPQRSTVWXYZ*~$=U';
return alphabet[Math.abs(checksumValue)]; //
}
Для функции get_checksum_character
вы вызываете checksumValue
в качестве параметра. В этой функции вы определяете строковую константу с именем alphabet
как пользовательскую строку буквенно-цифровых символов. В зависимости от значения, установленного для checksumValue
, эта функция вернет значение, которое связывает определенную строку из константы alphabet
с абсолютным значением checksumValue
.
Далее вы напишете функцию, которая использует две функции, описанные в этих разделах, для создания идентификатора из кодировки байтов в сочетании с символом контрольной суммы.
Добавьте следующие строки кода в файл index.js
:
...
function generate_Id_with_checksum(bytes_size) {
const bytes = crypto.randomBytes(bytes_size);
const checksum = calculate_checksum(bytes);
const checksumChar = get_checksum_character(checksum);
console.log("checksum character: ", checksumChar);
const encoded = base32Encode(bytes, 'Crockford');
return encoded + checksumChar;
}
const Hotel_resource_id =generate_Id_with_checksum(132)
console.log("Hotel resource id: ",Hotel_resource_id)
Этот раздел кода объединяет две ваши предыдущие функции, calculate_checksum
и get_checksum_character
(которые используются для генерации символов контрольной суммы), с функцией кодирования в новую функцию с метким названием generate_Id_with_checksum
, который создаст идентификатор с символом контрольной суммы.
Сохраните файл, затем запустите код в отдельном сеансе терминала:
- node index.js
Вы получите вывод, подобный этому:
Output
checksum character: B
Hotel resource id: 9V99B9P55K7M4DN5XYP4VTJYJGENZKJ0F9Q6EEEZ07X49G0V14AXJS3RYXBT3J1WJZXWGM76C6H7G895TJT27AW77BHBX2D16QNQ2ZNBY9MQHWG9NJ1WWVTNRCKRBX6HC3M7BB3JG0V413VJ767JN6FT0GFS5VQJ9X7KSP1KM29B02NAGXN3FP30WA8Y63N1XJAMGDPEE1RNHRTWH6P0B
Тот же символ контрольной суммы появляется в конце идентификатора, указывая на совпадение контрольных сумм.
Эта схематическая диаграмма обеспечивает структурное представление того, как работает эта составная функция:
Эта блок-схема демонстрирует, как идентификатор продукта, который является идентификатором, созданным счетчиком вручную для ресурса, преобразуется в уникальный идентификатор ресурса посредством процесса кодирования и модуля. crypto method
на диаграмме относится к функции crypto.randomBytes()
.
Вы создали идентификатор на основе размера байта, который будет включать символ контрольной суммы. В следующем разделе вы реализуете функцию identifier
для проверки целостности идентификатора с помощью декодирования base32
.
Проверка целостности идентификатора
Для обеспечения целостности теперь вы будете сравнивать символ контрольной суммы (последний символ идентификатора) с сгенерированной контрольной суммой, используя новую функцию под названием verify_Id
. Сравнение символа контрольной суммы является важным шагом для проверки целостности исходного идентификатора и подтверждения того, что он не был подделан.
Добавьте эти строки в файл index.js
:
...
function verify_Id(identifier) {
const value = identifier.substring( 0, identifier.length-1);
const checksum_char = identifier[identifier.length-1];
const buffer = Buffer.from( base32Decode(value, 'Crockford'));
const calculated_checksum_char = get_checksum_character(calculate_checksum(buffer));
console.log(calculated_checksum_char);
const flag =calculated_checksum_char== checksum_char;
return (flag);
}
console.log('\n');
console.log("computing checksum")
const flag = verify_Id(Hotel_resource_id);
if (flag) console.log("Checksums matched.");
else console.log("Checksums did not match.");
Функция verify_Id
проверяет целостность идентификатора, проверяя контрольную сумму. Остальные символы идентификатора декодируются в буфер, а затем calculate_checksum
и get_checksum_character
последовательно запускаются в этом буфере, чтобы извлечь символ контрольной суммы для сравнение (с calculated_checksum_char== checksum_char
).
Эта схематическая диаграмма демонстрирует, как работает составная функция:
На этой диаграмме под нарезкой понимается отделение значения идентификатора (value
) от символа контрольной суммы (checksum
). В предыдущем блоке кода функция identifier.substring( 0, идентификатор.length-1)
получает значение идентификатора, тогда как identifier[identifier.length-1]
берет последний символ из идентификатора ресурса.
Теперь ваш файл index.js
должен соответствовать следующему коду:
import crypto from 'crypto'; // for generating bytes from the number
import base32Encode from 'base32-encode'; // for encoding the bytes into Unique ID as string type
import base32Decode from 'base32-decode';// for decoding the ID into bytes
function generate_Id(byte_size) {
const bytes = crypto.randomBytes(byte_size);
return base32Encode(bytes, 'Crockford');
}
//console.log('ID for byte size = 1:',generate_Id(1), '\n');
//console.log('ID for byte size = 12:',generate_Id(12), '\n');
//console.log('ID for byte size = 123:',generate_Id(123), '\n');
function calculate_checksum(bytes) {
const intValue = BigInt(`0x${bytes.toString('hex')}`);
return Number(intValue % BigInt(37));
}
function get_checksum_character(checksumValue) {
const alphabet = '0123456789ABCDEFG' +
'HJKMNPQRSTVWXYZ*~$=U'; // custom-built string consisting of alphanumeric character
return alphabet[Math.abs(checksumValue)]; // picking out an alphanumeric character
}
function generate_Id_with_checksum(bytes_size) {
const bytes = crypto.randomBytes(bytes_size);
const checksum = calculate_checksum(bytes);
const checksumChar = get_checksum_character(checksum);
console.log("checksum character: ", checksumChar);
const encoded = base32Encode(bytes, 'Crockford');
return encoded + checksumChar;
}
const Hotel_resource_id =generate_Id_with_checksum(132)
console.log("Hotel resource id: ",Hotel_resource_id)
function verify_Id(identifier) {
const value = identifier.substring( 0, identifier.length-1);
const checksum_char = identifier[identifier.length-1];
//console.log(value,checksum_char);
const buffer = Buffer.from( base32Decode(value, 'Crockford'));
const calculated_checksum_char = get_checksum_character(calculate_checksum(buffer));
console.log(calculated_checksum_char);
const flag =calculated_checksum_char== checksum_char;
return (flag);
}
console.log('\n');
console.log("computing checksum")
const flag = verify_Id(Hotel_resource_id);
if (flag) console.log("Checksums matched.");
else console.log("Checksums did not match.");
Теперь вы можете запустить этот код:
node index.js
Вы получите следующий вывод:
Output...
computing checksum
AW75SY7FVC7TKT7VP5ZF0M8C67CN36YZK27BXHVFHSDXJFKH54HK2AXQFMPN89Q5YQRPGNHGAYQ5JFKVD40EKTXCET97Q0FEPX6MX1ZTNWGCA08SBRSHP8B0037ACJG6F6472FEVARCAWM6P5MRJ2F6WTRPXHYS9N1JEDZVH41D33RA5365VNFC5G5VYEFPFJJD8151B28XXDBRHAF80 H
H
Checksums matched.
Теперь у вас есть функция verify_Id
, которая проверяет целостность вашего идентификатора с помощью символа контрольной суммы. Далее, в учебных целях, вы можете изменить идентификатор ресурса, чтобы функция давала несоответствующий результат для оценки того, что происходит, когда проверка не удалась.
(Необязательно) Шаг 3 — Изменение идентификатора для несовпадающего результата
Теперь вы измените значение идентификатора, чтобы проверить, совпадут ли контрольные суммы. Изменение на этом шаге всегда будет приводить к несовпадению контрольной суммы, так как целостность не сохраняется при изменении любого символа в идентификаторе. Подобное изменение может быть результатом ошибок передачи или злонамеренного поведения. Это изменение предназначено для учебных целей и не рекомендуется для производственных сборок, но позволит вам оценить несоответствующий результат контрольной суммы.
В файле index.js
измените Hotel_resource_id
, добавив выделенные строки:
...
const altered_Hotel_resource_id= Hotel_resource_id.replace('P','H');
console.log("computing checksum")
const flag = verify_Id(altered_Hotel_resource_id);
if (flag) console.log("Checksum matched.");
else console.log("Checksums did not match.");
В приведенном выше коде вы заменяете любой P
на H
в идентификаторе и переименовываете переменную с Hotel_resource_ID
на altered_Hotel_resource_id
. . Опять же, эти изменения предназначены для информационных целей и могут быть отменены в конце этого шага для обеспечения целостности соответствия.
Сохраните файл, затем повторно запустите код с изменениями для идентификатора ресурса:
- node index.js
Вы получите вывод, что контрольные суммы не совпадают:
OutputChecksums did not match.
На этом шаге вы создали функцию для проверки того, проходит ли контрольная сумма проверку на целостность или нет, и столкнулись с обоими случаями. Несовпадающая контрольная сумма указывает на то, что идентификатор ресурса был изменен. Уведомление позволяет разработчику принять меры против злонамеренного поведения, например заблокировать запрос пользователя или сообщить о запросе, связанном с идентификатором ресурса, в зависимости от требований приложения.
Чтобы вернуть функцию к результату совпадающих контрольных сумм, удалите дополнительный код, добавленный в начале этого шага, чтобы код соответствовал файлу в конце шага 2.
Если вам нужен собственный уникальный идентификатор с контрольной суммой, вы можете использовать это руководство, чтобы помочь вам создавать модели данных, версии ваших API и многое другое.
Заключение
В этом руководстве вы разработали идентификаторы ресурсов, соответствующие характеристикам хорошего идентификатора. Вы также создали уникальный идентификатор ресурса с контрольной суммой в среде Node.js, используя кодировку base32
. Наконец, вы проверили целостность идентификатора, расшифровав его с помощью base32-decoding
.
Для взаимного подтверждения вы можете сравнить свои окончательные файлы с файлами из серии «Введение в GitHub» и «Проекты с открытым исходным кодом».
Теперь, когда вы понимаете основы контрольной суммы, вы можете поэкспериментировать с другими алгоритмами кодирования, такими как MD5.