生成一组可以在不保留白名单的情况下进行验证的唯一密钥
Generate a set of unique keys that can be validated without keeping a white-list
我需要生成一组 10 字节的唯一 ID。这些集合可能非常大(即 10000 个值),并由有限内存设备检查其有效性。所以有人在设备中输入其中一个ID,设备应该能够辨别这个ID是否是真实的(我生成的)。
基本方法是在设备内存中存储相同的一组 ID 并对照列表进行检查,但我不能使用所有内存。
我认为的第二种方法是使用 CRC 或散列函数:例如,启用 CRC 为 X 的所有 ID。这里的问题是我应该遍历所有可能的 ID 组合以找到给出正确 CRC 的 ID。
理想情况下,我想找到像这样工作的 one/two 函数:
uint8_t * generate_ID(uint16_t index);
bool is_valid validate_key(uint8_t * ID);
//optional
uint16_t index find_index(uint8_t * ID);
//example
//generate id value from index 0
uint8_t ID[10] = generate_ID(0)
//id is now {0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b}
bool is_valid = validate_key(ID);
//is_valid is True
uint16_t index = find_index(ID);
//index is now 0
ID[0] = 0xff; //change ID with random value
is_valid = validate_key(ID);
//is_valid is now False
//BONUS: use also a "seed" value, so that I can differentiate through sets of ids:
uint8_t * generate_ID(uint16_t index, uint16_t seed);
bool is_valid validate_key(uint8_t * ID, uint16_t seed);
find_index() 是可选的,因为一旦我知道密钥有效,我就可以简单地遍历所有索引以找到匹配的索引。
基本上,generate_ID() 函数应该足够复杂,这样即使不知道相当数量的 ID 也不会轻易猜到它,但能够在功率有限的嵌入式 CPU 上计算(皮质 M0)
一个 10 字节的密钥不足以保证任何安全。
您需要一个安全的散列函数,例如 SHA2-256,其输出长度为 32 字节。 SHA2 可以很容易地在大多数系统上实现。
您的密钥需要两部分:
[text + hash]
第一部分像 "username",第二部分像 "password"
您还需要一个"secret key"。此密钥是存储在您的软件中的字节数组。然后将 "secret key" 添加到 "username"。查找结果字符串的 SHA2 哈希。现在你有一个输出,它是原始文本的长度 + 哈希的 32 个字节。
您可以将此密钥用作唯一的可验证 ID。
要测试密钥的真实性,请使用 "username" 部分并添加您的密钥。对该字符串进行 SHA2,结果应匹配 "password"
如果保密性和唯一性不是大问题,那么您可以使用输出为 16 字节的 MD5。将纯文本更改为二进制,这样它可以用更少的字节存储更多的信息,而你的最终密钥将只有 20 个字节。您可以再减少一点,但不建议减少到 10 个字节。
这是一个例子。我使用了这个 link:
的 SHA2 实现
https://github.com/B-Con/crypto-algorithms(我不确定它是否适用于大端机器)
任何 SHA2 实现都应该有效。
void sha2(BYTE* dst, const BYTE* src, int len)
{
SHA256_CTX ctx;
sha256_init(&ctx);
sha256_update(&ctx, (const BYTE*)src, len);
sha256_final(&ctx, (BYTE*)dst);
}
void create_verifiable_id(const BYTE* source, BYTE *uid)
{
BYTE hash[32];
sha2(hash, source, ID_SIZE);
//combine source + hash
memcpy(uid, source, ID_SIZE);
memcpy(uid + ID_SIZE, hash, 32);
}
int test_verfiable_id(const BYTE *uid)
{
BYTE hash[32];
sha2(hash, uid, ID_SIZE);
//hash should match the second part of uid
return memcmp(hash, uid + ID_SIZE, 32) == 0;
}
int main(void)
{
//use a number from 0 to 0xFFFFFFFF, store in buf (4 bytes)
//this is the "plain text" portion
int number = 0x12345678;
BYTE buf[ID_SIZE];
for(int i = 0; i < sizeof(buf); i++)
{
buf[i] = number & 0xFF;
number >>= 8;
}
//add sha2 to "plain text" to make verifiable id
BYTE verifiable_id[32 + ID_SIZE];
create_verifiable_id(buf, verifiable_id);
printf("UID as hex string:\n");
for(int i = 0; i < 32 + ID_SIZE; i++)
printf("%02X", verifiable_id[i] & 0xFF);
printf("\n");
printf("Test (should succeed): %d\n", test_verfiable_id(verifiable_id));
//change verifiable_id and test it again
verifiable_id[0]++;
printf("Test (should fail): %d\n", test_verfiable_id(verifiable_id));
return 0;
}
一种非常简单的方法是使用模乘逆,正如我在我的博客 post 中所描述的:http://blog.mischel.com/2017/06/20/how-to-generate-random-looking-keys/.
我们的想法是将数字从 1 映射到某个数字 x,以便每个数字在同一范围内生成一个唯一值。因此,例如,映射可能是:
1 -> 9875
2 -> 362
3 -> 5247
...
这是一个可逆的计算。所以如果 f(1) => 9875,则 g(9875) => 1.
在博客 post 中显示的代码中,您可以通过修改 x
和 m
值来更改映射。
如果您希望密钥为字母数字,则需要在生成整数后对它们进行编码。然后你必须在用户输入之后和尝试验证之前将它们解码回整数。
因此,为了您的验证,请将 m
设置为一个非常大的数字。适当设置x
,最好是大于m
的素数。使用这些值生成前 10,000 个键。在应该验证这些数字的设备上,只需提供 x
和 m
值,以及最大索引(即 10,000)。
因此,用户输入他们获得的密钥。您 运行 与密钥生成相反,并获得 1 到 10,000 之间的数字。你知道这个号码是有效的。如果您的反向计算 returns 小于 1 或大于 10,000 的数字,则该密钥无效。
您可以将此扩展到多个设备,只需为每个设备提供它认为有效的开始和结束值。不管怎样,反向键的计算都是一样的。
此技术可确保唯一性。安全是......主要是通过默默无闻。如果有人知道正在使用的算法,包括 x
和 m
值,并且知道设备设置为接受的数字范围,那么他就可以生成密钥来击败系统。这是否是一个问题只有您才能回答。有人试图破坏您的系统的风险是什么?如果他们成功了,成本是多少?
我需要生成一组 10 字节的唯一 ID。这些集合可能非常大(即 10000 个值),并由有限内存设备检查其有效性。所以有人在设备中输入其中一个ID,设备应该能够辨别这个ID是否是真实的(我生成的)。
基本方法是在设备内存中存储相同的一组 ID 并对照列表进行检查,但我不能使用所有内存。
我认为的第二种方法是使用 CRC 或散列函数:例如,启用 CRC 为 X 的所有 ID。这里的问题是我应该遍历所有可能的 ID 组合以找到给出正确 CRC 的 ID。
理想情况下,我想找到像这样工作的 one/two 函数:
uint8_t * generate_ID(uint16_t index);
bool is_valid validate_key(uint8_t * ID);
//optional
uint16_t index find_index(uint8_t * ID);
//example
//generate id value from index 0
uint8_t ID[10] = generate_ID(0)
//id is now {0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b}
bool is_valid = validate_key(ID);
//is_valid is True
uint16_t index = find_index(ID);
//index is now 0
ID[0] = 0xff; //change ID with random value
is_valid = validate_key(ID);
//is_valid is now False
//BONUS: use also a "seed" value, so that I can differentiate through sets of ids:
uint8_t * generate_ID(uint16_t index, uint16_t seed);
bool is_valid validate_key(uint8_t * ID, uint16_t seed);
find_index() 是可选的,因为一旦我知道密钥有效,我就可以简单地遍历所有索引以找到匹配的索引。
基本上,generate_ID() 函数应该足够复杂,这样即使不知道相当数量的 ID 也不会轻易猜到它,但能够在功率有限的嵌入式 CPU 上计算(皮质 M0)
一个 10 字节的密钥不足以保证任何安全。
您需要一个安全的散列函数,例如 SHA2-256,其输出长度为 32 字节。 SHA2 可以很容易地在大多数系统上实现。
您的密钥需要两部分:
[text + hash]
第一部分像 "username",第二部分像 "password"
您还需要一个"secret key"。此密钥是存储在您的软件中的字节数组。然后将 "secret key" 添加到 "username"。查找结果字符串的 SHA2 哈希。现在你有一个输出,它是原始文本的长度 + 哈希的 32 个字节。
您可以将此密钥用作唯一的可验证 ID。
要测试密钥的真实性,请使用 "username" 部分并添加您的密钥。对该字符串进行 SHA2,结果应匹配 "password"
如果保密性和唯一性不是大问题,那么您可以使用输出为 16 字节的 MD5。将纯文本更改为二进制,这样它可以用更少的字节存储更多的信息,而你的最终密钥将只有 20 个字节。您可以再减少一点,但不建议减少到 10 个字节。
这是一个例子。我使用了这个 link:
的 SHA2 实现
https://github.com/B-Con/crypto-algorithms(我不确定它是否适用于大端机器)
任何 SHA2 实现都应该有效。
void sha2(BYTE* dst, const BYTE* src, int len)
{
SHA256_CTX ctx;
sha256_init(&ctx);
sha256_update(&ctx, (const BYTE*)src, len);
sha256_final(&ctx, (BYTE*)dst);
}
void create_verifiable_id(const BYTE* source, BYTE *uid)
{
BYTE hash[32];
sha2(hash, source, ID_SIZE);
//combine source + hash
memcpy(uid, source, ID_SIZE);
memcpy(uid + ID_SIZE, hash, 32);
}
int test_verfiable_id(const BYTE *uid)
{
BYTE hash[32];
sha2(hash, uid, ID_SIZE);
//hash should match the second part of uid
return memcmp(hash, uid + ID_SIZE, 32) == 0;
}
int main(void)
{
//use a number from 0 to 0xFFFFFFFF, store in buf (4 bytes)
//this is the "plain text" portion
int number = 0x12345678;
BYTE buf[ID_SIZE];
for(int i = 0; i < sizeof(buf); i++)
{
buf[i] = number & 0xFF;
number >>= 8;
}
//add sha2 to "plain text" to make verifiable id
BYTE verifiable_id[32 + ID_SIZE];
create_verifiable_id(buf, verifiable_id);
printf("UID as hex string:\n");
for(int i = 0; i < 32 + ID_SIZE; i++)
printf("%02X", verifiable_id[i] & 0xFF);
printf("\n");
printf("Test (should succeed): %d\n", test_verfiable_id(verifiable_id));
//change verifiable_id and test it again
verifiable_id[0]++;
printf("Test (should fail): %d\n", test_verfiable_id(verifiable_id));
return 0;
}
一种非常简单的方法是使用模乘逆,正如我在我的博客 post 中所描述的:http://blog.mischel.com/2017/06/20/how-to-generate-random-looking-keys/.
我们的想法是将数字从 1 映射到某个数字 x,以便每个数字在同一范围内生成一个唯一值。因此,例如,映射可能是:
1 -> 9875
2 -> 362
3 -> 5247
...
这是一个可逆的计算。所以如果 f(1) => 9875,则 g(9875) => 1.
在博客 post 中显示的代码中,您可以通过修改 x
和 m
值来更改映射。
如果您希望密钥为字母数字,则需要在生成整数后对它们进行编码。然后你必须在用户输入之后和尝试验证之前将它们解码回整数。
因此,为了您的验证,请将 m
设置为一个非常大的数字。适当设置x
,最好是大于m
的素数。使用这些值生成前 10,000 个键。在应该验证这些数字的设备上,只需提供 x
和 m
值,以及最大索引(即 10,000)。
因此,用户输入他们获得的密钥。您 运行 与密钥生成相反,并获得 1 到 10,000 之间的数字。你知道这个号码是有效的。如果您的反向计算 returns 小于 1 或大于 10,000 的数字,则该密钥无效。
您可以将此扩展到多个设备,只需为每个设备提供它认为有效的开始和结束值。不管怎样,反向键的计算都是一样的。
此技术可确保唯一性。安全是......主要是通过默默无闻。如果有人知道正在使用的算法,包括 x
和 m
值,并且知道设备设置为接受的数字范围,那么他就可以生成密钥来击败系统。这是否是一个问题只有您才能回答。有人试图破坏您的系统的风险是什么?如果他们成功了,成本是多少?