如何生成带校验和的资源标识符

作者选择了 自由和开源基金作为 写给捐款计划的一部分接受捐款。

介绍

Unique identifiers(UIDs),或 identifiers,可以是一个字符串值或整数,API开发人员通常会用它们来在API中寻找独特资源。API消费者然后会使用这些标识符来从资源集合中获取单个资源。没有一个独特的标识符,将资源分开并按需要命名几乎是不可能的。标识符可以参考数据库的结构元素,例如表的名称、表中的字段(列)或限制,并且可以进一步指定数据库中的单一项目。

API Design Patterns中,John J. Geewax 确定了一个好的标识符的七个基本特征。

  • 易于使用:标识符应避免预留字符(/),因为这些字符在URL中具有特定含义。
  • 独特:标识符应该能够在API中引用单个资源。
  • 快速生成:标识符的生成过程应在缩放时以可预测的方式进行一致性。
  • 不可预测:当标识符不可预测时,它为漏洞管理提供了安全优势。
  • 可读性:标识符应该是人读的,这是通过避免数字 1、下面的L``、上面的I`或管道字符的实现,因为这些字符可能导致混淆,如果有人需要手动检查标识符。

<$>[注] ** 注:** 更改标识符可能会造成意外的混淆. 如果您有一个标识符指定为 酒店(id=1234, name="Hyatt"),然后更改为 酒店(id=5678, name="Hyatt"),那么以前的标识符可能可重复使用。 如果以前的标识符可用,并且新酒店被创建为 酒店(id=1234, name="Grand Villa"),这个新酒店会重复使用原始标识符(1234)。

在本教程中,您将使用 Node.JS 生成满足这些特征的独特自定义资源标识符和相关的检查总和。 A checksum是使用数字对象的 hash 函数获得的文件或数字数据的数字指纹的哈希。

前提条件

在您开始本教程之前,您将需要以下内容:

  • Node.js 已安装在您的计算机上,您可以通过以下 How To Install Node.js来设置。本教程已与 Node.JS 版本 16.16.0.
  • 熟悉 Node.js. 在 How To Code in Node.js 系列中了解更多。
  • 熟悉 APIs. 有关使用 APIs 的全面教程,您可以查看 [How to Use Web APIs in Python(3)https://andsky.com/tech/tutorials/how-to-use-web-apis-in-python-3])。 虽然为 Python 撰写,但这篇文章将帮助您了解与 APIs 工作的核心概念。
  • 一个文本编辑器支持 JavaScript 语法突出,如 Atom, [Visual Code Studio]([L

步骤 1 – 生成编码ID

在此步骤中,您将写一个函数,将从随机字节生成一个独特的字符串的标识符。您的标识符将使用 base32 编码进行编码,但它将不会在教程中稍后附加到检查总数。

首先,为此项目创建一个新文件夹,然后移动到该文件夹:

1mkdir checksum
2cd checksum

该项目文件夹将被称为这个教程的checksum

在项目文件夹中创建并打开package.json文件(使用您最喜欢的编辑器):

1nano package.json

然后添加以下代码行:

1[label package.json]
2{
3  "name": "checksum",
4  "version": "1.0.0",
5  "main": "index.js",
6  "type": "module"
7}

在此文件中,您将项目名称定义为checksum,并考虑代码版本为1.0.0。您将主JavaScript文件定义为index.js。当您在package.json文件中有type:module,您的源代码应该使用 import syntax

保存并关闭文件。

您将使用几个 Node.js 模块来生成 ID: cryptobase32-encode,其相应的解码器 base32-decode)。 该 crypto 模块包装了 Node.JS,但您将需要安装 base32-encodebase32-decode 以便在本教程中稍后使用。 编码是将字母序列(字母,数字,点数和某些符号)放入一个专门的格式,以便进行高效的传输或存储。 解码是相反的过程:将编码的格式转换回原始字符序列。 Base32 编码使用

在终端会话中,使用以下命令在项目文件夹中安装这些模块包:

1npm i base32-encode base32-decode

您将收到一个输出,表明这些模块已被添加:

1[secondary_label Output]
2added 3 packages, and audited 5 packages in 2s
3
4found 0 vulnerabilities

如果安装过程中遇到问题,请参阅 如何使用 npm 和 package.json 的 Node.js 模块以获得支持。

在项目文件夹中,创建一个名为index.js的新文件:

1nano index.js

将以下JavaScript代码列入index.js文件:

 1[label index.js]
 2import crypto from 'crypto';  
 3import base32Encode from 'base32-encode';
 4import base32Decode from 'base32-decode';
 5
 6function generate_Id(byte_size) {
 7    const bytes = crypto.randomBytes(byte_size);
 8    return base32Encode(bytes, 'Crockford');
 9}
10
11console.log('ID for byte size = 1:',generate_Id(1), '\n');
12console.log('ID for byte size = 12:',generate_Id(12), '\n');
13console.log('ID for byte size = 123:',generate_Id(123), '\n');

命令导入加载所需的模块. 要从数字中生成字节,您可以定义一个generate_Id函数来取代字节的大小,然后使用crypto模块的randomBytes函数创建这种大小的随机字节。

用于教学目的,会生成几个ID,然后登录到控制台. 在接下来的步骤中,将使用base32-decode模块来解码资源ID。

保存您的 index.js 文件,然后使用此命令在终端会话中运行代码:

1[environment second]
2node index.js

您将收到类似于此的输出响应:

1[environment second]
2[secondary_label Output]
3ID for byte size = 1: Y8 
4
5ID for byte size = 12: JTGSEMQH2YZFD3H35HJ0 
6
7ID for byte size = 123: QW2E2KJKM8QZ7174DDB1Q3JMEKV7328EE8T79V1KG0TEAE67DEGG1XS4AR57FPCYTS24J0ZRR3E6TKM28AM8FYZ2AZTZ55C9VVQTABE0R7QRH7QBY7V3GBYBNN5D9JK0QMD9NXSWZN95S0772DHN43Q003G0QNTPA2J3AFA3P7Q167C1VNR92Z85PCDXCMEY0M7WA

您的 ID 值可能因生成字节的随机性而有所不同. 生成的 ID 可能更短或更长,取决于您选择的字节大小。

index.js中,使用JavaScript评论功能评论控制台输出(在行前添加//双片):

1[label index.js]
2...
3//console.log('ID for byte size = 1:',generate_Id(1), '\n'); 
4//console.log('ID for byte size = 12:',generate_Id(12), '\n');
5//console.log('ID for byte size = 123:',generate_Id(123), '\n');

这些行展示了编码将如何根据相关字节输出不同的标识符,因为这些行不会在以下部分中使用,您可以评论它们,如本代码块所示,或者完全删除它们。

在此步骤中,您通过编码随机字节创建了一个编码的 ID。

步骤 2 – 生成资源标识符

现在,您将创建具有支票总数字符的ID。生成支票总数字符是一个两步的过程。 为指导目的,创建组合函数的每个函数将在以下子节中单独构建。 首先,您将编写一个运行 modulo operation的函数。 然后,您将编写另一个函数,将结果绘制为支票总数字符,这就是您如何生成资源ID的支票总数。

运行一个模块操作

在本节中,您将将数字 ID 对应的字节转换为 0-36 之间的数字(包括限制,这意味着从 0 到 36 之间的任何数字,包括 0 到 36)。 数字 ID 对应的字节通过 modulo 操作转换为整数。

要执行此步骤,请将以下代码行添加到 index.js 文件的底部:

1[label index.js]
2...
3
4function calculate_checksum(bytes) {
5    const intValue = BigInt(`0x${bytes.toString('hex')}`);
6    return Number(intValue % BigInt(37));
7}

函数 calculate_checksum 与文件中早些时候定义的字节一起工作. 此函数将字节转换为六进制值,这些值进一步转换为 BigInteger BigInt 值. BigInt 数据类型代表比在 Javascript' 中原始数据类型 number' 所代表的数字更大的数字。 例如,虽然整数 37' 相对较小,但它被转换为 BigInt' 用于模块操作。

要实现这种转换,您首先使用BigInt转换方法设置intValue变量,使用toString方法将bytes设置为hex。然后,您使用Number构建器返回一个数字值,在其中您使用%符号运行模块操作,以使用37的样本值找到intValueBigInt之间的剩余部分。

如果「intValue」值为「123」(取决于「byte」),则模块操作将是「123 % 37」。

此函数将输入的字节绘制到 modulo 结果,接下来,您将写一个函数将 modulo 结果绘制到一个 checksum 字符。

获取支票金额特征

在上一节中获取modulo结果后,您可以将其绘制为检查总数字符。

添加以下代码行到前代码下面的 index.js 文件:

1[label index.js]
2...
3
4function get_checksum_character(checksumValue) {
5    const alphabet = '0123456789ABCDEFG' +
6        'HJKMNPQRSTVWXYZ*~$=U';  
7    return alphabet[Math.abs(checksumValue)]; // 
8}

对于get_checksum_character函数,您将checksumValue称为参数。在该函数中,您将名为alphabet的字符串常数定义为自定义的字符串。 根据checksumValue的值设置,此函数将返回一个值,该值将定义的字符串从alphabet常数与checksumValue的绝对值对齐。

接下来,您将编写一个函数,该函数使用这些部分中所写的两个函数来生成一个ID,从字节的编码结合到检查总数字符。

添加以下代码行到 index.js 文件中:

 1[label index.js]
 2... 
 3
 4function generate_Id_with_checksum(bytes_size) {
 5    const bytes = crypto.randomBytes(bytes_size);
 6    const checksum = calculate_checksum(bytes);
 7    const checksumChar = get_checksum_character(checksum);
 8    console.log("checksum character: ", checksumChar); 
 9    const encoded = base32Encode(bytes, 'Crockford');
10    return encoded + checksumChar;
11}
12
13const Hotel_resource_id =generate_Id_with_checksum(132)
14console.log("Hotel resource id: ",Hotel_resource_id)

此代码部分将您之前的两个函数calculate_checksumget_checksum_character(用于生成计数字符)相结合,并将编码函数组成一个名为generate_Id_with_checksum的新函数,该函数将创建具有计数字符的 ID。

保存文件,然后在单独的终端会话中运行代码:

1[environment second]
2node index.js

您将获得类似于此的输出:

1[environment second]
2[secondary_label Output]
3
4checksum character:  B
5Hotel resource id:  9V99B9P55K7M4DN5XYP4VTJYJGENZKJ0F9Q6EEEZ07X49G0V14AXJS3RYXBT3J1WJZXWGM76C6H7G895TJT27AW77BHBX2D16QNQ2ZNBY9MQHWG9NJ1WWVTNRCKRBX6HC3M7BB3JG0V413VJ767JN6FT0GFS5VQJ9X7KSP1KM29B02NAGXN3FP30WA8Y63N1XJAMGDPEE1RNHRTWH6P0B

同样的支票总数字符出现在 ID 的末尾,表示支票总数匹配。

此方案图提供了该复合函数如何工作的结构性表示:

A chart with Product ID at the top. It points to Crypto method, which points to Bytes. There are two branches from Bytes: base32-decode and Modulo process. The base32-decode branch points to the Encoded ID, whereas the Modulo process branch points to the Checksum. When the Encoded ID and Checksum are paired, they become the Resource ID.

此流程图展示了如何通过编码和模块化过程将产品ID,即由资源计数器手动创建的标识符转化为唯一的资源ID。

您根据字节大小创建了一个 ID,该 ID 将包含检查总数字符,在下一节中,您将执行一个标识符函数以通过base32 解码验证 ID 的完整性。

检查标识符的完整性

为了确保完整性,您将使用名为verify_Id的新函数将支票总数字符(标识符的最后一个字符)与生成的支票总数进行比较。

将这些行添加到您的 index.js 文件中:

 1[label index.js]
 2...
 3function verify_Id(identifier) {
 4    const value = identifier.substring( 0, identifier.length-1);
 5    const checksum_char = identifier[identifier.length-1];     
 6    const buffer = Buffer.from( base32Decode(value, 'Crockford'));
 7    const calculated_checksum_char = get_checksum_character(calculate_checksum(buffer));
 8    console.log(calculated_checksum_char);
 9    const flag =calculated_checksum_char== checksum_char;
10    return (flag);    
11     }
12console.log('\n');
13console.log("computing checksum")
14const flag = verify_Id(Hotel_resource_id);
15if (flag) console.log("Checksums matched.");
16else console.log("Checksums did not match.");

verify_Id函数通过检查支票总和来检查ID的完整性,其余的标识符符被解码为 buffer,然后在这个缓冲器上运行calculate_checksumget_checksum_character,以提取比较的支票总和符号(使用calculated_checksum_char== checksum_char)。

此方案图显示了复合函数的运作方式:

A chart with Resource ID at the top. It points to Slicing Method, which has two branches: Value and Checkum. The Value branch points to the base32-decode, which then becomes a Decoded checksum. The Checksum branch points to a Checksum. If the Decoded checksum and the Checksum match, it results in Verification.

在此图表中, slicing 指的是将 ID 值(‘值’)与支票总数字符(‘checksum’)分开。 在您之前的代码块中,函数 identifier.substring( 0, identifier.length-1) 会选取 ID 值,而 identifier[identifier.length-1] 会从资源 ID 选取最后一个字符。

您的 index.js 文件现在应该与以下代码匹配:

 1[label index.js]
 2import crypto from 'crypto';  // for generating bytes from the number
 3import base32Encode from 'base32-encode'; // for encoding the bytes into Unique ID as string type
 4import base32Decode from 'base32-decode';// for decoding the ID into bytes
 5
 6function generate_Id(byte_size) {
 7    const bytes = crypto.randomBytes(byte_size);
 8    return base32Encode(bytes, 'Crockford');
 9}
10
11//console.log('ID for byte size = 1:',generate_Id(1), '\n');
12//console.log('ID for byte size = 12:',generate_Id(12), '\n');
13//console.log('ID for byte size = 123:',generate_Id(123), '\n');
14
15function calculate_checksum(bytes) {
16    const intValue = BigInt(`0x${bytes.toString('hex')}`);
17    return Number(intValue % BigInt(37));
18}
19
20function get_checksum_character(checksumValue) {
21    const alphabet = '0123456789ABCDEFG' +
22        'HJKMNPQRSTVWXYZ*~$=U'; // custom-built string consisting of alphanumeric character
23    return alphabet[Math.abs(checksumValue)]; // picking out an alphanumeric character
24}
25
26function generate_Id_with_checksum(bytes_size) {
27    const bytes = crypto.randomBytes(bytes_size);
28    const checksum = calculate_checksum(bytes);
29    const checksumChar = get_checksum_character(checksum);
30    console.log("checksum character: ", checksumChar); 
31    const encoded = base32Encode(bytes, 'Crockford');
32    return encoded + checksumChar;
33}
34
35const Hotel_resource_id =generate_Id_with_checksum(132)
36console.log("Hotel resource id: ",Hotel_resource_id)
37
38function verify_Id(identifier) {
39    const value = identifier.substring( 0, identifier.length-1);
40    const checksum_char = identifier[identifier.length-1]; 
41    //console.log(value,checksum_char);
42    const buffer = Buffer.from( base32Decode(value, 'Crockford'));
43    const calculated_checksum_char = get_checksum_character(calculate_checksum(buffer));
44    console.log(calculated_checksum_char);
45    const flag =calculated_checksum_char== checksum_char;
46
47    return (flag);
48
49     }
50
51console.log('\n');
52console.log("computing checksum")
53const flag = verify_Id(Hotel_resource_id);
54if (flag) console.log("Checksums matched.");
55else console.log("Checksums did not match.");

现在你可以运行这个代码:

1[environment second]
2node index.js

您将获得以下输出:

1[environment second]
2[secondary_label Output]
3...
4computing checksum
5AW75SY7FVC7TKT7VP5ZF0M8C67CN36YZK27BXHVFHSDXJFKH54HK2AXQFMPN89Q5YQRPGNHGAYQ5JFKVD40EKTXCET97Q0FEPX6MX1ZTNWGCA08SBRSHP8B0037ACJG6F6472FEVARCAWM6P5MRJ2F6WTRPXHYS9N1JEDZVH41D33RA5365VNFC5G5VYEFPFJJD8151B28XXDBRHAF80 H
6H
7Checksums matched.

您现在有一个名为verify_Id的函数,该函数使用检查总数字符来检查您的标识符的完整性,接下来,为指导目的,您可以更改资源 ID,以便该函数提供不匹配的结果,以评估检查失败时会发生什么。

(可选)步骤 3 — 更改标识符以获取不匹配的结果

现在,您将更改标识符的值,以检查检查检查总数是否匹配。本步骤中的更改将始终导致不匹配的检查总数,因为如果对ID中的任何字符进行操纵,就不会保持完整性。这样的更改可能是由于传输错误或恶意行为造成的。

index.js文件中,通过添加突出的行来修改Hotel_resource_id:

1[label index.js]
2...
3const altered_Hotel_resource_id= Hotel_resource_id.replace('P','H');   
4console.log("computing checksum")
5const flag = verify_Id(altered_Hotel_resource_id);
6if (flag) console.log("Checksum matched.");
7else console.log("Checksums did not match.");

在上述代码中,您将 ID 中的任何P替换为H,并将变量从Hotel_resource_ID更名为altered_Hotel_resource_id

保存文件,然后重启代码,并对资源 ID 进行更改:

1[environment second]
2node index.js

您将收到一个输出,支票总数不匹配:

1[environment second]
2[secondary_label Output]
3Checksums did not match.

在此步骤中,您创建了一个功能来验证支票总数是否通过了完整性测试,并且您遇到过两种情况。不匹配的支票总数表明资源 ID 已被操纵。

若要将函数返回匹配的计数结果,请删除此步骤开始时添加的额外代码,以便该代码匹配步骤 2 结束时的文件。

当您需要一个自定义的独特ID和支票金额时,您可以使用本教程来帮助您生成数据模型,版本您的API等等。

结论

在本教程中,您开发了与一个好的标识符的特征一致的资源 ID. 您还创建了一个独特的资源 ID,在 Node.js 环境中使用base32-encoding

对于交叉确认,您可以将最终文件与 DigitalOcean 社区存储库中的文件进行比较。如果您熟悉git版本系统,您也可以将存储库git-clone或遵循 Introduction to GitHub and Open-Source Projects系列。

现在你已经了解了检查总量的基本知识,你可以尝试其他编码算法,例如 MD5

Published At
Categories with 技术
comments powered by Disqus