【问题标题】:Generate a set of unique keys that can be validated without keeping a white-list生成一组可以在不保留白名单的情况下进行验证的唯一密钥
【发布时间】:2018-11-16 13:29:30
【问题描述】:

我需要生成一组 10 字节的唯一 ID。这些集合可能非常大(即 10000 个值),并由有限内存设备检查其有效性。所以有人在设备中输入其中一个ID,设备应该能够辨别该ID是否真实(由我生成)。
基本方法是将相同的 ID 集存储在设备的内存中并检查列表,但我不能使用所有这些内存。
我认为的第二种方法是使用 CRC 或散列函数:例如,启用 CRC 为 X 的所有 ID。这里的问题是我应该遍历所有可能的 ID 组合以找到提供正确 CRC 的 ID。
理想情况下,我想找到一个/两个像这样工作的函数:

 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 (Cortex M0) 上进行计算

【问题讨论】:

  • 符号是数字(10 个符号)还是字母数字(如果固定大小写,则为 36 个符号,如果大小写混合则为 62,如果允许两个特殊字符加上固定字符,则为 64)。假设每个符号有 64 个值,那么 10 个符号 = 2^60 个可能的值,假设您允许 2^14 = 16384 个,那么您可以使用 46 位 CRC。如果有人知道它仅基于 CRC,它就不会那么安全了。在数据上使用一些异或模式可能会有所帮助。

标签: c hash key crc


【解决方案1】:

一个 10 字节的密钥不足以保证任何东西的安全。

您需要一个安全的散列函数,例如 SHA2-256,其输出长度为 32 字节。 SHA2 可以在大多数系统上轻松实现。

您的密钥需要两部分:

[text + hash]

第一部分类似于“用户名”,第二部分类似于“密码”

您还需要一个“密钥”。此密钥是存储在您的软件中的字节数组。然后,您将“密钥”添加到您的“用户名”。查找结果字符串的 SHA2 哈希。现在你有一个输出是原始文本的长度 + 32 字节的哈希值。

您可以将此密钥用作唯一的可验证 ID。

要测试密钥的真实性,请使用“用户名”部分并添加您的密钥。取那个字符串的 SHA2,结果应该匹配“密码”

如果保密性和唯一性不是大问题,那么您可以使用输出为 16 字节的 MD5。将纯文本更改为二进制,这样它就可以在更少的字节中存储更多信息,最终的密钥将只有 20 个字节。您可以再减少一点,但不建议减少到 10 个字节。

这是一个例子。我通过此链接使用了 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;
}

【讨论】:

  • 这是个好主意!您认为我可以对具有硬件功能的 AES128 使用相同的方法吗?唯一性很重要,我不希望两个ID发生冲突,安全性不太重要。
  • 我不知道如何以这种方式使用 AES。加密块是不相关的,因此您必须使用较弱的简单 ECB 模式。此外,AES 是一种 2 向加密,而哈希函数是 1 向加密。 MD5 容易受到攻击,但在这种情况下它比 AES 更好。 SHA2比较好,用于密码验证,这里比较适用。
  • @Vitomakes,从加密算法生成哈希函数并不难,您只需将源文本加密成固定大小的块,然后将它们全部异或(这是一个例子一种方法)块大小等于您想要的散列大小。但是你用的是什么只支持AES,根本不支持散列?为了避免碰撞你需要大键,你不能完全避免它,而是以概率的方式(源文本的空间总是大于散列键的空间)
  • @LuisColorado AES 只是“更容易”,因为我的 CPU 上有硬件外围设备,我根本不需要编写任何代码。顺便说一句,基本的 AES 块是 16 个字节,所以如果我将 ID 加长(正好 16 个字节),我可以只使用 1 个完整的块而没有冲突的风险,对吧?
  • @Vitomakes,您可以使用例如32 字节块大小(两个 AES 代码块,甚至更好,使用 30 字节块大小并从每个 AES 缓冲区中丢失两个字节,这将降低 AES 缓冲区之间的一致性)并获得更宽的散列空间,你会得到发生碰撞的机会更少。你真的有硬件 AES 但根本没有散列函数吗?奇怪。
【解决方案2】:

一种非常简单的方法是使用模乘逆,正如我在我的博客文章中所述: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。

在博文中显示的代码中,您可以通过修改xm 的值来更改映射。

如果您希望键是字母数字,则需要在生成整数后对其进行编码。然后你必须在用户输入它之后,在你尝试验证之前将它们解码回一个整数。

因此,为了您的验证,请将m 设置为一个非常大的数字。适当设置x,最好是大于m的素数。使用这些值生成前 10,000 个键。在应该验证这些数字的设备上,只需提供 xm 值,以及最大索引(即 10,000)。

因此,用户输入了他们获得的密钥。您运行密钥生成的逆过程并得到一个介于 1 和 10,000 之间的数字。你知道这个号码是有效的。如果您的反向计算返回的数字小于 1 或大于 10,000,则该键无效。

您可以将其扩展到多个设备,只需为每个设备提供它认为有效的开始和结束值。无论如何,反向键计算都是一样的。

这种技术确保了唯一性。安全性……主要是通过默默无闻。如果有人知道正在使用的算法,包括xm 值,并且知道设备设置接受的数字范围,那么他可以生成密钥来破坏系统。这是否是一个问题,只有你能回答。有人试图打败你的系统有什么风险,如果他们成功了,成本是多少?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-09-08
    • 2023-04-04
    • 1970-01-01
    • 2020-02-14
    • 1970-01-01
    • 1970-01-01
    • 2011-06-04
    • 1970-01-01
    相关资源
    最近更新 更多